All checks were successful
deploy / deploy (push) Successful in 33s
Proof: Adds repo-owned EURe-to-BTC request preflight, signing, gated live submission, durable request/result/outcome persistence, dashboard request lifecycle rows, and tests proving submitted/relay accepted are not completed without inventory movement. Assumptions: The NEAR Intents solver relay quote, publish_intent, and get_status JSON-RPC methods accept signed raw_ed25519 token_diff payloads with quote_hashes; live validation remains bounded to 5 EUR per attempt, at most five attempts, and 200 bps slippage. Still fake: Venue-native terminal fill linkage and fee-complete realized PnL are still unavailable; request completion is attributed from durable inventory deltas unless the venue later exposes a linked settlement id.
275 lines
9.3 KiB
JavaScript
275 lines
9.3 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 } = {}) {
|
|
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,
|
|
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');
|
|
});
|