Skip to main content
x402-upto-stellar settles upto payments: the payer signs terms with a cap, and the settlement authority named in them later charges any amount from zero up to that cap. The contract is immutable. It has no admin, no upgrade path, no constructor and no storage, and it never holds funds. Its source is contracts/stellar/upto (Apache-2.0), written against soroban-sdk 28.0.0.

Deployments

View it on stellar.expert. There is no pubnet deployment. A facilitator serving upto on pubnet configures its own contract and WASM hash (PUBNET_UPTO_CONTRACT, PUBNET_UPTO_WASM_HASH), and refuses to start if the live contract runs other code. The testnet deployment record is contracts/stellar/deployments/testnet.json: The address depends only on the network, the deployer and the salt, so it was known before anything was sent, and only the deployer can create a contract there. The contract cannot be upgraded, so the deployer holds no privilege over it afterwards. It replaces CCNU3CYA…AYPB (salt sha256("x402-upto-stellar/v1")), which runs the same source built with the SEP-58 recipe of the test contracts (no --optimize, no source_repo metadata); that contract stays on chain unchanged, but @rail402.dev/upto no longer names it. @rail402.dev/upto exports the deployment as UPTO_TESTNET_DEPLOYMENT, and a Rail402 service uses it on testnet unless TESTNET_UPTO_CONTRACT names another contract.

Interface

settle checks the cap and the ledger window, requires the payer’s authorization of settle(terms) and the settlement authority’s authorization of settle(payer, terms, amount), then, for a positive amount, calls the signed token.approve(payer, contract, max_amount, expiration_ledger) and pulls amount with transfer_from, which the token allows because the contract is the direct invoker. A zero amount makes no token call. It returns the amount settled and emits one upto_settled event (topics: "upto_settled", payer, recipient; data: token, amount, max_amount, facilitator), also for zero. Authorization failures are host errors: Error(Auth, InvalidAction) when an authorization is missing or does not match (for example, a submitter other than the settlement authority), Error(Auth, ExistingValue) when the payer’s entry was already used. Any error rolls back the whole settlement, the payer’s nonce included.

Rebuild and verify

The crate directory is the complete build input: Cargo.toml, Cargo.lock, rust-toolchain.toml, .gitignore and src/. The contract is built the way stellar.expert’s release workflow (stellar-expert/soroban-build-workflow) builds it:
with stellar 28.1.0 and Rust 1.98.0 (pinned by rust-toolchain.toml). The source_repo entry in the WASM’s contract metadata is the SEP-55 link from the deployed code to this repository. scripts/contracts/build.sh runs that build with the official stellar-cli 28.1.0 release binary (its archive’s SHA-256 is checked) on the Rust toolchain of a digest-pinned image. The result does not depend on the host path, user or toolchain; Docker, git, jq and curl are all that is needed. Verify the testnet deployment from a checkout of the repository:
It rebuilds the recorded revision from a clean export, compares the crate’s source archive and the WASM with the recorded hashes, downloads the WASM the deployed instance runs (stellar contract fetch) and compares it too, and prints the embedded metadata (rsver, rssdkver, cliver, source_repo). Other forms:

Source verification on stellar.expert

stellar.expert links a contract to its source only for WASM that its own release workflow built. A tag upto-contract-v<crate version> runs .github/workflows/contract-release.yml, which first rebuilds the contract with build.sh and requires it to be the WASM the testnet deployment runs, then calls stellar.expert’s workflow for contracts/stellar/upto. That workflow builds the contract with the command above, publishes the WASM as a GitHub release, attests it with GitHub build provenance and asks stellar.expert to validate it. The last job requires the released WASM to be the deployed one and its attestation to come from stellar.expert’s workflow (gh attestation verify <wasm> --repo tolgayayci/rail402 --signer-repo stellar-expert/soroban-build-workflow). stellar.expert shows the repository and commit on the contract’s page once it has processed that request; until a tagged release has run, the contract page shows the source as unavailable. The committed WASM is packages/testkit/fixtures/x402_upto_stellar.wasm, with its provenance in x402_upto_stellar.provenance.json; CI rebuilds it on every change and fails if the hash differs. The contract tests run with cargo test --locked in contracts/stellar/upto. A Rail402 service checks the contract at startup too: if the live instance runs WASM other than UPTO_WASM_HASH (the canonical hash for the canonical contract), it refuses to start.

TTL: monitoring, extension and restoration

The contract stores nothing per payment: the payer’s nonce and the allowance are temporary entries that expire on their own. Only two entries pay rent, the contract’s instance and its code, and the contract never extends them itself, so a settlement’s fee never depends on them. Once an entry’s TTL runs out it is archived, and until it is restored every upto payment that uses the contract is refused. Extending and restoring are permissionless: any funded account can pay for them, and neither changes the contract. Monitoring. A Rail402 service watches the contract under the alias upto: rail402_contract_ttl_ledgers{contract="upto"} for the instance and the code, /health warnings contract_ttl_low and contract_ttl_critical (CONTRACT_TTL_WARN_LEDGERS, CONTRACT_TTL_CRITICAL_LEDGERS), and contract_archived once an entry is archived. upto ttl status prints both entries’ TTL and the keeper’s plan. Extension. The service’s keeper (UPTO_TTL_KEEPER, on by default) extends both entries once either has fewer than UPTO_TTL_EXTEND_BELOW_LEDGERS ledgers left (518,400, about 30 days), to UPTO_TTL_EXTEND_TO_LEDGERS (the network maximum less one day). The sponsor pays, under UPTO_TTL_MAX_FEE_STROOPS, and one replica at a time does it, never while another replica provisions accounts from the sponsor. A round whose fee would leave the sponsor below SPONSOR_RESERVE_XLM is refused, so maintenance never stops settlements. Extending the testnet code entry by the network maximum cost about 32 XLM. By hand:
3110399 is the largest extension testnet accepts (max_entry_ttl − 1). Restoration. A payment never restores the contract: since protocol 23 a freshly simulated invocation of an archived contract would restore it inside the transaction and bill the rent to the fee payer, so while it is archived Rail402 refuses upto payments with invalid_upto_stellar_settlement_contract_archived and submits nothing. exact payments and /ready are not affected. Where UPTO_TTL_AUTO_RESTORE is on (testnet by default) the keeper restores and then extends the contract; elsewhere /health reports upto_contract_restore_required and an operator runs:
A restored entry comes back with the network’s minimum TTL, so restoration is always followed by an extension. Afterwards upto ttl status shows no archived entry and the next /health shows no upto warning. The full runbook is in operations. The archive, refusal, restore and settle sequence is tested on a private network with short TTLs (apps/rail402/test/upto-archival.integration.test.ts). A new instance lives only the network’s minimum TTL (about 7 days on testnet), so extend a contract of your own right after deploying it; scripts/contracts/deploy.sh does.