Some checks failed
deploy / deploy (push) Failing after 34s
Proof: npm test passed 159/159; npm run operator-dashboard:build passed; repo-local Postgres importer smoke test imported 163 live 1Click tokens with only 3 inventory-enabled seed assets and nBTC/EURe pairs at 49 bps. Assumptions: Forgejo main push is the repo deployment path; production has existing repo-managed POSTGRES_URL/POSTGRES_PASSWORD/NEAR_INTENTS_API_KEY secrets; startup seed may create initial current nBTC/EURe config but must preserve DB runtime pair flags after creation. Still fake: no live funds movement was attempted; imported supported assets remain catalog-only unless explicitly enabled in DB; production rollout evidence still depends on the Forgejo deploy job completing after this push.
458 lines
16 KiB
JavaScript
458 lines
16 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
|
|
import { evaluateTradeOpportunity } from '../src/core/strategy.mjs';
|
|
import {
|
|
CURRENT_EURE_ASSET_ID,
|
|
CURRENT_NBTC_ASSET_ID,
|
|
LEGACY_OMFT_BTC_ASSET_ID,
|
|
normalizeOneClickToken,
|
|
} from '../src/core/trading-config.mjs';
|
|
import {
|
|
createPairStrategyConfigVersion,
|
|
enableObserveOnlyPair,
|
|
importSupportedAssets,
|
|
loadTradingConfig,
|
|
seedTradingConfig,
|
|
} from '../src/lib/postgres.mjs';
|
|
|
|
test('1Click token normalizer preserves live asset fields', () => {
|
|
const token = normalizeOneClickToken({
|
|
assetId: CURRENT_NBTC_ASSET_ID,
|
|
decimals: 8,
|
|
blockchain: 'near',
|
|
symbol: 'BTC',
|
|
price: 80293,
|
|
priceUpdatedAt: '2026-05-12T16:25:00.425Z',
|
|
contractAddress: 'nbtc.bridge.near',
|
|
});
|
|
|
|
assert.equal(token.assetId, CURRENT_NBTC_ASSET_ID);
|
|
assert.equal(token.decimals, 8);
|
|
assert.equal(token.symbol, 'BTC');
|
|
assert.equal(token.latestPrice, '80293');
|
|
assert.equal(token.priceUpdatedAt, '2026-05-12T16:25:00.425Z');
|
|
});
|
|
|
|
test('supported token import is idempotent, does not enable inventory, and retires missing assets', async () => {
|
|
const pool = createMemoryPool();
|
|
const first = await importSupportedAssets(pool, {
|
|
fetchedAt: '2026-05-12T16:30:00.000Z',
|
|
response: [
|
|
token(CURRENT_NBTC_ASSET_ID, 'BTC', 8),
|
|
token(CURRENT_EURE_ASSET_ID, 'EURe', 18),
|
|
],
|
|
});
|
|
|
|
assert.equal(first.added_count, 2);
|
|
assert.equal(pool.assets.get(CURRENT_NBTC_ASSET_ID).enabled_for_inventory, false);
|
|
assert.equal(Object.hasOwn(first, 'raw_response'), false);
|
|
|
|
const second = await importSupportedAssets(pool, {
|
|
fetchedAt: '2026-05-12T16:31:00.000Z',
|
|
response: [
|
|
token(CURRENT_NBTC_ASSET_ID, 'BTC', 8),
|
|
token(CURRENT_EURE_ASSET_ID, 'EURe', 18),
|
|
],
|
|
});
|
|
assert.equal(second.added_count, 0);
|
|
assert.equal(second.unchanged_count, 2);
|
|
|
|
const third = await importSupportedAssets(pool, {
|
|
fetchedAt: '2026-05-12T16:32:00.000Z',
|
|
response: [
|
|
{ ...token(CURRENT_NBTC_ASSET_ID, 'BTC', 8), price: 81000 },
|
|
],
|
|
});
|
|
assert.equal(third.updated_count, 1);
|
|
assert.equal(third.retired_count, 1);
|
|
assert.equal(pool.assets.get(CURRENT_EURE_ASSET_ID).supported, false);
|
|
assert.equal(pool.assets.get(CURRENT_EURE_ASSET_ID).retired_at, '2026-05-12T16:32:00.000Z');
|
|
});
|
|
|
|
test('seeded DB config preserves current nBTC/EURe pair, 49 bps edge, and legacy BTC tracking', async () => {
|
|
const pool = createMemoryPool();
|
|
await seedTradingConfig(pool, { now: '2026-05-12T16:35:00.000Z' });
|
|
await seedTradingConfig(pool, { now: '2026-05-12T16:36:00.000Z' });
|
|
|
|
const snapshot = await loadTradingConfig(pool);
|
|
|
|
assert.equal(snapshot.ok, true);
|
|
assert.equal(snapshot.activePair, `${CURRENT_NBTC_ASSET_ID}->${CURRENT_EURE_ASSET_ID}`);
|
|
assert.equal(snapshot.pairs.length, 2);
|
|
assert.equal(snapshot.pairByKey.get(snapshot.activePair).strategyConfig.edgeBps, 49);
|
|
assert.equal(snapshot.trackedAssetIds.includes(LEGACY_OMFT_BTC_ASSET_ID), true);
|
|
assert.equal([...snapshot.makerPairKeys].some((pair) => pair.includes(LEGACY_OMFT_BTC_ASSET_ID)), false);
|
|
});
|
|
|
|
test('missing DB pair config fails closed', async () => {
|
|
const snapshot = await loadTradingConfig(createMemoryPool());
|
|
|
|
assert.equal(snapshot.ok, false);
|
|
assert.equal(snapshot.blockReason, 'no_enabled_pairs');
|
|
assert.equal(snapshot.enabledPairKeys.size, 0);
|
|
});
|
|
|
|
test('edge update creates a new active strategy version', async () => {
|
|
const pool = createMemoryPool();
|
|
await seedTradingConfig(pool);
|
|
|
|
const next = await createPairStrategyConfigVersion(pool, {
|
|
pairId: `${CURRENT_NBTC_ASSET_ID}->${CURRENT_EURE_ASSET_ID}`,
|
|
edgeBps: 75,
|
|
changedBy: 'test',
|
|
reason: 'test edge update',
|
|
});
|
|
const snapshot = await loadTradingConfig(pool);
|
|
const versions = [...pool.strategyConfigs.values()]
|
|
.filter((row) => row.pair_id === `${CURRENT_NBTC_ASSET_ID}->${CURRENT_EURE_ASSET_ID}`);
|
|
|
|
assert.equal(next.version, 2);
|
|
assert.equal(snapshot.pairByKey.get(`${CURRENT_NBTC_ASSET_ID}->${CURRENT_EURE_ASSET_ID}`).strategyConfig.edgeBps, 75);
|
|
assert.equal(versions.find((row) => row.version === 1).active, false);
|
|
});
|
|
|
|
test('observe-only enable does not downgrade an active trading pair', async () => {
|
|
const pool = createMemoryPool();
|
|
await seedTradingConfig(pool);
|
|
const pairId = `${CURRENT_NBTC_ASSET_ID}->${CURRENT_EURE_ASSET_ID}`;
|
|
|
|
const pair = await enableObserveOnlyPair(pool, {
|
|
assetIn: CURRENT_NBTC_ASSET_ID,
|
|
assetOut: CURRENT_EURE_ASSET_ID,
|
|
changedBy: 'test',
|
|
reason: 'avoid downgrade',
|
|
});
|
|
const snapshot = await loadTradingConfig(pool);
|
|
|
|
assert.equal(pair.pairId, pairId);
|
|
assert.equal(pair.mode, 'both');
|
|
assert.equal(snapshot.pairByKey.get(pairId).makerEnabled, true);
|
|
assert.equal(snapshot.pairByKey.get(pairId).takerEnabled, true);
|
|
});
|
|
|
|
test('observe-only enable creates a non-trading tracked pair', async () => {
|
|
const pool = createMemoryPool();
|
|
await seedTradingConfig(pool);
|
|
const pair = await enableObserveOnlyPair(pool, {
|
|
assetIn: LEGACY_OMFT_BTC_ASSET_ID,
|
|
assetOut: CURRENT_EURE_ASSET_ID,
|
|
changedBy: 'test',
|
|
reason: 'watch legacy route',
|
|
});
|
|
const snapshot = await loadTradingConfig(pool);
|
|
|
|
assert.equal(pair.mode, 'observe_only');
|
|
assert.equal(snapshot.pairByKey.get(`${LEGACY_OMFT_BTC_ASSET_ID}->${CURRENT_EURE_ASSET_ID}`).observeEnabled, true);
|
|
assert.equal(snapshot.pairByKey.get(`${LEGACY_OMFT_BTC_ASSET_ID}->${CURRENT_EURE_ASSET_ID}`).makerEnabled, false);
|
|
assert.equal(snapshot.pairByKey.get(`${LEGACY_OMFT_BTC_ASSET_ID}->${CURRENT_EURE_ASSET_ID}`).takerEnabled, false);
|
|
});
|
|
|
|
test('repo seed does not re-enable pair runtime flags already stored in DB', async () => {
|
|
const pool = createMemoryPool();
|
|
await seedTradingConfig(pool);
|
|
const pairId = `${CURRENT_NBTC_ASSET_ID}->${CURRENT_EURE_ASSET_ID}`;
|
|
const routeId = `${pairId}:btc-eur-reference`;
|
|
Object.assign(pool.pairs.get(pairId), {
|
|
mode: 'observe_only',
|
|
enabled: false,
|
|
status: 'disabled',
|
|
});
|
|
Object.assign(pool.routes.get(routeId), {
|
|
enabled: false,
|
|
});
|
|
|
|
await seedTradingConfig(pool);
|
|
const snapshot = await loadTradingConfig(pool);
|
|
const pair = snapshot.pairByKey.get(pairId);
|
|
|
|
assert.equal(pair.enabled, false);
|
|
assert.equal(pair.mode, 'observe_only');
|
|
assert.equal(pair.status, 'disabled');
|
|
assert.equal(pool.routes.get(routeId).enabled, false);
|
|
assert.equal(pair.priceRoute, null);
|
|
assert.equal(pair.makerEnabled, false);
|
|
assert.equal(pair.takerEnabled, false);
|
|
});
|
|
|
|
test('strategy uses DB pair config for current pair and persists config version', async () => {
|
|
const pool = createMemoryPool();
|
|
const snapshot = await seedTradingConfig(pool);
|
|
const result = evaluateTradeOpportunity({
|
|
demandEvent: {
|
|
payload: {
|
|
quote_id: 'quote-db-1',
|
|
pair: `${CURRENT_NBTC_ASSET_ID}->${CURRENT_EURE_ASSET_ID}`,
|
|
asset_in: CURRENT_NBTC_ASSET_ID,
|
|
asset_out: CURRENT_EURE_ASSET_ID,
|
|
request_kind: 'exact_in',
|
|
amount_in: '5000',
|
|
min_deadline_ms: '60000',
|
|
},
|
|
},
|
|
priceEvent: priceEvent(),
|
|
inventoryEvent: inventoryEvent(),
|
|
config: snapshot,
|
|
armed: true,
|
|
now: Date.parse('2026-05-12T16:35:05.000Z'),
|
|
});
|
|
|
|
assert.equal(result.decision.decision, 'actionable');
|
|
assert.equal(result.decision.edge_bps, '49');
|
|
assert.equal(result.decision.pair_config_version, '1');
|
|
assert.equal(result.command.quote_output.amount_out, '4975500000000000000');
|
|
assert.equal(result.command.pair_config_id, `${CURRENT_NBTC_ASSET_ID}->${CURRENT_EURE_ASSET_ID}:v1`);
|
|
});
|
|
|
|
function token(assetId, symbol, decimals) {
|
|
return {
|
|
assetId,
|
|
decimals,
|
|
blockchain: symbol === 'EURe' ? 'gnosis' : 'near',
|
|
symbol,
|
|
price: symbol === 'EURe' ? 1.17 : 80293,
|
|
priceUpdatedAt: '2026-05-12T16:25:00.425Z',
|
|
contractAddress: assetId.replace(/^nep141:/, ''),
|
|
};
|
|
}
|
|
|
|
function priceEvent() {
|
|
return {
|
|
ingested_at: '2026-05-12T16:35:00.000Z',
|
|
payload: {
|
|
price_id: 'price-db-1',
|
|
pair: `${CURRENT_NBTC_ASSET_ID}->${CURRENT_EURE_ASSET_ID}`,
|
|
eur_per_btc: '100000.00000000',
|
|
eure_per_btc: '100000.00000000',
|
|
btc_per_eur: '0.000010000000',
|
|
btc_per_eure: '0.000010000000',
|
|
source_used: 'kraken',
|
|
},
|
|
};
|
|
}
|
|
|
|
function inventoryEvent() {
|
|
return {
|
|
ingested_at: '2026-05-12T16:35:00.000Z',
|
|
payload: {
|
|
inventory_id: 'inventory-db-1',
|
|
spendable: {
|
|
[CURRENT_NBTC_ASSET_ID]: '1000000',
|
|
[CURRENT_EURE_ASSET_ID]: '10000000000000000000',
|
|
[LEGACY_OMFT_BTC_ASSET_ID]: '0',
|
|
},
|
|
pending_inbound: {
|
|
[CURRENT_NBTC_ASSET_ID]: '0',
|
|
[CURRENT_EURE_ASSET_ID]: '0',
|
|
[LEGACY_OMFT_BTC_ASSET_ID]: '0',
|
|
},
|
|
},
|
|
};
|
|
}
|
|
|
|
function createMemoryPool() {
|
|
return {
|
|
assets: new Map(),
|
|
pairs: new Map(),
|
|
strategyConfigs: new Map(),
|
|
routes: new Map(),
|
|
importRuns: new Map(),
|
|
audit: [],
|
|
async query(sql, params = []) {
|
|
if (/CREATE TABLE|CREATE (UNIQUE )?INDEX/i.test(sql)) return { rows: [], rowCount: 0 };
|
|
if (/SELECT \* FROM trading_assets\s*$/i.test(sql)) return rows(this.assets);
|
|
if (/SELECT \*\s+FROM trading_assets\s+ORDER BY/i.test(sql)) return rows(this.assets);
|
|
if (/INSERT INTO trading_assets/i.test(sql)) return insertAsset(this, params);
|
|
if (/UPDATE trading_assets/i.test(sql)) return retireAssets(this, params);
|
|
if (/INSERT INTO supported_asset_import_runs/i.test(sql)) return insertImportRun(this, params);
|
|
if (/SELECT \*\s+FROM supported_asset_import_runs/i.test(sql)) {
|
|
return { rows: [...this.importRuns.values()].slice(-1), rowCount: this.importRuns.size ? 1 : 0 };
|
|
}
|
|
if (/COUNT\(\*\)::INT AS known_count/i.test(sql)) {
|
|
const assets = [...this.assets.values()];
|
|
return {
|
|
rows: [{
|
|
known_count: assets.length,
|
|
supported_count: assets.filter((asset) => asset.supported).length,
|
|
retired_count: assets.filter((asset) => asset.retired_at || !asset.supported).length,
|
|
inventory_enabled_count: assets.filter((asset) => asset.enabled_for_inventory).length,
|
|
}],
|
|
rowCount: 1,
|
|
};
|
|
}
|
|
if (/INSERT INTO trading_pairs/i.test(sql)) return insertPair(this, params, sql);
|
|
if (/SELECT \*\s+FROM trading_pairs\s+WHERE pair_id = \$1/i.test(sql)) {
|
|
const row = this.pairs.get(params[0]);
|
|
return { rows: row ? [row] : [], rowCount: row ? 1 : 0 };
|
|
}
|
|
if (/SELECT \*\s+FROM trading_pairs/i.test(sql)) return rows(this.pairs);
|
|
if (/INSERT INTO pair_strategy_configs/i.test(sql)) return insertStrategyConfig(this, params);
|
|
if (/SELECT \*\s+FROM pair_strategy_configs\s+WHERE active = true/i.test(sql)) {
|
|
return { rows: [...this.strategyConfigs.values()].filter((row) => row.active), rowCount: 0 };
|
|
}
|
|
if (/SELECT \*\s+FROM pair_strategy_configs\s+WHERE pair_id = \$1 AND active = true/i.test(sql)) {
|
|
const active = [...this.strategyConfigs.values()]
|
|
.filter((row) => row.pair_id === params[0] && row.active)
|
|
.sort((left, right) => right.version - left.version)[0];
|
|
return { rows: active ? [active] : [], rowCount: active ? 1 : 0 };
|
|
}
|
|
if (/UPDATE pair_strategy_configs SET active = false/i.test(sql)) {
|
|
let count = 0;
|
|
for (const row of this.strategyConfigs.values()) {
|
|
if (row.pair_id === params[0] && row.active) {
|
|
row.active = false;
|
|
count += 1;
|
|
}
|
|
}
|
|
return { rows: [], rowCount: count };
|
|
}
|
|
if (/INSERT INTO pair_price_routes/i.test(sql)) return insertRoute(this, params, sql);
|
|
if (/SELECT \*\s+FROM pair_price_routes/i.test(sql)) {
|
|
return { rows: [...this.routes.values()].filter((row) => row.enabled), rowCount: 0 };
|
|
}
|
|
if (/INSERT INTO pair_config_audit_log/i.test(sql)) {
|
|
this.audit.push(params);
|
|
return { rows: [], rowCount: 1 };
|
|
}
|
|
throw new Error(`unhandled SQL in memory pool: ${sql}`);
|
|
},
|
|
};
|
|
}
|
|
|
|
function rows(map) {
|
|
return { rows: [...map.values()], rowCount: map.size };
|
|
}
|
|
|
|
function insertAsset(pool, params) {
|
|
const seed = params.length === 16;
|
|
const [
|
|
assetId,
|
|
venue,
|
|
symbol,
|
|
label,
|
|
decimals,
|
|
blockchain,
|
|
chain,
|
|
contractAddress,
|
|
latestPrice,
|
|
priceUpdatedAt,
|
|
] = params;
|
|
const previous = pool.assets.get(assetId);
|
|
const row = {
|
|
...(previous || {}),
|
|
asset_id: assetId,
|
|
venue,
|
|
symbol,
|
|
label,
|
|
decimals,
|
|
blockchain,
|
|
chain,
|
|
contract_address: contractAddress,
|
|
latest_price: latestPrice,
|
|
price_updated_at: priceUpdatedAt,
|
|
supported: seed ? (previous?.supported || params[10]) : true,
|
|
retired_at: null,
|
|
enabled_for_inventory: seed ? true : previous?.enabled_for_inventory === true,
|
|
role: seed ? params[12] : previous?.role || null,
|
|
withdraw_address: seed ? params[13] : previous?.withdraw_address || '',
|
|
raw_payload: JSON.parse(seed ? params[14] : params[10]),
|
|
last_supported_at: seed ? params[15] : params[11],
|
|
updated_at: seed ? params[15] : params[11],
|
|
};
|
|
pool.assets.set(assetId, row);
|
|
return { rows: [], rowCount: previous ? 0 : 1 };
|
|
}
|
|
|
|
function retireAssets(pool, params) {
|
|
const [retiredAt, importedIds] = params;
|
|
let count = 0;
|
|
for (const row of pool.assets.values()) {
|
|
if (row.venue === 'near-intents' && row.supported && !importedIds.includes(row.asset_id)) {
|
|
row.supported = false;
|
|
row.retired_at ||= retiredAt;
|
|
row.updated_at = retiredAt;
|
|
count += 1;
|
|
}
|
|
}
|
|
return { rows: [], rowCount: count };
|
|
}
|
|
|
|
function insertImportRun(pool, params) {
|
|
const row = {
|
|
run_id: params[0],
|
|
source_url: params[1],
|
|
fetched_at: params[2],
|
|
status: params[3],
|
|
token_count: params[4],
|
|
added_count: params[5],
|
|
updated_count: params[6],
|
|
unchanged_count: params[7],
|
|
retired_count: params[8],
|
|
raw_response_hash: params[9],
|
|
error: params[10],
|
|
raw_response: params[11] == null ? null : JSON.parse(params[11]),
|
|
};
|
|
pool.importRuns.set(row.run_id, row);
|
|
return { rows: [], rowCount: 1 };
|
|
}
|
|
|
|
function insertPair(pool, params, sql = '') {
|
|
const previous = pool.pairs.get(params[0]);
|
|
const row = {
|
|
pair_id: params[0],
|
|
venue: params[1],
|
|
asset_in: params[2],
|
|
asset_out: params[3],
|
|
mode: /mode = trading_pairs\.mode/i.test(sql) && previous ? previous.mode : params[4],
|
|
enabled: /enabled = trading_pairs\.enabled/i.test(sql) && previous ? previous.enabled : params[5],
|
|
status: /status = trading_pairs\.status/i.test(sql) && previous ? previous.status : params[6],
|
|
created_at: params[7],
|
|
updated_at: params[7],
|
|
};
|
|
pool.pairs.set(row.pair_id, row);
|
|
return { rows: [], rowCount: 1 };
|
|
}
|
|
|
|
function insertStrategyConfig(pool, params) {
|
|
const configId = params[0];
|
|
if (pool.strategyConfigs.has(configId)) return { rows: [], rowCount: 0 };
|
|
const row = {
|
|
config_id: configId,
|
|
pair_id: params[1],
|
|
version: params[2],
|
|
active: params[3],
|
|
edge_bps: params[4],
|
|
max_notional: params[5],
|
|
min_notional: params[6],
|
|
slippage_bps: params[7],
|
|
min_deadline_ms: params[8],
|
|
price_max_age_ms: params[9],
|
|
inventory_max_age_ms: params[10],
|
|
request_default_notional: params[11],
|
|
request_max_notional: params[12],
|
|
request_max_slippage_bps: params[13],
|
|
created_by: params[14],
|
|
reason: params[15],
|
|
created_at: '2026-05-12T16:35:00.000Z',
|
|
};
|
|
pool.strategyConfigs.set(configId, row);
|
|
return { rows: [], rowCount: 1 };
|
|
}
|
|
|
|
function insertRoute(pool, params, sql = '') {
|
|
const previous = pool.routes.get(params[0]);
|
|
const row = {
|
|
route_id: params[0],
|
|
pair_id: params[1],
|
|
source: params[2],
|
|
base_asset_id: params[3],
|
|
quote_asset_id: params[4],
|
|
route_config: JSON.parse(params[5]),
|
|
max_age_ms: params[6],
|
|
enabled: /enabled = pair_price_routes\.enabled/i.test(sql) && previous ? previous.enabled : params[7],
|
|
created_at: params[8],
|
|
updated_at: params[8],
|
|
};
|
|
pool.routes.set(row.route_id, row);
|
|
return { rows: [], rowCount: 1 };
|
|
}
|