From c94d651fc90c9b6be23133ea75b8ac2a9a27e176 Mon Sep 17 00:00:00 2001 From: philipp Date: Wed, 13 May 2026 15:47:23 +0200 Subject: [PATCH] Normalize Gnosis bridge chain for USDC Proof: Gnosis NEAR Intents assets now persist bridge chain eth:100, repo seed corrects legacy USDC chain rows, and tests prove USDC is not inventory-enabled until a pair activation enables it. Assumptions: NEAR Intents bridge deposit_address uses eth:100 for Gnosis funding handles while dashboard blockchain labels remain gnosis. Still fake: USDC deposit credit and live solver fills are not proven by this change; supported_tokens RPC remains upstream-unavailable. --- src/core/trading-config.mjs | 14 ++++++++-- src/lib/postgres.mjs | 8 +++--- test/trading-config.test.mjs | 50 +++++++++++++++++++++++++++++++++++- 3 files changed, 65 insertions(+), 7 deletions(-) diff --git a/src/core/trading-config.mjs b/src/core/trading-config.mjs index 709041b..0a05c64 100644 --- a/src/core/trading-config.mjs +++ b/src/core/trading-config.mjs @@ -33,6 +33,7 @@ export function normalizeOneClickToken(token) { const symbol = stringField(token.symbol, 'symbol'); const decimals = integerField(token.decimals, 'decimals'); const blockchain = stringField(token.blockchain ?? token.chain, 'blockchain'); + const chain = normalizeBridgeChain({ assetId, blockchain }); const contractAddress = optionalString(token.contractAddress ?? token.contract_address); const latestPrice = token.price == null ? null : String(token.price); const priceUpdatedAt = optionalTimestamp(token.priceUpdatedAt ?? token.price_updated_at); @@ -44,7 +45,7 @@ export function normalizeOneClickToken(token) { label: symbol, decimals, blockchain, - chain: blockchain, + chain, contractAddress, latestPrice, priceUpdatedAt, @@ -130,7 +131,7 @@ export function buildSeedAssets() { label: 'USDC', decimals: 6, blockchain: 'gnosis', - chain: 'gnosis', + chain: 'eth:100', contractAddress: '0x2a22f9c3b484c3629090feed35f17ff8f88f76f0', latestPrice: null, priceUpdatedAt: null, @@ -255,6 +256,15 @@ function optionalString(value) { return normalized || null; } +function normalizeBridgeChain({ assetId, blockchain }) { + const normalizedBlockchain = String(blockchain || '').trim(); + const normalizedAssetId = String(assetId || '').trim(); + if (normalizedBlockchain === 'gnosis' || normalizedAssetId.startsWith('nep141:gnosis-')) { + return 'eth:100'; + } + return normalizedBlockchain; +} + function integerField(value, field) { const number = Number(value); if (!Number.isInteger(number) || number < 0) { diff --git a/src/lib/postgres.mjs b/src/lib/postgres.mjs index 8a373f9..9d65da6 100644 --- a/src/lib/postgres.mjs +++ b/src/lib/postgres.mjs @@ -1564,11 +1564,11 @@ async function upsertSeedAsset(pool, { asset, now }) { symbol = EXCLUDED.symbol, label = EXCLUDED.label, decimals = EXCLUDED.decimals, - blockchain = COALESCE(${TRADING_ASSETS_TABLE}.blockchain, EXCLUDED.blockchain), - chain = COALESCE(${TRADING_ASSETS_TABLE}.chain, EXCLUDED.chain), + blockchain = EXCLUDED.blockchain, + chain = EXCLUDED.chain, contract_address = COALESCE(${TRADING_ASSETS_TABLE}.contract_address, EXCLUDED.contract_address), supported = ${TRADING_ASSETS_TABLE}.supported OR EXCLUDED.supported, - enabled_for_inventory = true, + enabled_for_inventory = ${TRADING_ASSETS_TABLE}.enabled_for_inventory OR EXCLUDED.enabled_for_inventory, role = COALESCE(${TRADING_ASSETS_TABLE}.role, EXCLUDED.role), withdraw_address = COALESCE(NULLIF(${TRADING_ASSETS_TABLE}.withdraw_address, ''), EXCLUDED.withdraw_address), raw_payload = CASE @@ -1766,7 +1766,7 @@ async function upsertImportedAsset(pool, { asset, fetchedAt }) { label = EXCLUDED.label, decimals = EXCLUDED.decimals, blockchain = EXCLUDED.blockchain, - chain = COALESCE(${TRADING_ASSETS_TABLE}.chain, EXCLUDED.chain), + chain = EXCLUDED.chain, contract_address = EXCLUDED.contract_address, latest_price = EXCLUDED.latest_price, price_updated_at = EXCLUDED.price_updated_at, diff --git a/test/trading-config.test.mjs b/test/trading-config.test.mjs index 39363cd..ddda109 100644 --- a/test/trading-config.test.mjs +++ b/test/trading-config.test.mjs @@ -38,6 +38,21 @@ test('1Click token normalizer preserves live asset fields', () => { assert.equal(token.priceUpdatedAt, '2026-05-12T16:25:00.425Z'); }); +test('1Click token normalizer maps Gnosis assets to bridge chain id', () => { + const token = normalizeOneClickToken({ + assetId: CURRENT_USDC_ASSET_ID, + decimals: 6, + blockchain: 'gnosis', + symbol: 'USDC', + price: 1, + priceUpdatedAt: '2026-05-12T16:25:00.425Z', + contractAddress: '0x2a22f9c3b484c3629090feed35f17ff8f88f76f0', + }); + + assert.equal(token.blockchain, 'gnosis'); + assert.equal(token.chain, 'eth:100'); +}); + test('supported token import is idempotent, does not enable inventory, and retires missing assets', async () => { const pool = createMemoryPool(); const first = await importSupportedAssets(pool, { @@ -106,9 +121,42 @@ test('seeded DB config preserves current nBTC/EURe pair, 49 bps edge, and legacy 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.assetRegistry.get(CURRENT_USDC_ASSET_ID).chain, 'eth:100'); + assert.equal(snapshot.trackedAssetIds.includes(CURRENT_USDC_ASSET_ID), false); assert.equal([...snapshot.makerPairKeys].some((pair) => pair.includes(LEGACY_OMFT_BTC_ASSET_ID)), false); }); +test('repo seed corrects legacy USDC Gnosis chain without inventory-enabling it', async () => { + const pool = createMemoryPool(); + pool.assets.set(CURRENT_USDC_ASSET_ID, { + asset_id: CURRENT_USDC_ASSET_ID, + venue: 'near-intents', + symbol: 'USDC', + label: 'USDC', + decimals: 6, + blockchain: 'gnosis', + chain: 'gnosis', + contract_address: '0x2a22f9c3b484c3629090feed35f17ff8f88f76f0', + latest_price: '1', + price_updated_at: '2026-05-12T16:25:00.425Z', + supported: true, + retired_at: null, + enabled_for_inventory: false, + role: null, + withdraw_address: '', + raw_payload: { source: 'legacy_test_row' }, + last_supported_at: '2026-05-12T16:25:00.425Z', + updated_at: '2026-05-12T16:25:00.425Z', + }); + + await seedTradingConfig(pool, { now: '2026-05-12T16:35:00.000Z' }); + const snapshot = await loadTradingConfig(pool); + const usdc = snapshot.assetRegistry.get(CURRENT_USDC_ASSET_ID); + + assert.equal(usdc.chain, 'eth:100'); + assert.equal(usdc.enabledForInventory, false); +}); + test('missing DB pair config fails closed', async () => { const snapshot = await loadTradingConfig(createMemoryPool()); @@ -536,7 +584,7 @@ function insertAsset(pool, params) { price_updated_at: priceUpdatedAt, supported: seed ? (previous?.supported || params[10]) : true, retired_at: null, - enabled_for_inventory: seed ? true : previous?.enabled_for_inventory === true, + enabled_for_inventory: seed ? previous?.enabled_for_inventory === true || params[11] === 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]),