examples/merchant. It sells one demo order for 0.01 USDC on Algorand behind the stock @x402/hono payment middleware, and it ships two releases at the same URL. Both are healthy and both settle payments. Only one keeps its promise to paying clients, and only a paid request against the release contract tells them apart.
The two releases
broken-v1 models a serializer change that renamed one field. The endpoint is up, the payment settles and the status is 200, yet every client that reads receiptId breaks.
402.
The release contract in examples/merchant/contract.json requires orderId, receiptId and fulfilled: true:
contract.json
examples/merchant/order.json: { "sku": "demo-widget", "quantity": 1 }. The merchant accepts a sku of 1 to 40 lowercase letters, digits or dashes, and a quantity from 1 to 10.
Prerequisites
- A checkout of the Selo repository, Node 24 and pnpm 10, with
pnpm install --frozen-lockfile. - For a real release test: a server with a public HTTPS domain, and a payee address on Algorand Mainnet opted in to USDC (ASA
31566704). The merchant only receives payments, so it needs the address, never a key. - For the paid runs: a payer wallet for
selo testholding 1.00 USDC per run. See Run a release test.
Run it locally
Locally, the merchant is useful to see its 402 and its behaviour. Selo cannot test it there, because Selo only calls public HTTPS endpoints.1
Start the merchant
Terminal
Output
2
Check its health
Terminal
Output
3
Ask for an order without paying
Terminal
402 Payment Required with an empty JSON body and the challenge in the PAYMENT-REQUIRED header. Decoded, its requirement on Testnet is:accepts[0]
10000 atomic units is 0.01 USDC. The challenge also declares a Bazaar extensions.bazaar block with the order input, an example receipt and the receipt schema. On algorand-mainnet, the network is algorand:wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8= and the asset is 31566704.4
Run its tests
The merchant’s own tests run both releases against a fake facilitator, with no wallet and no network:They check that both releases pass an ordinary health check, answer an unpaid request with the same challenge, and settle; that
Terminal
broken-v1’s paid body fails the contract and fixed-v2’s matches it; that one payment yields one receipt; and that contract.json equals the receipt schema the merchant advertises.Configuration
The environment is validated at startup, and the merchant refuses to start with a missing or invalid value.
Deploy it on your own domain
1
Prepare a payee address
Use an Algorand Mainnet address you control, opted in to USDC (ASA
31566704). Each paid release test pays it 0.01 USDC from Selo’s wallet.2
Start the broken release on Mainnet
On your server, from the checkout:Keep it running under your process manager, and put it behind a reverse proxy that terminates TLS for your domain, for example nginx with a Let’s Encrypt certificate, forwarding to
Terminal
127.0.0.1:8791. Set MERCHANT_PUBLIC_ORIGIN to exactly the origin clients use.3
Check it from outside
Terminal
{"status":"ok","release":"broken-v1"} and 402.4
Describe it as yours
The 402 and the Bazaar listing carry the description in
merchantDescription in examples/merchant/src/app.ts, which names Selo’s own demo. Edit it so your listing describes your deployment.Reproduce FAIL, fix, PASS
Two paid runs cost 2.00 USDC from your payer wallet to Selo. Selo pays your merchant 0.01 USDC per run from its own wallet.1
Authorize the route
Terminal
selo-verification=… line to the file named by SELO_VERIFICATION_FILE. The merchant reads it per request, so no restart is needed. Then:Terminal
2
Preflight for free
Terminal
target fee 0.01 USDC, paid by Selo and you pay 1.00 USDC to Selo for the release test.3
Test the broken release
Terminal
response_contract FAIL SCHEMA_MISMATCH, then gate BLOCK (exit 1): FAIL: response_contract, and exit 1.4
Ship the fix
Restart the merchant with
MERCHANT_RELEASE=fixed-v2, then check:Terminal
Output
5
Test the fixed release
Run the same
selo test command with --release fixed-v2, --summary fixed-v2.json and --attestation fixed-v2-attestation.json. Expect every check to pass, then gate ALLOW (exit 0): PASS on every release check, and exit 0.6
Verify both verdicts offline
Terminal
1 with verdict FAIL, the second 0 with verdict PASS. Presenting the fixed-v2 attestation for broken-v1 exits 2. See Verify a signed verdict.The discovery check compares your live 402 with your route’s Bazaar listing. On a fresh deployment the route may not be listed yet, and
discovery_contract reports WARN NOT_CATALOGED: a warning that does not block a PASS. If Selo cannot read the catalog at all, the check is INCONCLUSIVE CATALOG_UNAVAILABLE, and so is the verdict, unless another check proved a FAIL. INCONCLUSIVE is never a PASS: retry later.The recorded Mainnet runs
The public instance athttps://demo.useselo.xyz/v1/orders is the one Selo tested on Algorand Mainnet on 26 October 2026, through the same CLI commands. Its health check answered 200 on both releases.
You cannot run a paid test against that instance yourself: an authorization needs a line served from its origin, which only its operator can publish. Deploy your own copy to reproduce the runs. See Proof on Algorand Mainnet for the full record.