Some checks failed
deploy / deploy (push) Failing after 43s
Proof: Pair-native trade semantics and multi-asset outcome truth; strategy, request preflight, outcome attribution, valuation visibility, dashboard labels, alerts, and ops watch paths now use DB pair/asset/route metadata with nBTC/EURe compatibility and nBTC/USDC regressions covered. Assumptions: Postgres asset, pair, strategy config, and price route rows remain canonical; supported reference adapters remain BTC/EUR and BTC/USDC; deployment is push-driven through the existing Forgejo workflow. Still fake: Arbitrary multi-hop valuation, new execution venues, fee-complete realized PnL, venue-native terminal fill ingestion, and autonomous optimization remain unbuilt.
596 lines
19 KiB
JavaScript
596 lines
19 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
|
|
import { KeyPair } from 'near-api-js';
|
|
|
|
import { createIntentRequestController } from '../src/core/intent-request-controller.mjs';
|
|
import {
|
|
applySlippageBps,
|
|
buildSolverQuoteRequest,
|
|
computeBtcReceiveUnitsFromEure,
|
|
normalizeSolverQuotes,
|
|
parseDecimalToUnits,
|
|
selectBestSolverQuote,
|
|
} from '../src/core/intent-requests.mjs';
|
|
|
|
const BTC = {
|
|
assetId: 'nep141:btc.omft.near',
|
|
symbol: 'BTC',
|
|
decimals: 8,
|
|
};
|
|
const EURE = {
|
|
assetId: 'nep141:eure.omft.near',
|
|
symbol: 'EURe',
|
|
decimals: 18,
|
|
};
|
|
const USDC = {
|
|
assetId: 'nep141:usdc.omft.near',
|
|
symbol: 'USDC',
|
|
decimals: 6,
|
|
};
|
|
|
|
function buildConfig() {
|
|
return {
|
|
tradingBtc: BTC,
|
|
tradingEure: EURE,
|
|
nearIntentsAccountId: 'unrip.test.near',
|
|
nearVerifierContract: 'intents.near',
|
|
intentRequestDefaultAmountEure: 5,
|
|
intentRequestMaxAmountEure: 5,
|
|
intentRequestDefaultSlippageBps: 200,
|
|
intentRequestMaxSlippageBps: 200,
|
|
intentRequestMinDeadlineMs: 60_000,
|
|
intentRequestQuoteTimeoutMs: 10_000,
|
|
intentRequestPublishTimeoutMs: 10_000,
|
|
intentRequestStatusTimeoutMs: 10_000,
|
|
intentRequestInventoryMaxAgeMs: 30_000,
|
|
intentRequestPriceMaxAgeMs: 30_000,
|
|
executorResponseTimeoutMs: 10_000,
|
|
};
|
|
}
|
|
|
|
function buildStore({
|
|
inventoryUnits = '5000000000000000000',
|
|
nowIso = '2026-04-12T10:00:00.000Z',
|
|
inventorySyncedAt = nowIso,
|
|
priceObservedAt = nowIso,
|
|
} = {}) {
|
|
const preflights = [];
|
|
const submissions = [];
|
|
return {
|
|
preflights,
|
|
submissions,
|
|
async loadLatestInventorySnapshot() {
|
|
return {
|
|
ingested_at: nowIso,
|
|
payload: {
|
|
synced_at: inventorySyncedAt,
|
|
spendable: {
|
|
[EURE.assetId]: inventoryUnits,
|
|
[BTC.assetId]: '1000',
|
|
},
|
|
pending_inbound: {
|
|
[EURE.assetId]: '100000000000000000000',
|
|
},
|
|
},
|
|
};
|
|
},
|
|
async loadLatestMarketPrice() {
|
|
return {
|
|
ingested_at: nowIso,
|
|
payload: {
|
|
observed_at: priceObservedAt,
|
|
eure_per_btc: '50000',
|
|
},
|
|
};
|
|
},
|
|
async insertPreflight(payload) {
|
|
preflights.push(payload);
|
|
},
|
|
async findPreflight({ requestId, idempotencyKey }) {
|
|
return preflights.find((entry) => (
|
|
(requestId && entry.request_id === requestId)
|
|
|| (idempotencyKey && entry.idempotency_key === idempotencyKey)
|
|
)) || null;
|
|
},
|
|
async findSubmissionByRequest({ requestId }) {
|
|
return [...submissions].reverse().find((entry) => entry.request_id === requestId) || null;
|
|
},
|
|
async insertSubmissionResult(payload) {
|
|
submissions.push(payload);
|
|
},
|
|
async refreshOutcomes() {
|
|
return [];
|
|
},
|
|
};
|
|
}
|
|
|
|
function buildRelay() {
|
|
return {
|
|
quoteCalls: 0,
|
|
publishCalls: 0,
|
|
async quote() {
|
|
this.quoteCalls += 1;
|
|
return [{
|
|
quote_hash: 'quote-hash-1',
|
|
amount_out: '10000',
|
|
expiration_time: '2026-04-12T10:01:00.000Z',
|
|
}];
|
|
},
|
|
async publishIntent() {
|
|
this.publishCalls += 1;
|
|
return { status: 'OK', intent_hash: 'intent-hash-1' };
|
|
},
|
|
async getStatus() {
|
|
return { status: 'PENDING' };
|
|
},
|
|
};
|
|
}
|
|
|
|
function buildController({
|
|
store = buildStore(),
|
|
relay = buildRelay(),
|
|
armed = true,
|
|
verifierRegistered = true,
|
|
withMakerSuppressed = async (operation) => operation(),
|
|
getTradingConfig = null,
|
|
} = {}) {
|
|
return {
|
|
store,
|
|
relay,
|
|
controller: createIntentRequestController({
|
|
config: buildConfig(),
|
|
store,
|
|
relayRpcClient: relay,
|
|
verifierClient: {
|
|
async isPublicKeyRegistered() { return verifierRegistered; },
|
|
async currentSalt() { return '252812b3'; },
|
|
},
|
|
signer: KeyPair.fromRandom('ed25519'),
|
|
isArmed: () => armed,
|
|
isPaused: () => false,
|
|
getTradingConfig,
|
|
withMakerSuppressed,
|
|
now: () => Date.parse('2026-04-12T10:00:00.000Z'),
|
|
uuid: (() => {
|
|
let next = 1;
|
|
return () => `id-${next++}`;
|
|
})(),
|
|
}),
|
|
};
|
|
}
|
|
|
|
function buildPair({
|
|
sourceAsset,
|
|
destinationAsset,
|
|
source,
|
|
routeId,
|
|
canTrade = true,
|
|
blockReason = null,
|
|
}) {
|
|
const pairId = `${sourceAsset.assetId}->${destinationAsset.assetId}`;
|
|
return {
|
|
key: pairId,
|
|
pairId,
|
|
takerEnabled: true,
|
|
canTrade,
|
|
blockReason,
|
|
assetIn: sourceAsset,
|
|
assetOut: destinationAsset,
|
|
strategyConfig: {
|
|
configId: `${pairId}:v1`,
|
|
version: 1,
|
|
requestDefaultNotional: '5',
|
|
requestMaxNotional: null,
|
|
slippageBps: 200,
|
|
requestMaxSlippageBps: null,
|
|
minDeadlineMs: 60_000,
|
|
priceMaxAgeMs: 30_000,
|
|
inventoryMaxAgeMs: 30_000,
|
|
},
|
|
priceRoute: source ? {
|
|
routeId,
|
|
source,
|
|
baseAssetId: BTC.assetId,
|
|
quoteAssetId: sourceAsset.assetId === BTC.assetId ? destinationAsset.assetId : sourceAsset.assetId,
|
|
} : null,
|
|
};
|
|
}
|
|
|
|
test('EURe decimal parsing, BTC expected receive, and slippage math are exact enough for request limits', () => {
|
|
const sourceUnits = parseDecimalToUnits('5', EURE.decimals, { field: 'amount_eure' });
|
|
const expectedBtc = computeBtcReceiveUnitsFromEure({
|
|
eureUnits: sourceUnits,
|
|
eurPerBtc: '50000',
|
|
eureDecimals: EURE.decimals,
|
|
btcDecimals: BTC.decimals,
|
|
});
|
|
|
|
assert.equal(sourceUnits, '5000000000000000000');
|
|
assert.equal(expectedBtc, '10000');
|
|
assert.equal(applySlippageBps(expectedBtc, 200), '9800');
|
|
});
|
|
|
|
test('solver quote normalization selects the best quote above explicit min receive', () => {
|
|
const quotes = normalizeSolverQuotes({
|
|
quotes: [
|
|
{ quote_hash: 'low', amount_out: '9700' },
|
|
{ quote_hash: 'best', amount_out: '10050' },
|
|
{ quote_hash: 'ok', amount_out: '9900' },
|
|
],
|
|
});
|
|
const selected = selectBestSolverQuote(quotes, { minDestinationAmountUnits: '9800' });
|
|
|
|
assert.equal(selected.quote_hash, 'best');
|
|
assert.deepEqual(buildSolverQuoteRequest({
|
|
sourceAssetId: EURE.assetId,
|
|
destinationAssetId: BTC.assetId,
|
|
sourceAmountUnits: '5000000000000000000',
|
|
minDeadlineMs: 60_000,
|
|
}), {
|
|
defuse_asset_identifier_in: EURE.assetId,
|
|
defuse_asset_identifier_out: BTC.assetId,
|
|
exact_amount_in: '5000000000000000000',
|
|
min_deadline_ms: 60000,
|
|
});
|
|
});
|
|
|
|
test('preflight is side-effect-free and does not publish a live intent', async () => {
|
|
const { controller, relay } = buildController();
|
|
const preflight = await controller.preflight({ amount_eure: '5', slippage_bps: 200 });
|
|
|
|
assert.equal(preflight.state, 'draft');
|
|
assert.equal(preflight.live_submit_capable, true);
|
|
assert.equal(preflight.reason_code, 'quote_available');
|
|
assert.equal(relay.quoteCalls, 1);
|
|
assert.equal(relay.publishCalls, 0);
|
|
});
|
|
|
|
test('DB null request limits allow operator-chosen amount and slippage', async () => {
|
|
const store = buildStore({ inventoryUnits: '6000000000000000000' });
|
|
const relay = buildRelay();
|
|
relay.quote = async function quote() {
|
|
this.quoteCalls += 1;
|
|
return [{
|
|
quote_hash: 'uncapped-quote-hash',
|
|
amount_out: '12000',
|
|
expiration_time: '2026-04-12T10:01:00.000Z',
|
|
}];
|
|
};
|
|
const pairId = `${EURE.assetId}->${BTC.assetId}`;
|
|
const pair = {
|
|
key: pairId,
|
|
pairId,
|
|
takerEnabled: true,
|
|
canTrade: true,
|
|
assetIn: EURE,
|
|
assetOut: BTC,
|
|
strategyConfig: {
|
|
requestDefaultNotional: '5',
|
|
requestMaxNotional: null,
|
|
slippageBps: 200,
|
|
requestMaxSlippageBps: null,
|
|
minDeadlineMs: 60_000,
|
|
priceMaxAgeMs: 30_000,
|
|
inventoryMaxAgeMs: 30_000,
|
|
},
|
|
priceRoute: {
|
|
source: 'btc_eur_reference',
|
|
},
|
|
};
|
|
const { controller } = buildController({
|
|
store,
|
|
relay,
|
|
getTradingConfig: async () => ({
|
|
ok: true,
|
|
tradingEure: EURE,
|
|
tradingBtc: BTC,
|
|
pairByKey: new Map([[pairId, pair]]),
|
|
defaultTakerPair: pair,
|
|
}),
|
|
});
|
|
|
|
const preflight = await controller.preflight({ amount_eure: '6', slippage_bps: 250 });
|
|
|
|
assert.equal(preflight.state, 'draft');
|
|
assert.equal(preflight.reason_code, 'quote_available');
|
|
assert.equal(preflight.request_max_notional, null);
|
|
assert.equal(preflight.request_max_slippage_bps, null);
|
|
assert.equal(preflight.slippage_bps, 250);
|
|
assert.equal(preflight.live_submit_capable, true);
|
|
assert.equal(relay.quoteCalls, 1);
|
|
});
|
|
|
|
test('nBTC/USDC preflight accepts pair_id and source_amount without EURe price fields', async () => {
|
|
const nowIso = '2026-04-12T10:00:00.000Z';
|
|
const store = buildStore({ nowIso });
|
|
store.loadLatestInventorySnapshot = async () => ({
|
|
ingested_at: nowIso,
|
|
payload: {
|
|
synced_at: nowIso,
|
|
spendable: {
|
|
[USDC.assetId]: '10000000',
|
|
[BTC.assetId]: '0',
|
|
},
|
|
pending_inbound: {},
|
|
},
|
|
});
|
|
store.loadLatestMarketPrice = async ({ priceRouteId } = {}) => ({
|
|
ingested_at: nowIso,
|
|
payload: {
|
|
observed_at: nowIso,
|
|
price_id: 'price-usdc-1',
|
|
price_route_id: priceRouteId,
|
|
usdc_per_btc: '50000',
|
|
},
|
|
});
|
|
const relay = buildRelay();
|
|
relay.quote = async function quote(request) {
|
|
this.quoteCalls += 1;
|
|
assert.equal(request.defuse_asset_identifier_in, USDC.assetId);
|
|
assert.equal(request.defuse_asset_identifier_out, BTC.assetId);
|
|
assert.equal(request.exact_amount_in, '10000000');
|
|
return [{
|
|
quote_hash: 'usdc-quote-hash',
|
|
amount_out: '20000',
|
|
expiration_time: '2026-04-12T10:01:00.000Z',
|
|
}];
|
|
};
|
|
const pair = buildPair({
|
|
sourceAsset: USDC,
|
|
destinationAsset: BTC,
|
|
source: 'btc_usdc_reference',
|
|
routeId: 'btc-usdc-route',
|
|
});
|
|
const { controller } = buildController({
|
|
store,
|
|
relay,
|
|
getTradingConfig: async () => ({
|
|
ok: true,
|
|
tradingEure: EURE,
|
|
tradingBtc: BTC,
|
|
pairByKey: new Map([[pair.key, pair]]),
|
|
pairById: new Map([[pair.pairId, pair]]),
|
|
defaultTakerPair: pair,
|
|
}),
|
|
});
|
|
|
|
const preflight = await controller.preflight({
|
|
pair_id: pair.pairId,
|
|
source_amount: '10',
|
|
slippage_bps: 200,
|
|
});
|
|
|
|
assert.equal(preflight.state, 'draft');
|
|
assert.equal(preflight.reason_code, 'quote_available');
|
|
assert.equal(preflight.amount_eure, null);
|
|
assert.equal(preflight.source_asset_id, USDC.assetId);
|
|
assert.equal(preflight.destination_asset_id, BTC.assetId);
|
|
assert.equal(preflight.expected_destination_amount_units, '20000');
|
|
assert.equal(preflight.min_destination_amount_units, '19600');
|
|
assert.equal(preflight.notional, '10');
|
|
assert.equal(preflight.notional_symbol, 'USDC');
|
|
assert.equal(preflight.reference_price_id, 'price-usdc-1');
|
|
assert.equal(relay.quoteCalls, 1);
|
|
});
|
|
|
|
test('nBTC/USDC preflight blocks missing route before solver quote', async () => {
|
|
const relay = buildRelay();
|
|
const pair = buildPair({
|
|
sourceAsset: USDC,
|
|
destinationAsset: BTC,
|
|
source: null,
|
|
routeId: null,
|
|
canTrade: false,
|
|
blockReason: 'price_route_missing',
|
|
});
|
|
const { controller } = buildController({
|
|
relay,
|
|
getTradingConfig: async () => ({
|
|
ok: true,
|
|
tradingEure: EURE,
|
|
tradingBtc: BTC,
|
|
pairByKey: new Map([[pair.key, pair]]),
|
|
pairById: new Map([[pair.pairId, pair]]),
|
|
defaultTakerPair: pair,
|
|
}),
|
|
});
|
|
|
|
const preflight = await controller.preflight({ pair_id: pair.pairId, source_amount: '10' });
|
|
|
|
assert.equal(preflight.state, 'blocked');
|
|
assert.equal(preflight.reason_code, 'price_route_missing');
|
|
assert.equal(relay.quoteCalls, 0);
|
|
});
|
|
|
|
test('nBTC/USDC preflight blocks insufficient source inventory with source-specific reason', async () => {
|
|
const nowIso = '2026-04-12T10:00:00.000Z';
|
|
const store = buildStore({ nowIso });
|
|
store.loadLatestInventorySnapshot = async () => ({
|
|
ingested_at: nowIso,
|
|
payload: {
|
|
synced_at: nowIso,
|
|
spendable: {
|
|
[USDC.assetId]: '1',
|
|
},
|
|
pending_inbound: {},
|
|
},
|
|
});
|
|
store.loadLatestMarketPrice = async ({ priceRouteId } = {}) => ({
|
|
ingested_at: nowIso,
|
|
payload: {
|
|
observed_at: nowIso,
|
|
price_id: 'price-usdc-2',
|
|
price_route_id: priceRouteId,
|
|
usdc_per_btc: '50000',
|
|
},
|
|
});
|
|
const relay = buildRelay();
|
|
const pair = buildPair({
|
|
sourceAsset: USDC,
|
|
destinationAsset: BTC,
|
|
source: 'btc_usdc_reference',
|
|
routeId: 'btc-usdc-route',
|
|
});
|
|
const { controller } = buildController({
|
|
store,
|
|
relay,
|
|
getTradingConfig: async () => ({
|
|
ok: true,
|
|
tradingEure: EURE,
|
|
tradingBtc: BTC,
|
|
pairByKey: new Map([[pair.key, pair]]),
|
|
pairById: new Map([[pair.pairId, pair]]),
|
|
defaultTakerPair: pair,
|
|
}),
|
|
});
|
|
|
|
const preflight = await controller.preflight({ pair_id: pair.pairId, source_amount: '10' });
|
|
|
|
assert.equal(preflight.state, 'blocked');
|
|
assert.equal(preflight.reason_code, 'insufficient_spendable_usdc');
|
|
assert.equal(relay.quoteCalls, 0);
|
|
});
|
|
|
|
test('insufficient spendable EURe blocks before solver quote or signing', async () => {
|
|
const store = buildStore({ inventoryUnits: '0' });
|
|
const relay = buildRelay();
|
|
const { controller } = buildController({ store, relay });
|
|
const preflight = await controller.preflight({ amount_eure: '5', slippage_bps: 200 });
|
|
|
|
assert.equal(preflight.state, 'blocked');
|
|
assert.equal(preflight.reason_code, 'insufficient_spendable_eure');
|
|
assert.equal(preflight.inventory_snapshot.pending_inbound[EURE.assetId], '100000000000000000000');
|
|
assert.equal(relay.quoteCalls, 0);
|
|
assert.equal(relay.publishCalls, 0);
|
|
});
|
|
|
|
test('duplicate request submit returns stored result and never publishes twice', async () => {
|
|
const { controller, store, relay } = buildController();
|
|
const preflight = await controller.preflight({ amount_eure: '5', slippage_bps: 200 });
|
|
const first = await controller.submit({ request_id: preflight.request_id });
|
|
const second = await controller.submit({ request_id: preflight.request_id });
|
|
|
|
assert.equal(first.submission_result.status, 'accepted_by_relay');
|
|
assert.equal(second.duplicate, true);
|
|
assert.equal(relay.publishCalls, 1);
|
|
assert.equal(store.submissions.filter((entry) => entry.status === 'submit_requested').length, 1);
|
|
assert.equal(store.submissions.filter((entry) => entry.status === 'accepted_by_relay').length, 1);
|
|
});
|
|
|
|
test('executor disarmed blocks request submission without calling relay publish', async () => {
|
|
const { controller, relay } = buildController({ armed: false });
|
|
const preflight = await controller.preflight({ amount_eure: '5', slippage_bps: 200 });
|
|
const result = await controller.submit({ request_id: preflight.request_id });
|
|
|
|
assert.equal(result.submission_result.status, 'blocked');
|
|
assert.equal(result.submission_result.result_code, 'executor_disarmed');
|
|
assert.equal(relay.publishCalls, 0);
|
|
});
|
|
|
|
|
|
test('stale reference price blocks request preflight before solver quote', async () => {
|
|
const store = buildStore({ priceObservedAt: '2026-04-12T09:59:00.000Z' });
|
|
const relay = buildRelay();
|
|
const { controller } = buildController({ store, relay });
|
|
const preflight = await controller.preflight({ amount_eure: '5', slippage_bps: 200 });
|
|
|
|
assert.equal(preflight.state, 'blocked');
|
|
assert.equal(preflight.reason_code, 'stale_reference_price');
|
|
assert.equal(relay.quoteCalls, 0);
|
|
assert.equal(relay.publishCalls, 0);
|
|
});
|
|
|
|
test('unregistered signer blocks request preflight before solver quote or signing', async () => {
|
|
const relay = buildRelay();
|
|
const { controller } = buildController({ relay, verifierRegistered: false });
|
|
const preflight = await controller.preflight({ amount_eure: '5', slippage_bps: 200 });
|
|
|
|
assert.equal(preflight.state, 'blocked');
|
|
assert.equal(preflight.reason_code, 'signer_not_registered');
|
|
assert.equal(relay.quoteCalls, 0);
|
|
assert.equal(relay.publishCalls, 0);
|
|
});
|
|
|
|
test('relay publish failure records submit_requested first and never reports completion', async () => {
|
|
const relay = buildRelay();
|
|
relay.publishIntent = async function publishIntent() {
|
|
this.publishCalls += 1;
|
|
throw new Error('relay publish unavailable');
|
|
};
|
|
const { controller, store } = buildController({ relay });
|
|
const preflight = await controller.preflight({ amount_eure: '5', slippage_bps: 200 });
|
|
const result = await controller.submit({ request_id: preflight.request_id });
|
|
|
|
assert.equal(result.submission_result.status, 'failed');
|
|
assert.equal(result.submission_result.result_code, 'publish_intent_failed');
|
|
assert.equal(result.submission_result.result_text, 'relay publish unavailable');
|
|
assert.equal(relay.publishCalls, 1);
|
|
assert.equal(store.submissions[0].status, 'submit_requested');
|
|
assert.equal(store.submissions[1].status, 'failed');
|
|
assert.notEqual(result.submission_result.status, 'completed');
|
|
});
|
|
|
|
|
|
test('preflight suppresses maker responses while collecting solver quotes', async () => {
|
|
let suppressed = false;
|
|
let wrapperCalls = 0;
|
|
const relay = buildRelay();
|
|
relay.quote = async function quote() {
|
|
this.quoteCalls += 1;
|
|
assert.equal(suppressed, true);
|
|
return [{
|
|
quote_hash: 'external-quote-hash',
|
|
amount_out: '10000',
|
|
expiration_time: '2026-04-12T10:01:00.000Z',
|
|
}];
|
|
};
|
|
|
|
const { controller } = buildController({
|
|
relay,
|
|
withMakerSuppressed: async (operation, context) => {
|
|
wrapperCalls += 1;
|
|
assert.match(context.requestId, /^id-/);
|
|
suppressed = true;
|
|
try {
|
|
return await operation();
|
|
} finally {
|
|
suppressed = false;
|
|
}
|
|
},
|
|
});
|
|
|
|
const preflight = await controller.preflight({ amount_eure: '5', slippage_bps: 200 });
|
|
|
|
assert.equal(preflight.state, 'draft');
|
|
assert.equal(preflight.selected_quote.quote_hash, 'external-quote-hash');
|
|
assert.equal(wrapperCalls, 1);
|
|
assert.equal(suppressed, false);
|
|
});
|
|
|
|
|
|
test('refreshOutcomes refreshes relay status before recomputing settlement truth', async () => {
|
|
const { controller, store, relay } = buildController();
|
|
const preflight = await controller.preflight({ amount_eure: '5', slippage_bps: 200 });
|
|
await controller.submit({ request_id: preflight.request_id });
|
|
const accepted = store.submissions.find((entry) => entry.status === 'accepted_by_relay');
|
|
store.loadSubmissionsForStatusRefresh = async () => [accepted];
|
|
store.refreshOutcomes = async () => [{ request_id: preflight.request_id, outcome_status: 'failed' }];
|
|
relay.getStatus = async () => ({
|
|
intent_hash: accepted.intent_hash,
|
|
status: 'SETTLED',
|
|
data: { hash: 'settlement-tx-hash' },
|
|
});
|
|
|
|
const refreshed = await controller.refreshOutcomes();
|
|
const statusRefresh = store.submissions.at(-1);
|
|
|
|
assert.equal(refreshed.refreshed_statuses.length, 1);
|
|
assert.equal(refreshed.outcomes[0].outcome_status, 'failed');
|
|
assert.equal(statusRefresh.status, 'accepted_by_relay');
|
|
assert.equal(statusRefresh.result_code, 'relay_status_refreshed');
|
|
assert.equal(statusRefresh.relay_status, 'SETTLED');
|
|
assert.equal(statusRefresh.relay_status_response.data.hash, 'settlement-tx-hash');
|
|
assert.equal(statusRefresh.submitted_at, accepted.submitted_at);
|
|
assert.ok(statusRefresh.status_checked_at);
|
|
});
|