Skip to content

Harden the x402 facilitator: spend ceiling, caller allow-list, settle idempotency #147

Closed

Description

@Miracle656

Part of #135 (SCF RFP epic). Follows #125 / #126 — this is the hardening those two leave open.

Background

POST /settle submits a Stellar transaction and, with areFeesSponsored: true, spends the facilitator's fee-payer XLM on every call. The route is registered config: { public: true }, which skips API-key auth.

The only thing bounding it today is the global limiter: 100 requests/minute per IP for unauthenticated callers. That is not nothing, but 100 sponsored transactions per minute per IP is a meaningful drain, and IP addresses are cheap.

This is fine while the fee payer holds testnet lumens. It is not fine the first time it points at a funded mainnet account, which is the direction the RFP work is going.

To be clear about what is not wrong: a facilitator's /settle is meant to be callable by resource servers, and x402 assumes that. The gap is that nothing bounds how much it can be made to spend.

What to build

Three independent controls. All three, not whichever is easiest.

1. Spend ceiling

A hard cap on facilitator outflow that does not depend on request counting:

  • per-settlement maximum (reject a payment requiring more than X stroops in fees)
  • rolling daily total per network, tracked in Redis or Postgres so it survives a restart
  • when the cap is hit: refuse further settles with a clear error, and log loudly. Degrading is correct; silently draining is not.

Per network — mainnet and testnet ceilings are not the same number.

2. Caller allow-list

An optional allow-list of resource servers permitted to settle (FACILITATOR_ALLOWED_ORIGINS, or signed caller identity). Empty means open, preserving today's behaviour, so this cannot break the demo path.

3. Settle idempotency

A replayed /settle for a payment already settled must return the original result rather than submitting a second transaction. Key on something derived from the payment payload — the nonce plus the authorisation entry, not a client-supplied id, which an attacker controls.

