Skip to content

feat(payouts): automatic Stellar on-chain transaction tracking#518

Open
Petah1 wants to merge 1 commit into
ANYTECHS:mainfrom
Petah1:feat/stellar-tx-tracking
Open

feat(payouts): automatic Stellar on-chain transaction tracking#518
Petah1 wants to merge 1 commit into
ANYTECHS:mainfrom
Petah1:feat/stellar-tx-tracking

Conversation

@Petah1

@Petah1 Petah1 commented Jun 25, 2026

Copy link
Copy Markdown

Closes #362


Summary

Adds an automatic background polling system that tracks Stellar payout transactions against the Horizon API, keeping Payout records synchronized with their actual on-chain state without any manual intervention.

Problem

When a Stellar payout is initiated via initiateStellarPayout, the signed transaction XDR is prepared and the hash stored, but the confirmation status is never automatically reconciled with Horizon. If the transaction is submitted externally (e.g. by the user's wallet) or is delayed on the network, the payout record stays in pending indefinitely.

Solution

  • Hash stored at preparation timeinitiateStellarPayout now writes onChainTxHash the moment the signed transaction is built, so the hash is available for polling before the XDR ever reaches Horizon.
  • Background pollerStellarConfirmationProcessor (BullMQ WorkerHost, concurrency 1) schedules a repeatable job on startup that fires every 30 s by default (STELLAR_CONFIRMATION_INTERVAL_MS).
  • Batch poll — each tick calls PayoutsService.pollPendingStellarPayouts(), which finds every pending/processing Stellar payout that has an onChainTxHash and no confirmedAt, then checks each one against Horizon.
  • Status transitions:
    • Horizon returns successful: truecompleted, confirmedAt set to Horizon's created_at timestamp
    • Horizon returns successful: falsefailed
    • Not found, below retry limit → retryCount++, lastAttemptAt updated
    • Not found, retry limit reached (STELLAR_CONFIRMATION_MAX_POLLS, default 20 ≈ 10 min) → failed
  • Idempotency — all terminal status writes use updateMany with a status: { in: [...] } + confirmedAt: null filter so concurrent workers racing on the same record produce exactly one update.
  • Error isolation — transient Horizon/network errors on one payout are caught and logged; remaining payouts in the batch still run. The existing StellarService circuit breaker (5 failures → 30 s recovery) protects against Horizon outages propagating to the job.

Changed Files

File Change
src/payouts/stellar-confirmation.queue.ts New — queue name + env-configurable constants
src/payouts/stellar-confirmation.processor.ts New — BullMQ processor + repeatable job scheduling
src/payouts/stellar-confirmation.processor.spec.ts New — full test suite
src/payouts/payouts.service.ts Store onChainTxHash in initiateStellarPayout; add pollPendingStellarPayouts + confirmOneStellarPayout; remove duplicate FeeService import
src/payouts/payouts.module.ts Register stellar-confirmation queue and StellarConfirmationProcessor

Environment Variables

Variable Default Description
STELLAR_CONFIRMATION_INTERVAL_MS 30000 How often (ms) the poller runs
STELLAR_CONFIRMATION_MAX_POLLS 20 Polls before an unconfirmed payout is marked failed

Test Plan

  • All new unit tests pass: stellar-confirmation.processor.spec.ts
  • Existing payout service tests pass (no regression from duplicate import removal and onChainTxHash addition)
  • Manual: initiate a Stellar payout on testnet, observe onChainTxHash is stored immediately on the record
  • Manual: confirm the poller updates status to completed and confirmedAt matches Horizon's created_at
  • Manual: submit an invalid transaction hash, confirm payout is marked failed after MAX_POLLS attempts
  • Manual: confirm that two simultaneous worker instances do not double-update the same payout record

Introduces a background BullMQ poller that periodically queries the
Horizon API for all Stellar payouts that have been submitted but not
yet confirmed on-chain, keeping payout records automatically
synchronized with their actual ledger state.

Changes:
- Store `onChainTxHash` in `initiateStellarPayout` so the hash is
  persisted as soon as the signed transaction is prepared
- Add `pollPendingStellarPayouts()` to PayoutsService: finds all
  pending/processing Stellar payouts with a non-null `onChainTxHash`
  and no `confirmedAt`, then calls `confirmOneStellarPayout()` for each
- `confirmOneStellarPayout()` calls `StellarService.getTransactionStatus()`;
  on success marks the payout `completed` + sets `confirmedAt` to the
  timestamp returned by Horizon; on chain rejection marks `failed`;
  when not yet found increments `retryCount` and marks `failed` after
  `STELLAR_CONFIRMATION_MAX_POLLS` (default 20) exhausted
- All DB writes use `updateMany` with status-filter conditions so
  concurrent workers cannot double-update the same record
- Per-payout transient errors are caught and logged without halting
  the rest of the batch
- Add `StellarConfirmationProcessor`: BullMQ WorkerHost (concurrency=1)
  that schedules a repeatable job on startup (default every 30 s,
  env-configurable via `STELLAR_CONFIRMATION_INTERVAL_MS`) and
  delegates each tick to `pollPendingStellarPayouts()`
- Register `stellar-confirmation` queue and processor in PayoutsModule
- Remove duplicate `FeeService` import that was present in the service
- Add tests covering successful confirmations, Horizon timestamp
  persistence, fallback timestamp, chain rejection, retry increment,
  max-polls exhaustion, idempotent updateMany guards, transient-error
  isolation, and processor scheduling
@drips-wave

drips-wave Bot commented Jun 25, 2026

Copy link
Copy Markdown

@Petah1 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

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.

Track Stellar Payout Transaction Status

1 participant