unrip/test/intent-requests.test.mjs
philipp fdeb1287b4
Some checks failed
deploy / deploy (push) Failing after 40s
Fix pair-native runtime validation gaps
Proof: targeted pair-native strategy, preflight, outcome, dashboard, and ops tests pass; full npm test passes 237/237; operator dashboard production bundle builds; ops watcher Python test passes.

Assumptions: DB asset, pair, strategy config, and price route rows remain canonical; legacy EURe fields stay only for old-row/API compatibility; local shell has no Kubernetes context for direct live namespace recheck.

Still fake: venue-native terminal fill ids and realized fee/PnL attribution remain unavailable; live deployment verification must happen through the repo workflow because manual cluster repair is out of scope.
2026-05-18 19:44:54 +02:00

624 lines
20 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('pair-native preflight treats explicit zero source_amount as invalid instead of using default', async () => {
const relay = buildRelay();
const pair = buildPair({
sourceAsset: USDC,
destinationAsset: BTC,
source: 'btc_usdc_reference',
routeId: 'btc-usdc-route',
});
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: 0 });
assert.equal(preflight.state, 'blocked');
assert.equal(preflight.reason_code, 'invalid_source_amount');
assert.equal(preflight.source_amount, '0');
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);
});