Severity: Critical
Type: Bug
Scope: Donations, Stellar
Labels: bug, help wanted, Official Campaign
Description
DonationsService.createDonation (src/donations/donations.service.ts, lines ~46–72) short-circuits when prisma.donation.findUnique({ where: { txHash } }) returns a row, returning the previously stored DonationResponseDto verbatim. There is no live re-check against Horizon or the Soroban RPC, even when the stored status is PENDING or FAILED.
Concrete impact:
- If
verifyDonationOnChain polled during a Stellar RPC outage and stored FAILED, but the transaction actually succeeded on-chain, every subsequent POST with the same txHash returns the stale FAILED row. The user's UI shows the donation was rejected even though it is in the ledger.
- If a future flow promotes the row to
REFUNDED and the user retries, the idempotent path returns the pre-refund CONFIRMED payload.
- Clients cannot distinguish "idempotent replay" from "actual recovery", so the only remediation today is a manual database fix.
Recommendation
- On the idempotent branch, if
donation.status is PENDING or FAILED, re-run stellarTxs.verifyDonationTransaction and update the row before returning.
- Add an idempotency window (e.g. stop re-polling after 30 seconds) to absorb legitimate retry storms from the wallet client instead of triggering one Horizon call per retry.
- Include a
recovered: boolean flag in the response so consumers can tell a fresh confirmation apart from a cached one.
Severity: Critical
Type: Bug
Scope: Donations, Stellar
Labels:
bug,help wanted,Official CampaignDescription
DonationsService.createDonation(src/donations/donations.service.ts, lines ~46–72) short-circuits whenprisma.donation.findUnique({ where: { txHash } })returns a row, returning the previously storedDonationResponseDtoverbatim. There is no live re-check against Horizon or the Soroban RPC, even when the storedstatusisPENDINGorFAILED.Concrete impact:
verifyDonationOnChainpolled during a Stellar RPC outage and storedFAILED, but the transaction actually succeeded on-chain, every subsequent POST with the sametxHashreturns the staleFAILEDrow. The user's UI shows the donation was rejected even though it is in the ledger.REFUNDEDand the user retries, the idempotent path returns the pre-refundCONFIRMEDpayload.Recommendation
donation.statusisPENDINGorFAILED, re-runstellarTxs.verifyDonationTransactionand update the row before returning.recovered: booleanflag in the response so consumers can tell a fresh confirmation apart from a cached one.