Skip to content

fix(liquidity): skip exchanges without API credentials in balance polling#3859

Merged
davidleomay merged 2 commits into
developfrom
fix/skip-unconfigured-exchange
Jun 11, 2026
Merged

fix(liquidity): skip exchanges without API credentials in balance polling#3859
davidleomay merged 2 commits into
developfrom
fix/skip-unconfigured-exchange

Conversation

@Danswar

@Danswar Danswar commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator

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_SECRET are unset — the ccxt call throws AuthenticationError: 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

  • Add an isConfigured getter to ExchangeService, returning true only when both ccxt credentials are present (apiKey and secret).
  • In ExchangeAdapter.getForExchange(), if the resolved exchange service is not configured, skip it gracefully: return empty balances for those assets instead of calling getBalances(), and log a single WARN per exchange (guarded by an "already warned" set) rather than an ERROR every cycle.
  • The "configured but the call failed" case is unchanged — it still logs ERROR and propagates, since that's a real error.

This is general, not XT-specific: it applies to every exchange and self-heals per environment (dev simply doesn't need XT keys). ScryptService does not extend ExchangeService and has no isConfigured, 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/exchange Jest suites pass, including 3 new isConfigured cases (no creds / apiKey only / both creds) added to exchange.service.spec.ts.

Files changed

  • src/integration/exchange/services/exchange.service.tsisConfigured getter
  • src/subdomains/core/liquidity-management/adapters/balances/exchange.adapter.ts — skip + warn-once
  • src/integration/exchange/services/__tests__/exchange.service.spec.ts — unit tests

@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown

⚠️ Unverified Commits (1)

The following commits are not signed/verified:

  • 0d2585a Skip exchanges with no API credentials in liquidity balance polling (Daniel Padrino)
How to sign commits
# SSH signing (recommended)
git config --global gpg.format ssh
git config --global user.signingkey ~/.ssh/id_ed25519.pub
git config --global commit.gpgsign true

# Re-sign last commit
git commit --amend -S --no-edit
git push --force-with-lease

@Danswar Danswar force-pushed the fix/skip-unconfigured-exchange branch from 0d2585a to 294848e Compare June 9, 2026 18:48
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.
@Danswar Danswar force-pushed the fix/skip-unconfigured-exchange branch from 294848e to f9c4657 Compare June 9, 2026 18:51
@Danswar Danswar changed the title Skip exchanges with no API credentials in liquidity balance polling fix(liquidity): skip exchanges without API credentials in balance polling Jun 9, 2026

@davidleomay davidleomay left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'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.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 0ee5edfisConfigured 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.

@davidleomay davidleomay merged commit 8659183 into develop Jun 11, 2026
7 checks passed
@davidleomay davidleomay deleted the fix/skip-unconfigured-exchange branch June 11, 2026 15:03
Danswar added a commit that referenced this pull request Jun 12, 2026
…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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants