Skip to main content
A Rail402 facilitator settles exact payments in the tokens its operator accepts. By default that is Circle USDC on each network. Any other SEP-41 token can be added: a classic asset through its Stellar Asset Contract, or a custom token contract, with any number of decimals the configuration accepts (0 to 38). The tests below cover 6, 7 and 18 decimals.

Accepting another SEP-41 token

List every accepted token in ASSETS for the network, as CONTRACT:SYMBOL:DECIMALS[:MIN:MAX], comma-separated. Listing assets replaces the default, so keep USDC in the list if you still accept it:
  • CONTRACT is the token’s C… address.
  • SYMBOL labels the token in metrics, /health and search filters (asset=TEST6).
  • DECIMALS must equal the token’s own decimals(). It never changes an amount: x402 amounts are always base units, and the facilitator compares them exactly. It turns a search’s maxPrice in whole units into base units.
  • MIN and MAX bound the accepted amount in base units. Amounts up to the i128 maximum are accepted, so an 18-decimal token’s amounts beyond the 64-bit range settle like any other.

What the service checks at startup

Before it provisions channels, the service reads every configured token from the network: whether its contract instance exists, whether it is a Stellar Asset Contract or another contract, and its decimals(). Until the check passes, the background poll (every 15 seconds) reads the tokens again, and /ready passes once they match. A mismatch found then keeps /ready failing and is logged as an error; fix the configuration and restart. Every accepted token’s contract TTL is watched too (see Operations).

Classic assets and custom tokens

Both kinds settle the same way: one transfer(from, to, amount) signed by the payer, submitted by the facilitator, which only pays the fee. They differ in what a payment needs and in how a failure is named. The rules are listed with the other failure causes in Errors and verification rules.

Fees and the fee ceiling

A custom token runs its own WASM and keeps its own storage, so a payment in it can cost far more network fee than one in a Stellar Asset Contract, mostly as rent for the token’s storage entries. The test token, built on OpenZeppelin’s fungible token, extends a holder’s balance entry to 30 days whenever it reads one with less than 29 days left, and the payment whose transfer reads it pays that rent. Measured on stellar:testnet in a rehearsal of the canonical run (--asset usdc,test6 --payer g,c) against a Rail402 facilitator; the recorded canonical run lists the fee charged for each of its transactions, which varies with network conditions: The fee a simulation asks for is higher than the fee finally charged, because it includes refundable rent: 361,384 stroops for a smart-account payer’s first TEST6 payment to a new holder, and 539,505 for one to a payee that already held TEST6. The facilitator compares the fee the simulation asks for with MAX_TX_FEE_STROOPS and refuses a payment above it with invalid_exact_stellar_payload_fee_exceeds_maximum, before anything is submitted. The default is 1,000,000 stroops on testnet, which covers every TEST6 payment above, and 300,000 on pubnet. When you accept a custom token on pubnet, set the ceiling for that token’s costliest payments. upto settlements have their own ceiling, UPTO_MAX_TX_FEE_STROOPS (default 5,000,000 on testnet, 500,000 on pubnet): a smart-account payer’s upto payment in TEST6 to a payee that already held TEST6 was refused under a 500,000 ceiling and settled with 1,000,000. See fee ceilings for the measured costs behind the defaults. On a local network with default settings the same test-token payments cost about 160,000 stroops.

Pricing in a token

A stock x402 seller prices a resource in a custom token with an asset amount in base units, for example 0.25 TEST6 as { amount: "250000", asset: "CDS45IRSWVDBV4JM2FPZ52EYXYO54J6DJL35GOSXOTRA4BYM6FFRK2BK" }. The exact() helper of @rail402.dev/sdk/seller converts a price in whole units with the token’s decimals, which assetDecimals() reads from the network:
The same 0.25 is 2500000 base units of a 7-decimal token and 250000000000000000 of an 18-decimal one.

The TEST6 test token

TEST6 is a SEP-41 test token with 6 decimals on stellar:testnet, at CDS45IRSWVDBV4JM2FPZ52EYXYO54J6DJL35GOSXOTRA4BYM6FFRK2BK. It has no value, no admin and an open faucet: anyone can mint 100 TEST6 to any address.
Its source is contracts/testing/test-token in the repository, and its deployment record, with the transactions and the WASM hash, is contracts/testing/deployments/test6-testnet.json.

How it is tested

tools/conformance/test/assets.integration.test.ts runs the service as a process on a local network with three accepted tokens: a classic asset’s Stellar Asset Contract (7 decimals), the test token deployed with 6 decimals (TEST6) and with 18 (TEST18). For each token, a stock @x402/express seller and a stock @x402/fetch buyer complete payments from a G… payer and from a C… smart-account payer, and to a C… payTo; TEST18’s price of 12.5 is 12,500,000,000,000,000,000 base units, beyond the 64-bit range. Each payment is read back from the chain:
  • exactly one transfer event, from the payer to payTo, for the price, from the token’s contract;
  • the payer’s balance falls and payTo’s rises by the price, and no facilitator account holds or moves the token;
  • the fee is paid by the sponsor, through a fee bump around a channel account, and the sponsor’s XLM falls by exactly the fee charged.
The same three tokens are paid with upto: a stock seller with @rail402.dev/upto’s server scheme prices the resource at the cap and charges 40% of it through the stock settlement override, the buyer pays with the scheme’s own client, and only the charged amount moves, from payer to payTo, while the Bazaar lists the resource at its cap. The same suite checks the Bazaar listing’s token facts (symbol, decimals, receivable), search by asset=<symbol> and a maxPrice in whole units, insufficient_funds for a payer holding less than the amount and for one holding none, and the startup checks above.