Skip to main content
Does the upto design ship a Soroban contract? Yes. Our upto design ships a dedicated Soroban settlement contract (settle_upto via require_auth_for_args): no admin, no persistent storage, and it never holds a balance. SEP-41 allowances alone cannot enforce recipient binding or single settlement, which is why a contract is required. The implementation follows the converged design in x402-foundation/x402#3134 rather than a private variant — the rest of this document is why. Posted on stellar/x402-stellar#72 as comment 5303705529, 15 August 2026. This file is the text plus the reasoning behind it, versioned alongside the code that will implement whatever the thread settles on.

Why this is not a competing spec

The Stellar upto binding is already being worked out in the open, and further along than a reader arriving cold might assume:
  • stellar/x402-stellar#72 carries a full design with both schemes settling on testnet in real USDC, from keypair and smart accounts, alongside a fix upstream in x402-foundation/x402#3018.
  • A second implementation arrived at the same design independently, argued for validAfter to be enforced on-chain, and raised the atomic pull-max / pay-actual / refund shape.
  • x402-foundation/x402#3134 proposes the Stellar binding as a spec, implemented and tested on testnet.
So the design has converged and a spec is under review. A fourth private design would add a data point, not a decision. The questions still genuinely open — validAfter enforcement and residual-allowance hygiene — belong to the people who have implemented settlement, and both already have advocates there.

What we contributed instead

The requirements the discovery layer places on the spec. These are invisible from the settlement side, which is why three implementers had not raised them, and they are the part we are positioned to see: we run the catalog. 1. What does a listing advertise as the price of an upto resource? PaymentRequirements.amount is a single value. For exact it is the price. For upto the only figure a seller can honestly publish before the call is the ceiling, which is not the price and is usually much larger. A catalog that puts a ceiling in the field an agent reads as “cost” makes every metered service look expensive beside a fixed-price one — a bias against exactly the services upto exists to enable. The spec need not solve pricing, but it should say whether amount on an upto requirement is the ceiling, and ideally let a resource state a typical or unit price alongside it. Otherwise every catalog invents its own convention and cross-catalog price comparison stops being portable, which is the thing discovery is for. 2. Budget filters silently exclude the cheap case. “Find me something under X” is the natural agent behaviour. If a listing carries only the ceiling, the filter drops every metered service whose ceiling exceeds the budget even when the settled amount would land far below it. A unitPrice or typicalAmount hint, even non-normative, makes that filter correct instead of conservative. 3. Usage signals stop being comparable once amounts vary. Catalogs rank partly on observed settlements. Under exact, counting settlements is a reasonable proxy for usage. Under upto, a 0.001 settle and a 5.0 settle count the same, so an endpoint called constantly for trivial amounts outranks a substantial one — and gaming it gets cheaper the smaller the settlements are. If the settle response carries the actual amount in a stable place, saying so normatively lets catalogs weight by value rather than by count, which is materially harder to fake.

What the thread settled

Three outcomes, and together they set the scope of what this project should build. validAfter is bound on-chain already. It sits in the require_auth_for_args tuple and is enforced with if now < valid_after inside settle. That matters for discovery specifically: a listing is a cached claim a client may act on much later, and the more of the validity window the ledger enforces rather than the facilitator, the less a stale catalog entry can be turned into a payment nobody intended. Allowance hygiene resolves to approve → transfer_from → revoke, with autoRevoke always true. The alternative — atomic pull-max, pay-actual, refund-remainder — makes the contract take custody of money the payer never intended to spend purely to hand most of it back. The approve-and-revoke shape closes the same exposure window without the custody hop. Pricing metadata belongs at the discovery layer, not in the payment scheme. PaymentRequirements.amount stays unambiguously the authorization ceiling, and discovery decides how the settled amount is used. The reasoning is that upto spans per-token, per-second, per-byte, per-call, storage and multi-dimensional pricing, which share no unit, so a single unitPrice in the scheme would be wrong for most products. That third outcome delegates the problem to the layer this project builds. What we proposed back, and would open as a separate PR against the bazaar extension so #3134 stays narrow:
model and note are always expressible; unit and typical are optional precisely because the product types listed above show they are not universal. A catalog that receives none of them falls back to the ceiling and says so. The part only a catalog can do: a seller-declared typical is a claim, and the catalog is the sole party positioned to check it. Once the settled actual amount is exposed consistently, a catalog holding a resource’s settlement history compares declared-typical against observed-median and down-ranks the gap. That makes the field self-correcting instead of another number sellers optimise, and no settlement contract could enforce it even in principle — which is further evidence the metadata belongs where the thread put it.

What this commits us to

Implement whatever #3134 converges on rather than a variant of it, and say so publicly when we do. The funded work (Tranche 2) is the pricing metadata the thread delegated to discovery, proposed upstream as its own PR against the bazaar extension; a second conformant implementation with a published interop report — three implementations exist on Stellar and no two have been tested against each other, so interoperability is currently an assertion rather than a measured property; and the catalog-side implementation of the same shape. On process, and on deliverable 5.4. The RFP asks for a scheme_upto_stellar.md and asks that upstream contribution be coordinated through the x402 Technical Steering Committee. Preferring not to author a fourth private draft is a decision, not an oversight: #3134 already proposes the binding, a fourth draft would add a data point rather than a decision, and consolidation is what the thread is short of. So the position we take to the TSC is implement the standard rather than fork it. That preference is not a way out of the deliverable, and the commitment is unconditional in both directions. If neither open proposal has landed by the end of Tranche 2, we author and submit scheme_upto_stellar.md ourselves through the x402 spec process, at no change in cost or timeline — and equally, if the TSC would rather we author it sooner, the same tranche absorbs that. The RFP’s named artifact exists on every branch of the outcome; the only thing in question is whether it is ours or somebody else’s, and we would rather it were somebody else’s.