Demo mode: a reference storefront for eSIM operators. Prices are live, payments are off. Run your own operator →
Travel eSIM · demo
English

Cases: an AI agent buys an eSIM, then gets its money back

Contents

Real runs of the kit, with their transactions, so you can check every step on chain. Case 2 (section 10) is the same agent buying our capability through the hub. Case 3 (section 11) has two buyers, a coding agent in a terminal and ARGUS, and the hub counting the seller's deliveries from the buyers' signed receipts. The first one is the whole agent path in both directions: an AI agent finds an offer, pays for it in USDC over x402, hands the eSIM to its owner, and later asks for a refund, which the seller node pays back without anyone touching a wallet.

1. What happened

On 15 October 2026 the owner of ARGUS, a personal AI agent, wrote to it in Telegram: buy me the cheapest eSIM for Turkey. ARGUS asked the kit's seller node for offers, picked Turkey 500 MB for 7 days at 0.31 USDC, asked its owner for approval, and paid from its own wallet. Seconds later the owner had the QR code and the activation code in the chat. ARGUS itself never saw them.

Thirty-four minutes later the owner asked ARGUS for a refund. The eSIM was not installed, so the seller node cancelled it at the wholesaler and sent the 0.31 USDC back to the agent's wallet. Every wallet and the wholesale balance ended where they started.

The seller was the kit's private test stand: an ordinary seller node, selling at cost, reachable only from the agent's host. Nothing in the run was simulated.

2. Who took part

PartyWhat it isAddress or place
Ownerthe person ARGUS works for; approves every payment in Telegrama private Telegram chat
ARGUSthe agent (buyer); pays from its own wallet0x428f2Be4327908948e3B1915C55ddF8fA6bf73cc
Seller nodethe kit's rails/ node (seller) with automatic refunds onhttps://esim.modelmarket.dev/stand
Seller walletreceives payments and pays refunds (PAY_TO and REFUND_KEY_FILE are the same wallet)0xF47161fBB56D0D910a17e503C8292903F41BbAE2
FacilitatorCoinbase's x402 facilitator: verifies each signed transfer and puts it on chain, paying the gasCDP
WholesalereSIM Access: issues and cancels the profilethe operator's account

3. The whole chain

sequenceDiagram
    autonumber
    actor O as Owner (Telegram)
    participant A as ARGUS (agent)
    participant S as Seller node
    participant F as x402 facilitator
    participant W as eSIM Access
    O->>A: buy me the cheapest eSIM for Turkey
    A->>S: POST /quote {"country":"TR"}
    S-->>A: TR_0.5_7, 500 MB, 7 days, 0.31 USDC (signed)
    A->>O: approve esim_buy?
    O-->>A: /yes
    A->>S: POST /order {"offer_id":"TR_0.5_7"}
    S-->>A: 402 Payment Required: 0.31 USDC to the seller wallet
    A->>S: the same order with PAYMENT-SIGNATURE (EIP-3009, signed by ARGUS)
    S->>F: verify
    S->>W: order one profile (idempotent by the payment)
    W-->>S: profile 26101505200012
    S->>F: settle
    F-->>S: tx 0x617efd7a…3ae0
    S-->>A: delivery: activation code, QR, status token
    A->>O: QR and activation code, sent around the model
    Note over O,W: 34 minutes later
    O->>A: refund the last eSIM, I did not install it
    A->>O: approve esim_refund?
    O-->>A: /yes
    A->>S: POST /refund {esim_tran_no, status_token}
    S->>W: is it installed? any data used?
    W-->>S: GOT_RESOURCE, not installed, 0 bytes
    S->>W: cancel the profile
    S->>F: verify and settle 0.31 USDC, seller wallet to ARGUS
    F-->>S: tx 0x6dcfb7f0…ed4b
    S-->>A: refunded
    A->>O: refunded 0.31 USDC, tx 0x6dcfb7f0…ed4b

4. The transactions

All on Base mainnet, in USDC (0x8335…2913). Open any of them on basescan.org.

