Skip to content

fix(exchange-tx): mark stale pending Scrypt deposits as ok instead of failed#3777

Closed
TaprootFreak wants to merge 1 commit into
developfrom
fix/scrypt-stale-pending-deposits-to-ok
Closed

fix(exchange-tx): mark stale pending Scrypt deposits as ok instead of failed#3777
TaprootFreak wants to merge 1 commit into
developfrom
fix/scrypt-stale-pending-deposits-to-ok

Conversation

@TaprootFreak
Copy link
Copy Markdown
Collaborator

Problem

The hourly cron cleanupStalePendingDeposits flips every still-pending DEPOSIT older than one day to 'failed' — regardless of which exchange it belongs to. For Scrypt that is structurally wrong: Bank → Scrypt SEPA inbound transfers have never produced a chargeback / bank_tx_return in the historical record. A stale pending row therefore does not mean the funds were rejected — it means Scrypt missed flipping the deposit status to ok even though the FIX BALANCE stream already shows the money on account.

This is the root cause of the financial-log overstatement we saw on 2026-05-27 (~100k CHF). The matching OlkyPay bank_tx#200493 (30'006.71 EUR) and bank_tx#200504 (80'006.76 EUR) couldn't pair with their Scrypt counterparts exchange_tx#138339 / #138379 because the cleanup cron had already marked them failed — so they kept counting in pending.toScrypt while the same amount was simultaneously visible in Asset.balance.amount (FIX-stream live balance) → ~110k EUR / ~100k CHF double count.

Fix

Partition the stale-deposit set in ExchangeTxService.cleanupStalePendingDeposits:

  • Scryptstatus = 'ok' (funds confirmed by arrival history; no chargebacks ever observed)
  • Every other exchangestatus = 'failed' (unchanged behaviour)

Two separate repo.update calls, both guarded against empty ID lists. One-line English comment over the partition explains the why (no historical chargebacks on Scrypt inbound).

Why this is the right place to fix it

PR #3775 (already open) tightens the FinancialDataLog receiver-filter so that even a failed Scrypt deposit gets matched against its bank_tx — that is a defensive aggregator-level patch. This PR fixes the root cause one layer down: stale Scrypt rows should never be failed to begin with. The two PRs are complementary (defense in depth) and can be merged in either order. After this PR, the filter in #3775 will mostly be there as a backstop.

Risk

  • If a future business change introduces real Scrypt chargebacks (e.g. new bank counter-party with reject capability), this assumption breaks and would over-count funds as ok that were actually returned. Mitigation: monitor bank_tx_return rows where the source bank_tx points to a Scrypt-type transfer. Today: zero such rows in the entire history.
  • Other exchanges (Kraken, Binance, MEXC, XT) keep the existing failed default — no behaviour change for them.
  • No schema migration; no API surface change.

Test plan

  • npm run lint
  • npm run format:check
  • npx tsc --noEmit
  • npx jest --testPathPattern="exchange-tx.service.spec" — 4/4 (new spec file: Scrypt → ok, non-Scrypt → failed, mixed batch partition, defined)
  • CI green
  • DEV verification after merge: trigger the cron, confirm next batch of stale Scrypt deposits comes out as ok
  • Production verification: existing failed Scrypt deposits #138339 / #138379 will need a one-time manual fix-up (separate DB op) — this PR only changes go-forward behaviour

… failed

Bank → Scrypt inbound transfers have never produced a chargeback in the
historical record, so a Scrypt deposit that is still pending after a day
means the funds did arrive and only the deposit status update was missed
on Scrypt's side. The previous blanket flip to 'failed' kept the
matching bank_tx unmatched in pending.toScrypt while Asset.balance.amount
already reflected the funds via the FIX BALANCE stream — observed as a
~100k CHF overstatement in totalBalanceChf.

Partition the stale set by exchange: Scrypt → 'ok' (funds confirmed by
arrival history), everything else → 'failed' (unchanged default).
@TaprootFreak TaprootFreak marked this pull request as ready for review May 27, 2026 12:19
@TaprootFreak TaprootFreak requested a review from davidleomay as a code owner May 27, 2026 12:19
@TaprootFreak
Copy link
Copy Markdown
Collaborator Author

Closing in favor of a cleaner approach.

Why

This PR flipped stale pending Scrypt deposits to 'ok' instead of 'failed' in the cleanup cron. Better than the previous behaviour, but still wrong: we synthesized a status from inside our API for an exchange whose status truth lives at Scrypt. If the funds actually arrive five days later (as observed with deposit #138339), an 'ok' set by us a day after creation would mean the financial-log sees the bank_tx as matched while the Scrypt liquidity hasn't risen yet → temporary under-count for those days.

The right fix: don't manipulate Scrypt status at all in our cleanup. Pending stays pending until Scrypt itself flips it via the syncExchanges cron. If Scrypt never flips it, our DB row stays pending (which is semantically correct as long as the money hasn't arrived).

→ Closing without merge. Replacement PR coming.

@davidleomay
Copy link
Copy Markdown
Member

Superseded by #3778 which fixes the root cause: the 24h stale deposit cleanup was racing against Scrypt's reconciliation window (can take >24h). Scrypt deposits now get 7 days before cleanup, allowing the sync to pick up the completed version.

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