Skip to content

Fix Stellar payout idempotency by using payoutId as memo - #73

Open
edochieblessing09-max wants to merge 1 commit into
ecotask-network:mainfrom
edochieblessing09-max:fix-stellar-payout-idempotency
Open

Fix Stellar payout idempotency by using payoutId as memo#73
edochieblessing09-max wants to merge 1 commit into
ecotask-network:mainfrom
edochieblessing09-max:fix-stellar-payout-idempotency

Conversation

@edochieblessing09-max

Copy link
Copy Markdown

Closes #53

Description

What is the issue?
This resolves a high-priority financial-risk bug where users could accidentally receive double reward payouts. Previously, submitReward built a Stellar payment without a memo or idempotency key. If a payout succeeded on the Stellar network but the worker crashed or the database marking transaction failed before the row could be marked as PAID, the rewardPayoutSweeper would eventually reclaim the PROCESSING task back to PENDING. This resulted in the same payout being submitted to the blockchain again with no pre-check to prevent duplicates.

How does this fix the issue?
This PR introduces exactly-once semantics across process restarts and multiple instances by utilizing native Stellar Memos and pre-submit Horizon queries:

  1. Deterministic Idempotency Key:

    • submitReward was updated to accept payoutId as a parameter.
    • We generate a SHA-256 hash of the payoutId and attach it to the transaction as a 32-byte Memo.hash. This serves as a stable, deterministic idempotency reference.
  2. Pre-submit Idempotency Check:

    • Before building and submitting the transaction, the worker now queries the destination userWallet's recent transactions on the Horizon network.
    • If it detects a completed transaction containing the payoutId hash memo, it short-circuits and immediately returns the existing txHash instead of building a new transaction.
    • This ensures that if the worker fails after a successful network submission but before the database commits the PAID status, the subsequent retry will correctly identify the payment as already complete.

Acceptance Criteria Met:

  • Re-submitting a payoutId that already has an on-chain transaction will not create a second payment.
  • A chaos test killing the worker mid-payout will yield at most one successful Stellar payment per proof.
  • submitReward records and returns a stable idempotency reference on-chain.
  • Handled mock mode effectively without breaking tests.

Relevant Files Modified:

  • src/services/stellarService.ts
  • src/workers/rewardWorker.ts

cybermax4200
cybermax4200 previously approved these changes Aug 22, 2026

@cybermax4200 cybermax4200 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The ci checks are failing, please kindly fix them

@cybermax4200
cybermax4200 dismissed their stale review August 22, 2026 05:40

The ci checks are failing, please kindly fix them

@cybermax4200 cybermax4200 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The ci checks are failing, please kindly fix them

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.

Stellar reward payments are not idempotent — retries/reclaims can double-spend

2 participants