StepTransactionBlock, time (UTC)From → toAmountGas paid by
Funding the agent0x225afba5cd651e3ccdf70593fe985d1e24ddc14836270e83b13f546b6d01757852624834owner's wallet 0x097e…475a → ARGUS0.50the owner
Purchase0x617efd7abe4b4c6a80b54046617a73415aa54df7ada5f002ce7260cf68ac3ae052626137, 05:20:21ARGUS → seller wallet0.31facilitator 0x93f6…be44
Refund0x6dcfb7f075ad7fb6308064ac53e310dfb6adfd9b65d9f44b3c9a6dd39ca9ed4b52627173, 05:54:53seller wallet → ARGUS0.31facilitator 0x4c93…0205

Both the purchase and the refund are EIP-3009 transferWithAuthorization calls: the paying wallet signs, the facilitator submits. Neither ARGUS nor the seller wallet held any ETH, before, during or after.

5. Money and balances

flowchart LR
    A["ARGUS wallet"] -- "purchase: 0.31 USDC (x402)" --> S["Seller wallet"]
    S -- "refund: 0.31 USDC (EIP-3009 via the facilitator)" --> A
    N["Seller node"] -- "order 0.30 USD, then cancel" --> W["eSIM Access balance"]
    F["x402 facilitator"] -. "gas for both transfers" .-> B["Base"]
BeforeAfter the purchaseAfter the refund
ARGUS wallet0.50 USDC0.19 USDC0.50 USDC
Seller wallet00.31 USDC0
eSIM Access balance$20.00$19.70$20.00
ETH spent by ARGUS or the seller000

The stand sold at cost (MARGIN_PCT=0, MIN_MARGIN_USD=0.01): $0.30 wholesale, 0.31 USDC to the agent. With the kit's default margin the same plan sells for $1.30.

6. The refund rules

stateDiagram-v2
    [*] --> delivered: paid and settled
    delivered --> in_use: installed or data used
    in_use --> [*]: 409 in_use, no refund
    delivered --> cancelled: POST /refund with the status token
    cancelled --> refunded: refund wallet pays the price back
    cancelled --> due: no refund wallet, or the payout failed
    due --> [*]: the operator pays it by hand
    refunded --> [*]

7. Turning refunds on for your node

Refunds are off until you give the node a wallet to pay them from. Without it, POST /refund still cancels the unused eSIM and records the refund as due, and you get the agent_order.refund_due webhook to pay it yourself.

# a wallet that holds USDC on Base for refunds: PAY_TO's own wallet, so refunds return the money
# that came in, or a separate float. It needs no ETH: the facilitator pays the gas.
cd ~/esim-operator-kit
umask 077
printf 'Refund wallet private key: '; read -rs v; printf %s "$v" > secrets/refund_key; echo; unset v
sudo chown 1000:1000 secrets/refund_key && sudo chmod 600 secrets/refund_key
echo "REFUND_KEY_FILE=/run/secrets/esim/refund_key" >> .env
echo "REFUND_MAX_USD=20" >> .env
echo "REFUND_DAILY_MAX_USD=100" >> .env
docker compose up -d rails
curl -s https://<your domain>/agents/healthz    # "refunds": "automatic"
SettingWhat it does
REFUND_KEY_FILEThe key of the wallet refunds come from. The only private key the kit ever reads, and only when you opt in.
REFUND_MAX_USDThe most one refund pays.
REFUND_DAILY_MAX_USDThe most all refunds pay in a day.
CDP_KEY_FILEAlready set for payments; refunds go through the same facilitator.

Keep only a float on the refund wallet if it is not your main wallet: a key on a server is a key a break-in can read.

8. Asking for a refund

An agent needs the two values it got with the delivery:

