upto lets a buyer authorize a spending ceiling and settle only the actual usage. It is the fit for a metered service like token billing, where the price is not known until the work runs. This page is the design and the on-ledger enforcement; The upto scheme is the lighter conceptual version, and the network spec is drafted for upstream contribution.
Rail402 authored the Stellar upto profile. There was an EVM spec and an SVM spec, but no scheme_upto_stellar.md, so the design below is that profile.
Why a contract
The genericupto spec mandates four cryptographic guarantees: single-use authorization, time bounds, recipient binding, and maximum-amount enforcement. On Stellar a signed auth entry commits to exact invocation arguments, so an exact-style transfer(from, to, amount) binds the amount at signing time and there is no way to settle it for less. Something has to sit between the buyer’s signature and the transfer.
SEP-41 allowances alone cannot be that something. An approve / transfer_from allowance lets a spender move up to an approved amount, which sounds right, but it does not bind the settlement to one recipient and it does not enforce a single settlement: a stale allowance can be drawn more than once, toward any recipient the spender chooses.
So Rail402 ships a minimal Soroban contract. Both existing profiles ship on-chain code (EVM’s x402UptoPermit2Proxy and SVM’s payment-channels program), and a contract-free Stellar profile would be the only one in the ecosystem that fails the generic spec’s own MUSTs.
The two-node authorization tree
The design’s key move is what the buyer signs and what it deliberately leaves unsigned. The buyer, as the transfer’sfrom, signs an authorization for a two-node invocation tree:
Every value in that tree is known at signing time. What is not signed is actual_amount and the settlement hook, arguments 7 and 8 of the on-ledger settle(token, from, to, max_amount, expiration_ledger, nonce, actual_amount, hook). Leaving them unsigned is the whole trick: the facilitator can fill in the real usage at settle without invalidating the buyer’s signature, because the signature only ever committed to the five values above (via Soroban’s require_auth_for_args).
At settle:
1
The facilitator substitutes the actual amount
It sets
actual_amount on the already-signed transaction. This is safe precisely because actual_amount is outside the signed tuple, so the substitution changes nothing the buyer committed to.2
The contract asserts the ceiling
settle refuses if actual_amount > max_amount. The buyer’s authorized ceiling is the hard maximum, enforced on chain.3
The contract moves exactly the actual amount
Using the signed allowance, the contract calls
transfer_from(spender = self, from = buyer, to, actual_amount), bound to the recipient the buyer signed.upto’s auth tree has a signed sub-invocation (the approve). The exact scheme forbids sub-invocations outright, so the two schemes need separate validation paths: Rail402’s upto validator deliberately permits the sub-invocation, while exact’s deliberately does not. Relaxing exact to allow sub-invocations would be a security regression, so they stay separate.The contract guarantees
The contract (contracts/upto-stellar/src/lib.rs, 17 Rust tests) enforces four properties on chain:
Single-use nonce
Each nonce is recorded in
temporary() storage and checked-then-set before any transfer. A replayed authorization meets a nonce that is already consumed. This backs the auth-layer replay refusal with a second, independent one.Ledger-bounded expiration
settle rejects an expiration_ledger in the past (AuthorizationExpired) and one beyond current + NONCE_TTL_LEDGERS (about 24 hours, InvalidExpiration). The upper bound guarantees the nonce record always outlives the authorization it makes single-use.Zero-amount is still consumed
A settle of
0 consumes the nonce, calls the hook with 0, and returns before any transfer. Single-use has to mean used, so a zero settlement cannot leave the authorization live and re-spendable.Settlement hook
After the transfer, the contract calls the buyer’s spending policy’s
release(from, nonce, actual). This is what lets a smart-account budget reconcile from the reserved ceiling down to the real charge.upto compose with an on-ledger budget rather than only enforce a per-payment ceiling. It is a versioned ABI (SettlementHook v1), and it is optional: a plain keypair buyer has no policy, so the hook is None and the contract short-circuits it. Reserving a ceiling on chain is the straightforward half; the hook adds the reconciliation that brings the reserved ceiling back down to the actual charge, so an agent’s budget reflects real spend rather than the worst case. See Smart accounts and spending policies for the reserve-then-reconcile flow it drives.
Proof
The contract is deployed on testnet and has settled real payments, including in USDC.Upstream contribution
upto is meant to benefit the whole ecosystem, so both the network spec and the implementation are prepared for upstream. The proposal and the design discussion with SDF engineers are public:
stellar/x402-stellar#71, proposinguptofor Stellar.stellar/x402-stellar#72, theexactanduptodesign discussion.
Next steps
Smart accounts and spending policies
The reserve-then-reconcile budget the hook drives.
Expiration and replay
The ledger window and single-use enforcement.
Verify it yourself
The settled upto transactions on chain.
Meter usage with upto
Wiring a metered endpoint as a seller.