Paychannels
06 · Agents

Deposit once.
Pay a million times.

One deposit on Polygon opens a channel that pays any merchant. Every request after that is one EIP-712 signature from a hot session key — no gas, no transaction, no waiting for a block. The hub verifies it before it answers and the response comes back paid.

paste into your agent
Read https://paychannels.mudit.blog/PAYING.md and use x402 to buy https://paychannels.mudit.blog/api/paid/time

That one line is the whole onboarding. The agent discovers the chain, funds itself, deposits, signs and buys. The step-by-step shell recipe is PAYING.md.

01

Two keys. The hot one can't lose your money.

The root key holds funds and signs the deposit — then goes cold. The session key signs every voucher and lives wherever your agent runs. The deposit binds signer[payer] = sessionKey on-chain, so a leaked session key can at worst spend the deposit you already made on the merchants you're already paying. It can never withdraw.

bash · cast · PAYING.md §2
mkdir -p .wallets && chmod 700 .wallets
W="$(cast wallet new)"
PAYER=$(grep -oE '0x[0-9a-fA-F]{40}' <<<"$W" | head -1)
grep -oE '0x[0-9a-fA-F]{64}' <<<"$W" | head -1 > .wallets/payer.key && chmod 600 .wallets/payer.key
S="$(cast wallet new)"
SESSION_ADDR=$(grep -oE '0x[0-9a-fA-F]{40}' <<<"$S" | head -1)
grep -oE '0x[0-9a-fA-F]{64}' <<<"$S" | head -1 > .wallets/session.key && chmod 600 .wallets/session.key
unset W S

On the devnet the dispenser hands you gas and spend in one call. Fund once for the entire lifetime of the channel.

bash · PAYING.md §3
curl -s -X POST "$DISPENSER_URL/fund" -H 'content-type: application/json' \
     -d "{\"address\":\"$PAYER\"}"
# 200 -> {"address":"0x...","pol_wei":"100000000000000000","usdc":"1000000000","pol_tx":"0x...","usdc_tx":"0x..."}
02

The last transaction you'll send.

Approve the hub, deposit, bind the session key — two transactions, once. From here every payment is free and every merchant on the hub is reachable from this one balance.

bash · cast · PAYING.md §5
DEPOSIT=500000000    # 500 USDC of your 1000 - leaves the rest for exact + headroom
cast send "$USDC" 'approve(address,uint256)' "$HUB" "$DEPOSIT" \
     --private-key "$(cat .wallets/payer.key)" --rpc-url "$RPC_URL"
cast send "$HUB" 'deposit(address,address,uint256)' "$PAYER" "$SESSION_ADDR" "$DEPOSIT" \
     --private-key "$(cat .wallets/payer.key)" --rpc-url "$RPC_URL"

Zero on-chain transactions per payment. Forever.

03

Pay by signature.

A voucher is a cumulative ceiling: cumulative = what you've spent + this price, signed by the session key. The merchant forwards it, the hub checks the signature and the headroom, serves the request, and you persist the new counter. No round trip to a chain, no confirmation to wait for.

bash · cast · PAYING.md §6
cat > /tmp/voucher.json <<EOF
{"types":{"EIP712Domain":[{"name":"name","type":"string"},{"name":"version","type":"string"},{"name":"chainId","type":"uint256"},{"name":"verifyingContract","type":"address"}],
"Voucher":[{"name":"payer","type":"address"},{"name":"merchant","type":"address"},{"name":"epoch","type":"uint64"},{"name":"cumulative","type":"uint256"}]},
"primaryType":"Voucher",
"domain":{"name":"ThunderpoltHub","version":"1","chainId":$CHAIN_ID,"verifyingContract":"$HUB"},
"message":{"payer":"$PAYER","merchant":"$MERCHANT","epoch":"$EPOCH","cumulative":"$NEW"}}
EOF
VSIG=$(cast wallet sign --private-key "$(cat .wallets/session.key)" --data --from-file /tmp/voucher.json)
bash · PAYING.md §6
XP=$(jq -nc --arg net "eip155:$CHAIN_ID" --arg p "$PAYER" --arg m "$MERCHANT" --arg e "$EPOCH" \
     --arg c "$NEW" --arg sig "$VSIG" --arg hub "$HUB" --arg id "$PAYID" \
 '{x402Version:2,scheme:"batch-settlement",network:$net,
   payload:{binding:"thunderpolt-hub/v1",voucher:{payer:$p,merchant:$m,epoch:$e,cumulative:$c},signature:$sig,hub:$hub},
   extensions:{"payment-identifier":{id:$id}}}' | base64 | tr -d '\n')
CODE=$(curl -s -D /tmp/ch.hdr -o /tmp/ch.json -w '%{http_code}' "$SITE_URL/api/paid/time" -H "X-PAYMENT: $XP")
[ "$CODE" = 200 ] && echo "$NEW" > .wallets/cum.txt

One signature, sixteen payments in flight

Sign a ceiling K prices high and fire K requests with distinct payment-ids under it — one signature for the whole burst, arrival order irrelevant. This is how one payer keeps many payments in flight at once.

bash · PAYING.md §6
K=16
CEIL=$((OLD + K * PRICE))
# ...sign ONE voucher at cumulative=$CEIL exactly as above (VSIG)...
for k in $(seq 1 "$K"); do
  PAYID="pay_$(openssl rand -hex 14)"
  # build XP from the SAME voucher+VSIG, this $PAYID, and curl in the
  # background — all K may be in flight at once, arrival order is free
done
wait; echo "$CEIL" > .wallets/cum.txt   # persist once the burst settles
04

Stress it.

The devnet is open and the dispenser is unlimited. Have your agent build its own load generator from the public protocol — no engine access, no operator — and climb by adding payers. The reference facilitator is unoptimised on purpose; the hubs behind it are not — 100 of them measured 45M fully-verified payments/s (benchmark).

pseudocode · STRESS-TESTING.md §2.2
domain    = { name: "ThunderpoltHub", version: "2", chainId, verifyingContract: HUB }
Voucher   = { payer: address, merchant: address, cumulative: uint256 }

# one window per payer: ONE signature, K requests in flight
C = known_cumulative + K × price
sign Voucher{payer, merchant, cumulative: C} with the session key
fire K requests carrying that voucher + signature, each with a distinct payment-id
on resolve: known_cumulative = C        # unauthorized-cumulative → one /quote, resync

# scale by adding payers (32 × K 16–32 is a good high-rate shape), not by raising K
paste into your agent
Read https://paychannels.mudit.blog/STRESS-TESTING.md, build your own load generator from PAYING.md's wire protocol, and run a 60-second load test against https://paychannels.mudit.blog. Report the sustained payments/s and the latency percentiles, with the run shape.
or make it spend
Read https://paychannels.mudit.blog/PAYING.md, open a channel on the Paychannels devnet, and pay for https://paychannels.mudit.blog/api/paid/time 100 times as fast as you can. Show me the receipts and the total on-chain transactions you sent.

The devnet is open and needs no run registration: point your generator at the public URL and read the receipts from the response headers.