curl -s -X POST https://<seller>/agents/refund -H 'content-type: application/json' \
  -d '{"esim_tran_no":"26101505200012","status_token":"…"}'
{"success": true, "result": {"esim_tran_no": "26101505200012", "status": "refunded", "amount_usd": "0.31",
 "to": "0x428f2be4327908948e3b1915c55ddf8fa6bf73cc", "at": "2026-10-15T05:54:50.399Z",
 "transaction": "0x6dcfb7f075ad7fb6308064ac53e310dfb6adfd9b65d9f44b3c9a6dd39ca9ed4b"}}

status is refunded with the transaction, or due when the seller pays by hand. /listing.json says which: its refund.policy names the rule, and /healthz shows "refunds": "automatic" or "operator".

9. What we learned

10. Case 2: through the hub, from our public node

In case 1 the agent was given the seller's address. In case 2 it found the seller itself: ARGUS searched the modelmarket.dev hub for eSIM sellers, got our capability esim.quote@v1 of esim-modelmarket, asked it for offers through the hub, and paid the seller's own /order over x402. The hub routed the free quote and never touched the money.

For this test our public node was switched from demo to selling, and switched back to demo right after. The node behind esim-modelmarket (modelmarket.dev/providers/esim) sold for 52 minutes, from 06:56:55 to 07:48:33 UTC on 15 October 2026, at cost (MARGIN_PCT=0, MIN_MARGIN_USD=0.01), paying into the same seller wallet as the stand and refunding from it automatically (at most $2 per refund, $5 a day). It has refused every order with demo_mode again since 07:48:33.

sequenceDiagram
    autonumber
    actor O as Owner (Telegram)
    participant A as ARGUS (agent)
    participant H as modelmarket.dev hub
    participant S as Seller node (modelmarket.dev/providers/esim)
    participant F as x402 facilitator
    O->>A: buy me the cheapest eSIM for Turkey
    A->>H: GET /ai-market/v2/search?intent=esim
    H-->>A: esim.quote@v1 of esim-modelmarket (via esim.modelmarket.dev)
    A->>H: POST /ai-market/v2/invoke esim.quote@v1 (free)
    H->>S: relays the quote
    S-->>A: seller demo: false · TR_0.5_7 0.31 USDC · order at …/providers/esim/order
    A->>O: [SELLS] TR_0.5_7 0.31 USDC · approve esim_buy?
    O-->>A: /yes
    A->>S: x402 straight to the seller: 402, then PAYMENT-SIGNATURE (EIP-3009)
    S->>F: verify, then settle
    F-->>S: tx 0xeea12962…881e
    S-->>A: delivery
    A->>O: QR and activation code, around the model
    O->>A: refund the last eSIM
    A->>O: approve esim_refund?
    O-->>A: /yes
    A->>S: POST /refund {esim_tran_no, status_token}
    S->>F: cancel at eSIM Access, then 0.31 USDC back
    F-->>S: tx 0x45dc5ce6…ec88
    S-->>A: refunded
Time (UTC)What happened
06:56:55The public node switches from demo to selling. Through the hub, its quote now says demo: false and 0.31 USDC
07:21First attempt: ARGUS sees the offers but refuses to buy. The node was still called "modelmarket.dev demo seller", and the agent judged by the name
07:23Fix deployed: the node's name follows its mode, and ARGUS labels every offer [SELLS] or [DEMO] from the seller's own answer
07:47:35ARGUS buys TR_0.5_7 after the owner's /yes; eSIM 26101507470034 is delivered to the owner's Telegram
07:48:02The owner asks for the refund, approves with /yes; the node cancels the unused eSIM and pays back
07:48:33The public node is switched back to demo
StepTransactionBlock, time (UTC)From → toUSDCGas paid by
Purchase0xeea12962c4f494e55acc59a376203bd4b704804f19a84b4b8f47a96b265a881e52630556, 07:47:39ARGUS → seller wallet 0xF471…bAE20.31facilitator 0x68a9…7863
Refund0x45dc5ce6579d8e889750442d63773586c481a11b30ddf44805960860f3bbec8852630569, 07:48:05seller wallet → ARGUS0.31facilitator 0x625d…ac39

