Soroban contracts for the Stellar x402 upto payment scheme, used by the
AgentSmith x402 facilitator and agentsmith-x402-sdk.
Warning
These contracts have not been audited. They move user funds. The ABI is experimental and may change in a new deployment. Review the code yourself before relying on it, and report vulnerabilities as described in SECURITY.md.
In x402 upto, a buyer authorizes a maximum charge, the service runs, and the facilitator then
settles the actual amount, which may be lower. upto-settlement-kernel enforces that on chain:
- the buyer signs the settlement terms once, including the maximum;
- the facilitator named in those terms signs the actual amount;
- the contract checks both and moves the actual amount of a SEP-41 token directly from the buyer to the recipient, in one atomic invocation.
The contract is stateless. It has no owner, admin, upgrade, pause, withdrawal, arbitrary-call or storage interface, and it never holds funds. A new version is a new deployment at a new contract ID.
Both networks run the same WASM, built from this repository at source commit dccd19c.
| Network | Contract ID | WASM SHA-256 |
|---|---|---|
stellar:pubnet |
CCV4RALFBEE2QLKPK6H5ZX24CYMKIFN3723J5J4TMDEFWAFLELGYODNI |
ba832b45c0ac604f3aabca40203099556235da24c45e76fdb3bb56dc239de916 |
stellar:testnet |
CBB7WCIQOBOJ4G7JUPVOA3RI553HBSYQ3GLBJ25X7W7S7MRJIGVCXRE3 |
ba832b45c0ac604f3aabca40203099556235da24c45e76fdb3bb56dc239de916 |
Deployment details, verification evidence and deprecated contract IDs are in
deployments/. Use only the contract IDs above.
Validates the terms, checks both authorizations, then:
- calls
approve(payer, kernel, max_amount, deadline_ledger)onasset; - calls
transfer_from(kernel, payer, pay_to, actual_amount)onasset.
If anything fails, the whole invocation reverts, including the approval. Nothing is returned;
the token emits its usual approve and transfer events. The kernel defines no events of its own
yet.
| Field | Type | Meaning |
|---|---|---|
payer |
Address |
The buyer whose tokens are spent. |
asset |
Address |
The SEP-41 token contract. |
pay_to |
Address |
The recipient. |
facilitator |
Address |
The only address that may choose the actual amount. |
max_amount |
i128 |
The most that can be settled, in the token's atomic units. Must be positive. |
valid_after_ledger |
u32 |
Settlement is refused before this ledger. |
deadline_ledger |
u32 |
Settlement is refused after this ledger; the approval also expires here. |
The transaction must carry two authorization entries:
payer: settle(terms) ← signs the terms, not the actual amount
└─ asset.approve(payer, kernel, max_amount, deadline_ledger)
facilitator: settle(terms, actual_amount) ← signs the exact amount
The payer's entry must include the nested approve. An allowance the payer granted earlier does
not replace it.
| Code | Name | When |
|---|---|---|
| 1 | InvalidMaximum |
max_amount is zero or negative. |
| 2 | InvalidActualAmount |
actual_amount is zero or negative. |
| 3 | ExceedsMaximum |
actual_amount is greater than max_amount. |
| 4 | Expired |
The current ledger is after deadline_ledger. |
| 5 | NotYetValid |
The current ledger is before valid_after_ledger. |
| 6 | InvalidValidityWindow |
valid_after_ledger is after deadline_ledger. |
A missing or wrong authorization fails with the host's authorization error.
max_amountcaps one authorization, not all authorizations with the same terms. Separately signed authorizations can each settle up to that maximum.settleoverwrites any allowance the buyer already granted the kernel, because SEP-41approvesets rather than adds.- Remaining allowance is not cleared after settlement; it expires at
deadline_ledger. - Zero-value settlements are rejected. A facilitator that closes a zero charge without an on-chain transaction must enforce single use itself.
| Path | Contents |
|---|---|
upto-settlement-kernel/ |
The kernel and its tests. |
e2e-simple-account/ |
A minimal contract account used only by integration tests. Never deploy it to hold funds. |
deployments/ |
One record per network: contract IDs, WASM hashes, source commits and verification. |
Requirements: the Rust toolchain pinned in rust-toolchain.toml (installed by rustup
automatically) and the Stellar CLI 23 or later.
cargo test --workspace
cargo fmt --all --check
cargo clippy --workspace --all-targets -- -D warnings
stellar contract build --package upto-settlement-kernelThe build writes target/wasm32v1-none/release/upto_settlement_kernel.wasm. The checked-in
Cargo.lock and toolchain pin keep builds reproducible.
Build at the source commit of the deployment, fetch the deployed WASM and compare the hashes:
git checkout dccd19c
stellar contract build --package upto-settlement-kernel
shasum -a 256 target/wasm32v1-none/release/upto_settlement_kernel.wasm
# pubnet: the CLI has no default mainnet RPC, so pass one
stellar contract fetch \
--rpc-url "$MAINNET_RPC_URL" \
--network-passphrase "Public Global Stellar Network ; September 2015" \
--id CCV4RALFBEE2QLKPK6H5ZX24CYMKIFN3723J5J4TMDEFWAFLELGYODNI --out-file deployed.wasm
shasum -a 256 deployed.wasm
# testnet
stellar contract fetch --network testnet \
--id CBB7WCIQOBOJ4G7JUPVOA3RI553HBSYQ3GLBJ25X7W7S7MRJIGVCXRE3 --out-file deployed-testnet.wasm
shasum -a 256 deployed-testnet.wasmMAINNET_RPC_URL is any Stellar mainnet RPC endpoint; see the
RPC providers list. Every command
should print ba832b45c0ac604f3aabca40203099556235da24c45e76fdb3bb56dc239de916.
Not audited. See SECURITY.md to report a vulnerability.
Licensed under the Apache License, Version 2.0. Copyright 2026 57Blocks; see NOTICE.