You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Congratulations, @SagaciousBoss! 🎉 Your application was accepted by the repo's maintainers, and the issue is due on October 22, 2026.
🧑💻 @SagaciousBoss: Please resolve the issue such that the repo's maintainers have enough time to review your contribution before the due date. You'll earn Points for completing the issue on-time, which will make you eligible for a share of the Stellar Wave Program's reward pool.
Warning
When opening a PR, please link it to this issue to ensure it gets tracked accurately. Points are awarded when this issue is marked as completed by the maintainer.
🤠 Repo maintainers: Please keep an eye on the contributor's progress and review their work before the due date. You can manage this issue, including adjusting its complexity and points, here.
getCurrentRate returned 1.0 for any pair it did not recognise, so GBP -> BTC quoted one Bitcoin per pound and confirmConversion recorded it. Unquoted pairs now raise UNSUPPORTED_FIAT_PAIR; lockRate refuses to create a lock and GET /api/v1/stripe/rates answers 400.
Added backend/src/services/money.ts (bigint minor units, half-away-from-zero rounding) and routed FX conversion through it. 33.33 * 1.08 is 35.99640000000001 as a double, so the crypto amount disagreed with the fiat amount charged.
lockRate accepted NaN, negative and infinite amounts, producing locks whose conversion amount was NaN. It now validates and normalises sub-cent input to the currency's minor units.
processFiatRefund recorded status 'failed' (indistinguishable from a real failure), returned true without refunding anything when no Stripe payment intent was present, and was not idempotent, so a retry issued a second refund. It now records 'refunded' with refundedAt and collapses concurrent calls onto one refund.
getConversion / listConversions returned live internal references, letting a caller mutate a financial record. They now return snapshots.
idempotency() did not bind a key to its payload: reusing a key with a different body silently replayed the earlier response, so a caller could believe it created a new resource while holding the old one. It now fingerprints the body (object keys sorted) and answers 409 idempotency_key_reuse_mismatch on divergence. The response cache is also bounded instead of growing without limit.
Revenue pool recipient ratios were validated individually but not in aggregate, so a pool could be configured to distribute more than 100% of what it received. addRecipient and updateRecipientRatio now enforce sum(ratioBps) <= 10000.
recordDistribution had no idempotency on txHash, so a chain reorg or a retried indexer double-counted revenue against every recipient. It now returns the existing row on replay.
Tests:services/__tests__/money.test.ts (new), plus additions to services/__tests__/fiat-crypto.test.ts, middleware/__tests__/idempotency.test.ts and a new services/revenue-pool/__tests__/revenue-pool.test.ts.
😎 @SagaciousBoss: You earned 200 Points for completing this issue! Once the Wave's rewards are finalized, you'll be eligible for a percentage of the Wave's reward pool based on the percentage of total points you've earned. Learn more here. You can also Leave a review to share your experience working on this issue.
🧑💻 Repo maintainers: How'd the contributor do? Leave a review to share your experience working with them.
Activity
OmniZlatoon commentedon Oct 17, 2026
@OmniZlatoon has applied to work on this issue as part of the Stellar Wave Program's 10th wave.
ℹ️ Repo Maintainers: To accept this application, review their application or assign @OmniZlatoon to this issue.
SagaciousBoss commentedon Oct 17, 2026
@SagaciousBoss has applied to work on this issue as part of the Stellar Wave Program's 10th wave.
ℹ️ Repo Maintainers: To accept this application, review their application or assign @SagaciousBoss to this issue.
drips-wave commentedon Oct 17, 2026
Congratulations, @SagaciousBoss! 🎉 Your application was accepted by the repo's maintainers, and the issue is due on October 22, 2026.
🧑💻 @SagaciousBoss: Please resolve the issue such that the repo's maintainers have enough time to review your contribution before the due date. You'll earn Points for completing the issue on-time, which will make you eligible for a share of the Stellar Wave Program's reward pool.
Warning
When opening a PR, please link it to this issue to ensure it gets tracked accurately. Points are awarded when this issue is marked as completed by the maintainer.
🤠 Repo maintainers: Please keep an eye on the contributor's progress and review their work before the due date. You can manage this issue, including adjusting its complexity and points, here.
🌊 Happy Wave 🌊
feat: payout execution, money correctness and payout API security (#1183
SagaciousBoss commentedon Oct 17, 2026
Delivered in #1290 (merged).
Payment correctness work in that PR:
getCurrentRatereturned1.0for any pair it did not recognise, soGBP -> BTCquoted one Bitcoin per pound andconfirmConversionrecorded it. Unquoted pairs now raiseUNSUPPORTED_FIAT_PAIR;lockRaterefuses to create a lock andGET /api/v1/stripe/ratesanswers400.backend/src/services/money.ts(bigint minor units, half-away-from-zero rounding) and routed FX conversion through it.33.33 * 1.08is35.99640000000001as a double, so the crypto amount disagreed with the fiat amount charged.lockRateacceptedNaN, negative and infinite amounts, producing locks whose conversion amount wasNaN. It now validates and normalises sub-cent input to the currency's minor units.processFiatRefundrecorded status'failed'(indistinguishable from a real failure), returnedtruewithout refunding anything when no Stripe payment intent was present, and was not idempotent, so a retry issued a second refund. It now records'refunded'withrefundedAtand collapses concurrent calls onto one refund.getConversion/listConversionsreturned live internal references, letting a caller mutate a financial record. They now return snapshots.idempotency()did not bind a key to its payload: reusing a key with a different body silently replayed the earlier response, so a caller could believe it created a new resource while holding the old one. It now fingerprints the body (object keys sorted) and answers409 idempotency_key_reuse_mismatchon divergence. The response cache is also bounded instead of growing without limit.addRecipientandupdateRecipientRationow enforcesum(ratioBps) <= 10000.recordDistributionhad no idempotency ontxHash, so a chain reorg or a retried indexer double-counted revenue against every recipient. It now returns the existing row on replay.Tests:
services/__tests__/money.test.ts(new), plus additions toservices/__tests__/fiat-crypto.test.ts,middleware/__tests__/idempotency.test.tsand a newservices/revenue-pool/__tests__/revenue-pool.test.ts.Docs:
docs/PAYMENT_CORRECTNESS.md.drips-wave commentedon Oct 17, 2026
This issue has been completed by @SagaciousBoss as part of the Stellar Wave Program's 10th Wave 🥳
😎 @SagaciousBoss: You earned 200 Points for completing this issue! Once the Wave's rewards are finalized, you'll be eligible for a percentage of the Wave's reward pool based on the percentage of total points you've earned. Learn more here. You can also Leave a review to share your experience working on this issue.
🧑💻 Repo maintainers: How'd the contributor do? Leave a review to share your experience working with them.
fix: correct the type errors introduced in Smartdevs17#1290 (Smartdev…