ARGUS was the only buyer in the window: the node's records hold one order, refunded. At the end ARGUS held 0.50 USDC again, the seller wallet 0, the eSIM Access balance $20.00, the eSIM was in state CANCEL, and no wallet had spent ETH.

What this case showed:

11. Case 3: two buyers, and the hub counts deliveries

A seller paid directly is invisible to the hub: the money goes from the agent to the seller, and the hub sees only the free quote. So, from the hub, a seller that takes the money and delivers nothing looked the same as one that delivers. Since 15 October 2026 the seller node signs a receipt for every delivery and every refund, and the buyer can bring it to the hub with its transaction. The hub checks the signature and the transfer on chain, and counts a confirmed delivery on the seller's listing. Case 3 tested this end to end with two different buyers.

For this test our public node was switched from demo to selling a third time, and switched back to demo right after. It sold for 9 minutes, from 09:38:59 to 09:48:22 UTC on 15 October 2026, on the same terms as in case 2: at cost, into the same seller wallet, with automatic refunds of at most $2 each and $5 a day. It has refused every order with demo_mode (403) again since 09:48:22.

BuyerWhat it isWallet
Claude Codea coding agent in the owner's terminal. It ran the kit's example buyer rails/examples/agent-buy.mjs; the script read the key from a key file, and the agent never saw it0x097e3F339D0b023605e12A6B81E2d6Cb7571475a
ARGUSthe owner's agent in Telegram, as in cases 1 and 2. The owner approved the purchase and the refund with /yes0x428f2Be4327908948e3B1915C55ddF8fA6bf73cc

11.1 The purchase from Claude Code

The whole purchase is one command. Without --yes it only quotes; with --refund it asks for the money back right after the delivery.

