Keep funding addresses refreshing without token list
Some checks failed
deploy / deploy (push) Failing after 29s

Proof: supported_tokens bridge RPC failures no longer abort liquidity-manager deposit address refresh; regression tests cover the non-fatal warning path.

Assumptions: deposit handles remain chain-level NEAR Intents bridge data and Gnosis assets share the Gnosis handle when the bridge deposit_address RPC succeeds.

Still fake: USDC deposits are not proven credited yet; supported_tokens is still unavailable upstream until the bridge RPC responds successfully.
This commit is contained in:
philipp 2026-05-13 15:43:37 +02:00
parent 0f33a53fa9
commit 4bf15be22e
6 changed files with 94 additions and 11 deletions

View file

@ -18,6 +18,7 @@ import {
} from '../core/funding-observations.mjs'; } from '../core/funding-observations.mjs';
import { createJsonStateStore } from '../core/json-state-store.mjs'; import { createJsonStateStore } from '../core/json-state-store.mjs';
import { normalizeLiquidityState } from '../core/liquidity-state.mjs'; import { normalizeLiquidityState } from '../core/liquidity-state.mjs';
import { refreshSupportedTokens } from '../core/liquidity-supported-tokens.mjs';
import { buildBridgeWithdrawalPlan } from '../core/liquidity-withdrawals.mjs'; import { buildBridgeWithdrawalPlan } from '../core/liquidity-withdrawals.mjs';
import { createLogger, serializeError } from '../core/log.mjs'; import { createLogger, serializeError } from '../core/log.mjs';
import { assertFundingObservationEvent, assertLiquidityActionEvent } from '../core/schemas.mjs'; import { assertFundingObservationEvent, assertLiquidityActionEvent } from '../core/schemas.mjs';
@ -133,8 +134,12 @@ async function refresh() {
if (state.paused) return; if (state.paused) return;
try { try {
const supported = await bridgeClient.supportedTokens({ chains }); await refreshSupportedTokens({
state.supported_tokens = mapSupportedTokens(supported?.tokens || []); bridgeClient,
chains,
state,
logger,
});
for (const chain of chains) { for (const chain of chains) {
await refreshChain(chain, state); await refreshChain(chain, state);
@ -816,15 +821,6 @@ async function shutdown() {
process.on('SIGINT', shutdown); process.on('SIGINT', shutdown);
process.on('SIGTERM', shutdown); process.on('SIGTERM', shutdown);
function mapSupportedTokens(tokens) {
return Object.fromEntries(
tokens.map((token) => [
`${token.near_token_id}:${token.defuse_asset_identifier}`,
token,
]),
);
}
function mapDepositAssetId(deposit, chain) { function mapDepositAssetId(deposit, chain) {
return bridgeDepositAssetId(deposit, { return bridgeDepositAssetId(deposit, {
assetRegistry: runtimeConfig.assetRegistry, assetRegistry: runtimeConfig.assetRegistry,

View file

@ -3,6 +3,7 @@ export function normalizeLiquidityState(state, { withdrawalsFrozen }) {
state.deposits ||= {}; state.deposits ||= {};
state.tracked_withdrawals ||= {}; state.tracked_withdrawals ||= {};
state.supported_tokens ||= {}; state.supported_tokens ||= {};
state.supported_tokens_error ??= null;
state.funding_observations ||= {}; state.funding_observations ||= {};
state.funding_observations_by_handle ||= {}; state.funding_observations_by_handle ||= {};
state.funding_visibility_by_asset ||= {}; state.funding_visibility_by_asset ||= {};

View file

@ -0,0 +1,34 @@
import { serializeError } from './log.mjs';
export function mapSupportedTokens(tokens = []) {
return Object.fromEntries(
tokens.map((token) => [
`${token.near_token_id}:${token.defuse_asset_identifier}`,
token,
]),
);
}
export async function refreshSupportedTokens({
bridgeClient,
chains,
state,
logger = null,
}) {
try {
const supported = await bridgeClient.supportedTokens({ chains });
state.supported_tokens = mapSupportedTokens(supported?.tokens || []);
state.supported_tokens_error = null;
return { ok: true, token_count: Object.keys(state.supported_tokens).length };
} catch (error) {
const serialized = serializeError(error);
state.supported_tokens_error = serialized;
logger?.warn?.('supported_tokens_refresh_failed', {
details: {
chains,
error: serialized,
},
});
return { ok: false, error: serialized };
}
}

View file

@ -62,6 +62,7 @@ export function summarizeServiceState(service, state) {
'funding_observer_paused', 'funding_observer_paused',
'withdrawals_frozen', 'withdrawals_frozen',
'deposit_addresses', 'deposit_addresses',
'supported_tokens_error',
'withdrawal_defaults', 'withdrawal_defaults',
'latest_funding_observation_at', 'latest_funding_observation_at',
'last_refresh_at', 'last_refresh_at',

View file

@ -16,6 +16,7 @@ test('normalizeLiquidityState hydrates missing nested maps from persisted partia
assert.deepEqual(state.deposits, {}); assert.deepEqual(state.deposits, {});
assert.deepEqual(state.tracked_withdrawals, {}); assert.deepEqual(state.tracked_withdrawals, {});
assert.deepEqual(state.supported_tokens, {}); assert.deepEqual(state.supported_tokens, {});
assert.equal(state.supported_tokens_error, null);
assert.deepEqual(state.funding_observations, {}); assert.deepEqual(state.funding_observations, {});
assert.deepEqual(state.funding_observations_by_handle, {}); assert.deepEqual(state.funding_observations_by_handle, {});
assert.deepEqual(state.funding_visibility_by_asset, {}); assert.deepEqual(state.funding_visibility_by_asset, {});

View file

@ -0,0 +1,50 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import {
mapSupportedTokens,
refreshSupportedTokens,
} from '../src/core/liquidity-supported-tokens.mjs';
test('mapSupportedTokens indexes bridge tokens by near and intents identifiers', () => {
const mapped = mapSupportedTokens([
{
near_token_id: 'token.near',
defuse_asset_identifier: 'nep141:token.near',
decimals: 18,
},
]);
assert.deepEqual(Object.keys(mapped), ['token.near:nep141:token.near']);
assert.equal(mapped['token.near:nep141:token.near'].decimals, 18);
});
test('refreshSupportedTokens records warning state without throwing', async () => {
const state = {
supported_tokens: {
previous: { near_token_id: 'previous' },
},
};
const warnings = [];
const result = await refreshSupportedTokens({
bridgeClient: {
async supportedTokens() {
throw new Error('Bridge RPC supported_tokens failed');
},
},
chains: ['btc', 'gnosis'],
state,
logger: {
warn(event, fields) {
warnings.push({ event, fields });
},
},
});
assert.equal(result.ok, false);
assert.equal(state.supported_tokens.previous.near_token_id, 'previous');
assert.equal(state.supported_tokens_error.message, 'Bridge RPC supported_tokens failed');
assert.equal(warnings[0].event, 'supported_tokens_refresh_failed');
assert.deepEqual(warnings[0].fields.details.chains, ['btc', 'gnosis']);
});