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.
Every /api/v1/payouts route — including the read routes — now requires an authenticated principal holding finance_approver or admin. The check runs before any lookup, so an unauthenticated caller cannot use the status code to probe which batch ids exist.
The approver role is enforced twice: once in the route and again inside PayoutEngine on every decision, so a caller that reaches the engine by another path is not less protected.
Idempotency-Key on every money-moving route. Reusing a key with a different body is rejected with 409 idempotency_key_reuse_mismatch instead of silently replaying the earlier response, and the rejected request is not applied.
The response cache is bounded (DEFAULT_MAX_CACHE_ENTRIES = 10_000, oldest evicted first), so a flood of unique keys cannot exhaust memory.
Strict input allowlists on the new API: assets restricted to USDC / USDT / XLM / EURC, schedule cadence restricted to daily / weekly / monthly / threshold, amounts positive/finite and capped at 1_000_000, identifiers non-empty and bounded to 128 characters.
The asset allowlist and the amount ceiling are enforced in the engine as well as the route, so a caller that reaches PayoutEngine directly cannot pay an unlisted asset or an unbounded amount.
Route-scoped rate limits on top of the global /api limiter: 120 requests/minute for the router, 30 requests/minute for approve / reject / retry / release. evaluate and retries fan out to the settlement rail, so an unthrottled caller could turn a rail hiccup into a payout stampede.
Engine sentinel errors are mapped to sanitised HTTP responses; rail error text never reaches the client and the stack is only attached outside production by the shared errorHandler.
Fiat conversion hardening: conversions are capped at MAX_FIAT_CONVERSION_AMOUNT, settlement destinations must match the Stellar address format, and settlement references are now derived from hash(destination | amount | asset) instead of Math.random() so they reconcile against the chain.
Tests: new routes/__tests__/payouts.test.ts asserts authentication on every route including reads, role enforcement, validation, idempotency replay vs mismatch, the approve/reject lifecycle, and that the rate limiter is wired. Additional fingerprint and cache-eviction tests in middleware/__tests__/idempotency.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
innocentharuna commentedon Oct 17, 2026
@innocentharuna 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 @innocentharuna to this issue.
Obaara293 commentedon Oct 17, 2026
@Obaara293 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 @Obaara293 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 security work in that PR:
/api/v1/payoutsroute — including the read routes — now requires an authenticated principal holdingfinance_approveroradmin. The check runs before any lookup, so an unauthenticated caller cannot use the status code to probe which batch ids exist.PayoutEngineon every decision, so a caller that reaches the engine by another path is not less protected.Idempotency-Keyon every money-moving route. Reusing a key with a different body is rejected with409 idempotency_key_reuse_mismatchinstead of silently replaying the earlier response, and the rejected request is not applied.DEFAULT_MAX_CACHE_ENTRIES = 10_000, oldest evicted first), so a flood of unique keys cannot exhaust memory.USDC/USDT/XLM/EURC, schedule cadence restricted todaily/weekly/monthly/threshold, amounts positive/finite and capped at1_000_000, identifiers non-empty and bounded to 128 characters.PayoutEnginedirectly cannot pay an unlisted asset or an unbounded amount./apilimiter: 120 requests/minute for the router, 30 requests/minute forapprove/reject/retry/release.evaluateandretriesfan out to the settlement rail, so an unthrottled caller could turn a rail hiccup into a payout stampede.errorHandler.MAX_FIAT_CONVERSION_AMOUNT, settlement destinations must match the Stellar address format, and settlement references are now derived fromhash(destination | amount | asset)instead ofMath.random()so they reconcile against the chain.Tests: new
routes/__tests__/payouts.test.tsasserts authentication on every route including reads, role enforcement, validation, idempotency replay vs mismatch, the approve/reject lifecycle, and that the rate limiter is wired. Additional fingerprint and cache-eviction tests inmiddleware/__tests__/idempotency.test.ts.Docs:
docs/security/payment-security.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…