cd rails
node examples/agent-buy.mjs TR --hub https://modelmarket.dev --key-file ~/.aicom-pov-buyer.json --yes --refund
seller   modelmarket.dev eSIM seller (https://modelmarket.dev → https://esim.modelmarket.dev)

offer    TR_0.5_7 — Turkey 500MB 7Days — 0.31 USDC, from modelmarket.dev eSIM seller
order at https://modelmarket.dev/providers/esim/order
payer    0x097e3F339D0b023605e12A6B81E2d6Cb7571475a

delivered  esim_tran_no 26101509410001 · iccid 8997…7746 · apn internet
activation LPA:1$rsp-eu.s… (the traveller's secret)
paid       0x85d355853807967e85b1efc3f15261e0c58006091898cb98d46095db07bc103e
hub        delivery counted: {"confirmed":1,"distinct_buyers":1,"refunded":0}

refund     refunded · 0.31 USDC to 0x097e3f339d0b023605e12a6b81e2d6cb7571475a · 0x3313e8be7726ce1e3bc4dcf95afde0bd5eb3de0c8a57b601f8b3aaf86ed85c00
hub        refund counted: {"confirmed":1,"distinct_buyers":1,"refunded":1}

The script found the seller through the hub, skipped sellers in demo mode, took the cheapest offer, paid over x402 with a cap of $1 per payment, and brought both receipts to the hub. Three minutes later ARGUS bought the same plan in Telegram, and the counter reached two buyers.

11.2 The chain with receipts

sequenceDiagram
    autonumber
    participant B as Buyer (agent-buy.mjs or ARGUS)
    participant H as modelmarket.dev hub
    participant S as Seller node
    participant C as Base
    B->>H: search intent=esim, then esim.quote@v1 (free)
    H->>S: relays the quote
    S-->>B: [SELLS] TR_0.5_7 · 0.31 USDC · order URL
    B->>S: x402 order: EIP-3009 authorization with nonce N1
    S-->>B: delivery + delivery receipt {payer, pay_to, nonce N1}, signed with the provider key
    B->>H: POST /ai-market/v2/reputation/deliveries {receipt, signature, transaction}
    H->>H: the signature against the provider_pubkey the seller's listing published
    H->>C: a transfer under nonce N1, from the payer to pay_to, confirmed?
    C-->>H: yes
    H-->>B: counted: deliveries_30d
    B->>S: POST /refund {esim_tran_no, status_token}
    S-->>B: refunded + refund receipt {refund_from, refund_nonce N2}
    B->>H: the refund receipt and its transaction
    H->>C: a transfer under nonce N2, from the seller wallet to the payer?
    H-->>B: counted: refunded +1
Time (UTC)What happenedCounter on the listing afterwards
09:38:59The public node switches from demo to sellingnone yet
09:41:05Claude Code pays for TR_0.5_7; eSIM 26101509410001 is delivered1 delivery, 1 buyer, 0 refunds
09:41:19Its refund is paid back; the eSIM is cancelled1, 1, 1
09:44:19ARGUS pays after the owner's /yes; eSIM 26101509440019 goes to the owner's Telegram2, 2, 1
09:44:53ARGUS's refund is paid back after /yes2, 2, 2
09:48:22The public node is switched back to demo2, 2, 2 (kept for 30 days)

11.3 The transactions

StepTransactionBlock, time (UTC)From → toUSDCGas paid by
Purchase, Claude Code0x85d355853807967e85b1efc3f15261e0c58006091898cb98d46095db07bc103e52633959, 09:41:050x097e…475a → seller wallet 0xF471…bAE20.31facilitator 0xca5e…c70d
Refund, Claude Code0x3313e8be7726ce1e3bc4dcf95afde0bd5eb3de0c8a57b601f8b3aaf86ed85c0052633966, 09:41:19seller wallet → 0x097e…475a0.31facilitator 0x93f6…be44
Purchase, ARGUS0xf9ac406f3e329ac2c4c02e26562b6fb944e4165b71b88c4a0756a193325f966952634056, 09:44:19ARGUS → seller wallet0.31facilitator 0x93f6…be44
Refund, ARGUS0x5fa5da5455226a20d10dc84ffb8adf56b01d972dcc72f8e135b4a507a8c3528752634073, 09:44:53seller wallet → ARGUS0.31facilitator 0x8f5c…e531

The node's records hold exactly these two orders in the window, both refunded. At the end both buyers held what they had before (0.594148 and 0.50 USDC), the seller wallet held 0, the eSIM Access balance was $20.00, both eSIMs were in state CANCEL with 0 bytes used, and no wallet had spent ETH.

11.4 The receipt, and what the hub checks

The delivery receipt of ARGUS's purchase, as the node signs it. It names the payment, not the eSIM: there is no activation code, no ICCID and no status token in it.

{"v": 1, "kind": "delivery", "capability": "esim.order@v1", "product_id": "esim-modelmarket", "chain_id": 8453,
 "payer": "0x428f2be4327908948e3b1915c55ddf8fa6bf73cc", "pay_to": "0xF47161fBB56D0D910a17e503C8292903F41BbAE2",
 "nonce": "0xddde4ccd3b8d8ba361b94985b0b063f50ae41f43217cfc3ccadc2d636201f79e", "offer_id": "TR_0.5_7",
 "at": "2026-10-15T09:44:…Z"}

The signature is Ed25519 over the canonical JSON of the receipt (sorted keys, no spaces), with the same provider key that signs every free answer. A refund receipt has kind: "refund", refund_from (the wallet that paid back), refund_nonce (that transfer's EIP-3009 nonce) and delivery_nonce.

flowchart TD
    R["receipt + signature + transaction"] --> K{"signed by a provider_pubkey<br/>a listing on this hub published?"}
    K -- no --> E404["404: no such seller"]
    K -- yes --> T{"on Base: a USDC transfer under the receipt's nonce,<br/>from the payer to pay_to<br/>(refund: from refund_from to the payer)?"}
    T -- "not yet confirmed" --> E422["422: try again in a few seconds"]
    T -- no --> E422b["422: the transaction does not back this receipt"]
    T -- yes --> D{"this transaction already counted?"}
    D -- yes --> SAME["200, recorded: false"]
    D -- no --> OK["200, recorded: true: deliveries_30d +1"]

11.5 Where things stand

What this case showed:

The kit on GitHub →