All checks were successful
deploy / deploy (push) Successful in 32s
Proof: Live dashboard-triggered request settled at the relay without the expected EURe decrease and BTC increase, exposing self-matching between our taker preflight and our maker responder. This change suppresses maker quote responses while an own-request preflight is collecting solver quotes, refreshes relay status before outcome derivation, and records relay SETTLED without expected durable inventory delta as failed rather than completed or not_filled. Assumptions: Suppressing maker responses during the short quote RPC window prevents our own quote from being selected for our own taker request. Relay SETTLED remains insufficient for success unless durable inventory shows the expected source decrease and destination increase. Still fake: Venue-native fill id parsing and fee-complete realized PnL for request-created trades are still not modeled; completed still depends on durable inventory delta attribution.
340 lines
12 KiB
JavaScript
340 lines
12 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,
|
|
};
|
|
|
|
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() } = {}) {
|
|
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,
|
|
withMakerSuppressed,
|
|
now: () => Date.parse('2026-04-12T10:00:00.000Z'),
|
|
uuid: (() => {
|
|
let next = 1;
|
|
return () => `id-${next++}`;
|
|
})(),
|
|
}),
|
|
};
|
|
}
|
|
|
|
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('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);
|
|
});
|