fix(liquidity): skip exchanges without API credentials in balance polling#3859
Conversation
|
0d2585a to
294848e
Compare
The liquidity-management exchange adapter polls every exchange's balance each cycle. When an exchange has no API credentials configured (e.g. XT on dev, where XT_KEY/XT_SECRET are unset), the ccxt call threw an AuthenticationError that was logged as ERROR and re-thrown every cycle (~60 error logs/hour). Add an isConfigured getter to ExchangeService (true only when both apiKey and secret are set) and, in the adapter, skip unconfigured exchanges gracefully: return empty balances instead of calling getBalances(), and warn once per exchange instead of erroring every cycle. The configured- but-failed case keeps the existing ERROR + re-throw behavior.
294848e to
f9c4657
Compare
davidleomay
left a comment
There was a problem hiding this comment.
The 'isConfigured' in exchangeService duck-type check is fragile. Since getExchange returns ExchangeService | ScryptService, ScryptService could define its own isConfigured (checking GetConfig().scrypt.apiKey / apiSecret, same pattern) and ExchangeService could declare it abstractly. Then the adapter simplifies to if (!exchangeService.isConfigured) — no in guard needed.
| const exchangeService = this.exchangeRegistry.getExchange(exchange); | ||
|
|
||
| // not configured (no API credentials) -> skip gracefully, warn once. Scrypt has no isConfigured and is treated as configured. | ||
| if ('isConfigured' in exchangeService && !exchangeService.isConfigured) { |
There was a problem hiding this comment.
'isConfigured' in exchangeService guards against ScryptService not having the getter. If ScryptService defined its own isConfigured (checking its own GetConfig().scrypt credentials) and ExchangeService declared it abstractly, this becomes if (!exchangeService.isConfigured) — cleaner and less fragile.
There was a problem hiding this comment.
Done in 0ee5edf — isConfigured is now part of the type contract: a getter on ExchangeService (checks the ccxt apiKey/secret) and ScryptService defines its own against GetConfig().scrypt. The adapter is now a plain if (!exchangeService.isConfigured), no in guard. CI green.
…ured (#3878) The Cardano register strategy polls Tatum every minute and always includes its payment deposit address, so on environments without a (funded) Tatum key every cycle fails with an ERROR (~60/h on dev, currently a 402 because the key's plan is out of credits). Follow the exchange-credentials pattern (#3859): expose isConfigured on CardanoClient (Tatum key present) and skip the cron with a single WARN when unset. A key that is set but rejected still errors, so a real prod misconfiguration stays visible. Solana/Tron are webhook-driven and make no unprompted Tatum calls, so no guard is needed there.
Problem
The liquidity-management exchange adapter polls every exchange's balance on each cycle. When an exchange has no API credentials configured — e.g. XT on dev, where
XT_KEY/XT_SECRETare unset — the ccxt call throwsAuthenticationError: xt requires "apiKey" credential. The adapter logged this as ERROR and re-threw it, producing roughly 60 error logs per hour, indefinitely. The app was asking an exchange for a balance using credentials it doesn't have.Fix
isConfiguredgetter toExchangeService, returning true only when both ccxt credentials are present (apiKeyandsecret).ExchangeAdapter.getForExchange(), if the resolved exchange service is not configured, skip it gracefully: return empty balances for those assets instead of callinggetBalances(), and log a single WARN per exchange (guarded by an "already warned" set) rather than an ERROR every cycle.This is general, not XT-specific: it applies to every exchange and self-heals per environment (dev simply doesn't need XT keys).
ScryptServicedoes not extendExchangeServiceand has noisConfigured, so it is treated as configured/unchanged.Safety note
A genuinely misconfigured prod exchange (keys missing by mistake) is not silently swallowed — it still surfaces a WARN once, so the misconfiguration remains visible.
Tests
tsc --noEmit: passes.src/integration/exchangeJest suites pass, including 3 newisConfiguredcases (no creds / apiKey only / both creds) added toexchange.service.spec.ts.Files changed
src/integration/exchange/services/exchange.service.ts—isConfiguredgettersrc/subdomains/core/liquidity-management/adapters/balances/exchange.adapter.ts— skip + warn-oncesrc/integration/exchange/services/__tests__/exchange.service.spec.ts— unit tests