exact scheme. The upto scheme covers the metered variant, and Stellar essentials is the lighter introduction.
Auth entries, not pre-signed transactions
On Stellar, x402 does not work by having the buyer pre-sign a whole transaction. The buyer signs a Soroban authorization entry under Stellar’s authorization framework (CAP-46): a signed commitment that a specific contract invocation, atransfer(from, to, amount) on the payment asset’s Stellar Asset Contract (SAC), is authorized, and nothing else. The facilitator builds and submits the transaction that carries that entry.
This split is the whole reason fee sponsorship and the non-custodial property are possible. The buyer authorizes the transfer, and the facilitator owns the transaction.
The endpoint surface
The facilitator is a Hono app (apps/facilitator/src/app.ts) exposing the standard x402 surface, /supported, /verify, and /settle, plus /health, /metrics, and the /discovery/* Bazaar routes. Middleware order is deliberate: the body is parsed before the auth check runs, so a per-network authentication exemption (testnet is open) can read the request’s network before deciding.
The HTTP layer is thin on purpose. It validates the request shape, calls facilitator.verify() or facilitator.settle(), and catalogs the result as a side effect. The Soroban invocation is built and submitted inside the scheme objects, not in the HTTP handler. Those scheme objects come from a single registry (facilitator/build.ts), and /supported is derived from the same registry, so the facilitator can never advertise a scheme it cannot run, or reject as unsupported a scheme it advertises.
Rail402 does not reimplement verify and settle. It instantiates
@x402/stellar’s ExactStellarScheme per network and wraps it. The wrapper below adds reasons and guards; the cryptographic settlement is the upstream package.The payload format is accepted verbatim
An x402 Stellar payment payload is{ transaction }, a base64-encoded XDR transaction envelope. Rail402 accepts that shape exactly as the spec defines it, with no local field renames or “fixed” variants. This is what lets a stock client pay a Rail402 endpoint with no Rail402-specific code.
One guard sits in front of the decode. A string that is valid base64 but not a decodable envelope (for example "AAAA") throws a TypeError deep inside the SAC decode path; left alone, that escapes as a retryable HTTP 500. Rail402 checks decodability up front (isTransactionUndecodable, facilitator/scheme.ts) and returns a non-retryable invalid_exact_stellar_payload_malformed with a reason instead. A malformed payload is the client’s bug, and telling it to retry forever would be wrong.
Auth-entry validation
The facilitator accepts an authorization only if it authorizes exactly the declared call, asset, amount, and recipient, not replayed, not expired.@x402/stellar does this binding correctly: its validateAuthEntries requires address-credentialed entries (not source-account credentials), rejects a missing entry, rejects an expiration ledger beyond the allowed window, checks that the payer actually signed, and rejects a wrong asset, wrong recipient, wrong amount, or a facilitator that is itself the payer.
What the package does not do well is explain a rejection. It sets a machine reason on roughly one of its 20 rejection sites and leaves the human-readable message empty on the rest. Because every rejection must carry a non-null reason, Rail402 wraps the scheme in an enrichment layer (EnrichedExactStellarScheme, facilitator/scheme.ts):
1
It never reimplements the check
Verify and settle are delegated to the upstream scheme unchanged. The wrapper only acts on the result.
2
It re-attaches a non-null reason
On any failure, the wrapper maps the machine code to a registered, human-legible reason from the error registry. No rejection leaves the facilitator with an empty reason.
3
It recovers the discarded host error, once, off the hot path
For the single generic code
invalid_exact_stellar_payload_simulation_failed, the wrapper re-simulates the already-failed transaction (one extra RPC call, only on a path that has already failed) to recover the host error string the package threw away, then classifies it.facilitator/classify.ts) turns raw Soroban host errors into specific codes. SAC error ordinals map to a negative amount, a balance out of range, or a missing trustline; a __check_auth refusal maps to invalid_exact_stellar_payload_account_policy_refused; a nonce collision (Error(Auth, ExistingValue)) maps to a replay; the phrase “signature has expired” maps to an expiration. When attribution is ambiguous, it stays generic rather than guessing, because a wrong specific reason is worse than an honest generic one.
The non-custodial invariant
The facilitator never takes custody of funds and is never the source of them. The only fund movement any code path can cause is submitting the buyer-signed authorization exactly as authorized. This is structural, not a policy. In a settlement, the transfer’sfrom is the buyer, carried inside an address-credentialed auth entry the buyer signed. The transaction’s source is the facilitator, which pays the fee. Those are two different addresses by construction. If the facilitator ever appeared as a party to the transfer, the exact scheme rejects it outright (…unsupported_credential_type), because a transfer sourced by the transaction account would use source-account credentials instead of the signed auth entry.
You can see both facts on any settlement. On transaction 3f6031ed… the fee is charged to the facilitator’s signer and the transfer from is the buyer, a different address. Tampering with the payment (a changed amount, recipient, or asset) changes what was signed, so it fails signature verification and never settles. The negative tests prove each of these refusals with its own code.
Where it lives
Next steps
Fee sponsorship and fee ceilings
How the facilitator pays the fee, and the ceiling that guards it.
Expiration, replay, and front-running
Ledger-based validity and the verify-to-settle race.
The upto scheme
Metered settlement through a Soroban contract.
Verify it yourself
Confirm the non-custodial fee source on chain.