Key files

  • src/api/facilitator.ts (or wherever #125/#126 land)
  • src/index.ts — rate-limit registration, for a tighter per-route limit
  • src/config.ts — per-network config map, following the pattern in #113

Acceptance criteria

Each must be demonstrated by a test that fails without the control.

  • A settle whose fee exceeds the per-settlement cap is rejected before any transaction is submitted
  • Once the daily cap is reached, further settles are refused; the counter survives a process restart
  • Caps are configured and enforced per network; exhausting testnet does not block mainnet
  • With an allow-list configured, a caller outside it is refused; with it empty, behaviour is unchanged
  • Replaying an identical settle returns the original result and submits exactly one transaction — assert the submission count, not just the response body
  • /settle has its own rate limit, tighter than the global 100/min IP default
  • Refusals return a spec-valid x402 error shape, not a bare 500
  • Every rejection path is logged with enough detail to tell abuse from misconfiguration

Notes

  • Do not put the ceiling only in the rate limiter. Rate limits bound frequency; the thing being protected is a balance.
  • Fail closed. A Redis outage must not mean unlimited settling — decide the behaviour deliberately and state it in the code.
  • Worth checking against the upstream x402 spec whether a standard error code exists for "facilitator declined" before inventing one; the RFP is judged on spec compliance.

Drips Wave · Complexity: Advanced · 200 points


Required: Before submitting, join the contributor Telegram so your work can be tracked and counted toward the Stellar Wave: https://t.me/+fxHXq8f1SwlkZDBk

Activity

  1. sudo-robi commented on Aug 30, 2026

    @sudo-robi

    @sudo-robi has applied to work on this issue as part of the Stellar Wave Program's 8th wave.

    Hi maintainer I'm a full stack developer and smart contract developer and auditor let me help you resolve this issue and I assure you it will be thoroughly done neatly as I have seen my way to solve the issue.

    ℹ️ Repo Maintainers: To accept this application, review their application or assign @sudo-robi to this issue.

  2. TechBroAfrica commented on Aug 30, 2026

    @TechBroAfrica

    @TechBroAfrica has applied to work on this issue as part of the Stellar Wave Program's 8th wave.

    Focused on building trust through consistent, meaningful contributions. I approach every issue with curiosity, attention to detail, and a commitment to delivering clean, maintainable, and well-tested solutions. Driven by impact, not just commits, I value clear communication, effective collaboration with maintainers, continuous learning, and delivering meaningful value to every project I contribute to.

    ℹ️ Repo Maintainers: To accept this application, review their application or assign @TechBroAfrica to this issue.

  3. Clararosey67 commented on Aug 30, 2026

    @Clararosey67

    @Kingsilva36 has applied to work on this issue as part of the Stellar Wave Program's 8th wave.

    I would love to work on this professionally, please assign it to me

    ℹ️ Repo Maintainers: To accept this application, review their application or assign @Kingsilva36 to this issue.

  4. Daniel202251 commented on Aug 30, 2026

    @Daniel202251

    @Daniel202251 has applied to work on this issue as part of the Stellar Wave Program's 8th wave.

    I plead with you in the name of God please assign me this issue boss

    ℹ️ Repo Maintainers: To accept this application, review their application or assign @Daniel202251 to this issue.

  5. arandomogg commented on Aug 30, 2026

    @arandomogg
    Contributor

    @arandomogg has applied to work on this issue as part of the Stellar Wave Program's 8th wave.

    Hi, I’d like to work on this issue. I’m a full-stack blockchain developer with experience in JavaScript/TypeScript and Web3. I’ve reviewed the requirements and would be happy to implement the changes and submit a focused PR following the project’s conventions.

    ℹ️ Repo Maintainers: To accept this application, review their application or assign @arandomogg to this issue.

  6. alaminharuna-dev commented on Aug 30, 2026

    @alaminharuna-dev

    @alaminharuna-dev has applied to work on this issue as part of the Stellar Wave Program's 8th wave.

    Hi Maintainer, I came across this issue and found it relevant to my experience. I’d be glad to take some time to work through it.

    ℹ️ Repo Maintainers: To accept this application, review their application or assign @alaminharuna-dev to this issue.

  7. Adeolu01 commented on Aug 30, 2026

    @Adeolu01

    @Adeolu01 has applied to work on this issue as part of the Stellar Wave Program's 8th wave.

    I’d like to work on this issue, as I’ve gone through the requirements and can handle the implementation end-to-end, including updating the affected components, covering edge cases, and adding tests to prevent regressions.

    ℹ️ Repo Maintainers: To accept this application, review their application or assign @Adeolu01 to this issue.

  8. sudo-robi commented on Aug 31, 2026

    @sudo-robi

    @Miracle656 please the wave is about to end

  9. therealbibson commented on Sep 23, 2026

    @therealbibson
    Contributor

    @therealbibson has applied to work on this issue as part of the Stellar Wave Program's 9th wave.

    Hi Maintainer, I’ve read through the issue and I’m confident I can handle it. Could you please assign it to me

    ℹ️ Repo Maintainers: To accept this application, review their application or assign @therealbibson to this issue.

  10. drips-wave commented on Sep 23, 2026

    @drips-wave

    Congratulations, @therealbibson! 🎉 Your application was accepted by the repo's maintainers, and the issue is due on September 30, 2026.

    🧑‍💻 @therealbibson: 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 🌊

  11. Miracle656 commented on Oct 16, 2026

    @Miracle656
    CollaboratorAuthor

    @therealbibson — heads up before you see the assignment change, and please read this before worrying.

    Your PR #159 is open and this issue is yours. Nothing about that has changed.

    The mechanical problem: this issue was assigned on GitHub outside the Drips Wave, and the Wave will not let us add an already-assigned issue to the programme. That means that as things stand, when #159 merges you close a 200-point issue and earn nothing for it. That is not the outcome anyone wants.

    So I am going to unassign you on GitHub for a few minutes, add this issue to the Wave, and then approve your claim so it comes straight back to you. Please apply for it on Drips as soon as you see this so the gap is as short as possible. If anyone else applies in the meantime I will decline it — your PR predates all of this and the work is plainly yours.

    Doing the same for #166, #174 and #175, which are in the same position.

    Your other two PRs, #158 and #160, are in the review queue and I am getting to them today.

  12. Miracle656 commented on Oct 16, 2026

    @Miracle656
    CollaboratorAuthor

    @CollinsKRO and @Wittig18 — please do not start work on this one. Read this before you write any code, because I do not want either of you losing an evening to it.

    This issue already has a complete, open pull request: #159 by @therealbibson, submitted on 12 October. It implements the per-settlement fee ceiling, the rolling daily spend ceiling, the caller allow-list and the rate limit. It is in my review queue today.

    The issue looks unclaimed because of a problem on my side, not because it is free. It was assigned on GitHub outside the Wave, which meant it could not be added to the Wave programme, which meant the person who had already done the work would have earned nothing when it merged. To fix that I unassigned them so it could be added — and in the hour that gap was open, your two applications arrived. That is my mistake in sequencing, and I am sorry for the wasted attention.

    Both of your applications are declined for that reason alone, and not for anything about the applications themselves.

    @therealbibson — this is now in the Wave and waiting on you. Please apply through Drips as soon as you see this and I will approve it straight away. Your PR predates all of this and the work is plainly yours.

    Where to go instead. Only seven Wave issues in this repo are genuinely unclaimed right now, and these are the ones with no assignee and no PR:

    #129 and #130 are the Bazaar discovery pair and are also open, though they are the hardest in the track.

    Supply is the constraint here, not demand — more issues are being published to this repo continuously, so it is worth watching the label rather than racing for the last few. Apologies again for the confusion; it was caused by me, not by either of you.

  13. Miracle656 commented on Oct 16, 2026

    @Miracle656
    CollaboratorAuthor

    Correction to my earlier comments on this issue — please read, because I got this wrong.

    Earlier today I said PR #159 was finished work for this issue, that it was "plainly yours", and I unassigned @therealbibson so the issue could be added to the Wave specifically so that the work would be paid for when it merged.

    I have now read the branch rather than the file list, and #159 does not implement this issue. Its head carries one commit — the pair-issuer matching fix — plus 23 unrelated files (FontAwesome fonts, a .vscode directory). That fix is already on main, merged as 41b1f62 on 8 September, and the source is byte-identical to the copy on the branch. Merging #159 today would touch 123 files and revert work that has landed since.

    I based my earlier reading on the PR description and on GitHub's changed-files list, which shows what a branch changed since its merge base — not what merging it would do now. Both told a story the commit does not support. That is my mistake and I am sorry for the noise it caused on this issue.

    Where that leaves things:

    • This issue is genuinely unclaimed, and is in the Wave programme.
    • @therealbibson — if your work for it exists somewhere, push it from a fresh branch off current main and apply through Drips; I will review it the day it arrives, and there is nothing held against you.
    • Anyone else is welcome to apply for it. My earlier "do not apply, this is held" notice on this issue no longer stands.

    Details are on #159.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Stellar WaveIssues in the Stellar wave programarea:x402Payment middlewaredifficulty:advancedMulti-day projecthelp wantedExtra attention is neededpoints:200Advanced tier — 200 Wave points

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions