DEV Community

bykamo
bykamo

Posted on • Originally published at bykamodev.Medium

One $0.001 Price, Two Settlement Models: x402 on Base vs MPP Sessions on Tempo

I charged the same $0.001 on two payment rails and measured every call.

On x402 + Base, each settled call incurred $0.00144 in on-chain gas, more than the payment itself, paid by a facilitator relayer I never see. On Tempo with an MPP session, I deposited once, made 100 calls the chain never recorded individually, and the open-plus-close gas was too small to show up in my balance at all. Same price on the invoice, a different machine underneath.

One thing up front, because it changes how you should read this. I tested one x402 configuration (the exact scheme on Base, settled through Coinbase's CDP facilitator) and one MPP session service on Tempo (the official RPC). This is not a universal benchmark of either ecosystem. It's a measurement of two production payment paths an agent can use today, and most of what I can compare cleanly is the buyer's side: cost and latency per call.

TL;DR

  • The honest framing isn't "x402 vs Tempo." It's x402 (a standard) vs MPP (a protocol) on Tempo (a chain). x402 standardizes the HTTP 402 handshake. MPP, the Machine Payments Protocol co-authored by Stripe and Tempo, optimizes the settlement rail underneath. They sit at different layers, and MPP can wrap an x402-style handshake.
  • In the x402 exact scheme I tested, every successful call settled on-chain: 30 calls, 30 transactions, ~$0.00144 gas each (measured), paid by a facilitator relayer. At a $0.001 price, the gas is bigger than the sale, which is why hosted facilitators carry a per-call floor.
  • The MPP session I tested deposits once and spends against an off-chain voucher. Across 100 calls the chain saw two transactions, open and close, and the gas for that pair was below my balance's resolution ($0.000001). For a high-frequency agent loop, that's the difference that matters.
  • If you're a non-US builder (I'm in Kyoto), Tempo's headline merchant edge, Stripe USD settlement, is US-only today. That one constraint can flip the recommendation back to x402.

A note on fairness. On the x402 side I built the seller endpoint, a Cloudflare Worker. On the Tempo side I paid an existing seller, the official Tempo RPC service, through the tempo CLI. So per-call buyer cost and latency are a clean comparison. How hard the seller is to build is not, because I never built the Tempo seller.

Architecture

The hinge: x402 puts the payment in the request; an MPP session puts the request inside a deposit. In the exact scheme, an x402 call is one on-chain settlement: my 30 calls produced 30 transactions. An MPP session is two transactions, open and close, regardless of how many calls ride between them; I sent 100 and the chain still recorded two.

How it works

Rail A — x402 (the seller, extracted from the production docolumn worker)

The seller answers 402 with payment requirements, then verifies and settles through Coinbase's CDP facilitator. The whole paid path is about 140 lines. The core is the verify-settle round-trip:

// bench/08-x402-vs-tempo/x402/worker.ts  (Cloudflare Worker)
import { createCdpAuthHeaders } from "@coinbase/x402";

// 1) no payment yet -> 402 with requirements (scheme "exact", USDC on Base, "1000" = $0.001)
if (!xPayment) return make402(env);

// 2) client retried with a signed EIP-3009 payload -> verify, then settle
const verifyRes = await fetch(`${env.FACILITATOR_URL}/verify`, { /* CDP auth + payload */ });
if (!verifyJson.isValid) return make402(env, verifyJson.invalidReason);

const settleRes = await fetch(`${env.FACILITATOR_URL}/settle`, {
  body: JSON.stringify({ /* ... */ waitUntil: "confirmed" }),   // <- waits for the Base tx
});
// returns X-Bench-Verify-Ms / X-Bench-Settle-Ms / X-Bench-Tx so the client can record the breakdown
Enter fullscreen mode Exit fullscreen mode

The client is just x402-fetch. wrapFetchWithPayment(fetch, wallet) catches the 402, signs transferWithAuthorization (EIP-3009), and retries. The wallet only signs. It needs USDC but no ETH, because the facilitator submits the transaction.

Rail B — Tempo / MPP (the buyer, via the official CLI)

I didn't hand-roll a Tempo seller. I paid a real one. The tempo CLI is a curl-compatible client that negotiates MPP automatically:

# bench/08-x402-vs-tempo/tempo/rpc-bench.mjs drives this call N times
tempo request -t -X POST \
  --json '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' \
  https://rpc.mpp.tempo.xyz/
Enter fullscreen mode Exit fullscreen mode

The service advertises intent: session at $0.001/call. The first call opens a session, depositing to the MPP escrow precompile (0x4d50500…0000, "MPP" in ASCII), and every call after rides an off-chain voucher against that deposit. The deposit is dynamic; my 100-call run locked about $1.90, and I got all of it back minus the $0.101 I actually spent. Tempo charges gas in the stablecoin itself; there's no native gas token (the account's native balance is a 0x4242… sentinel).

What it cost

Measured directly: x402 over 30 calls, Tempo over 100 ($0.001/call on both, Base mainnet ETH=$1,667). No extrapolation.

Axis x402 exact / Base / CDP (N=30) MPP session / Tempo RPC (N=100)
Seller can safely release the response settle p50 1,569ms (p95 2,349) voucher p50 554ms warm (p95 831)
verify (validity check only) p50 269ms (p95 594) folded into the voucher
HTTP response (wall) p50 1,863ms (p95 2,757) warm p50 554ms; cold/idle first call 3,163ms
On-chain transactions every call (30 calls = 30 tx) 2 total (open + close), for all 100
Per-call gas $0.00144 (gasUsed ~86,278, paid by a relayer) ~$0 (open+close gas below $0.000001 in my balance)
Who pays the gas a facilitator relayer (you hold no ETH) you, in stablecoin, on open/close
Attempts / failures 30 calls, 0 failed 100 calls completed; 1 cold-start attempt failed (1 of 101 paid attempts)

Three numbers, not one, because "fast" depends on what you mean by done.

  1. verify confirms the payment is valid. In x402 that's quick (p50 269ms), but the funds haven't moved yet.
  2. Seller is covered is the one that matters for billing. In x402 that's settle (p50 1,569ms): USDC has actually moved on-chain and the call is final. In the MPP session it's the voucher (p50 554ms warm): an accepted voucher backed by funds already in escrow, not a per-call on-chain settlement. These are different assurance mechanisms, not just different speeds.
  3. On-chain finality is per-call for x402 (same as settle) and happens once, at close, for the session.

Compared on the same definition (seller is covered), the Tempo session was about 3x faster per call: 554ms vs 1,569ms. The x402 latency is dominated by waitUntil: "confirmed", which makes each call wait for a Base block (settle p50 1,569ms, p95 2,349ms).

On cost, the gap is starker. x402 settles on-chain every call, so the relayer pays ~$0.00144 of gas each time, on top of moving a $0.001 payment. The MPP session paid that gas only twice, at open and close, and even then it was too small to register against my balance. I completed 100 calls, but the cold-start failed once and was retried, so 101 paid vouchers were charged in total: $0.101 in service fees. The unused remainder of the dynamic deposit was refunded on close.

One caveat I won't paper over: I billed myself 30 x402 calls, not 100, and the Tempo "~$0 gas" is a balance-delta measurement, not a per-tx receipt readout. It means the open/close gas is below $0.000001 in my wallet, not provably zero.

What broke

Recorded while wiring both rails. This is the part you can't get from docs.

  1. x402: every call 402'd, and it wasn't the balance or the keys. It was self_send_not_allowed. I'd wired the bench with the payer and the payTo set to the same wallet. The CDP facilitator rejects from == to before it checks balance, so every call failed with a generic 402. The fix was one line: print the 402 body so the invalidReason surfaces, then split the addresses. The deeper lesson is a real property of the rail. A hosted facilitator structurally blocks self-dealing, so you can't quietly settle your own $0.001 against yourself for free.

  2. Tempo: the first paid request died on an "untrusted escrow contract." The CLI rejected the session challenge because the escrow it was handed (0x4d50500…0000) wasn't in its trust list; it wanted 0x33b9010…24f25. That address is the real MPP escrow precompile, and the deposit genuinely lands there, so this wasn't a malicious server. It was a stale CLI extension. The retry auto-updated tempo-request to v0.5.2 and the same call went through. Mainnet-early tooling churn, caught live.

  3. Tempo: the cold first call of a session is slow and can fail. Across 101 paid attempts, exactly one failed: the cold first call, with KeyAlreadyExists in a keychain precompile during gas estimation. It took 3.2s before the retry succeeded, and the other 100 calls went through. The access key was already provisioned, but opening the session tried to register it again. Worth knowing if you script this: budget a retry for the first call, and expect an idle session's next call to run 2–3s before it warms back to ~554ms.

Which rail to build on

The measurements only matter if they change a decision. Here's the framework I'd use, and the short version is: it depends on how often your agent pays.

If your situation is… Lean Why
One-shot charges (a webhook, a report, an occasional inference) x402 per-call settlement is fine; the gas overhead matters less when calls are infrequent or higher-priced
Tens-to-hundreds of small calls in a loop (LLM inference, metered tools, streaming) MPP session the session amortizes the chain to ~$0/call and lands at ~554ms
Non-US business, can't use Stripe's US-only stablecoin settlement x402 Tempo's biggest merchant edge doesn't apply to you
You want multi-chain optionality (Base, Polygon, Arbitrum, World, Solana) x402 broader chain support today
You're building closer to an agent marketplace than a single endpoint MPP session payment QoS and pre-funded sessions fit that shape

The honest Kyoto-builder conclusion: for my setup, where I already run a production x402 worker, x402 was the shorter path to monetize this quarter, and the per-call gas overhead is acceptable at the prices I charge. To bet on the high-frequency agent loop, the MPP session model is genuinely a different machine, and the numbers back it up, but its loudest selling point (Stripe USD settlement) is US-only right now. I measured two production payment paths. Which one fits is a decision about your billing shape and your geography, not about which logo wins.

Appendix — reproduce this

Everything that produced the numbers above. Nothing here is paywalled — the worker, the client, the gas/latency scripts, and a results writeup are in a public gist: https://gist.github.com/bykamodev-web/e66ab462b7c727b60410dea9a5fe52c8

At a glance:

x402   30 calls · 30 Base txs · 0 failed · gas mean $0.00144 · settle p50/p95 1,569/2,349 ms
MPP    100 calls (101 paid attempts, 1 cold-start fail) · 2 Tempo txs · voucher p50/p95 554/831 ms · ~$0 gas
Enter fullscreen mode Exit fullscreen mode

Commands:

# x402 seller = Cloudflare Worker (exact scheme, USDC on Base), client = x402-fetch + viem
npx wrangler dev --port 8787
N=30 node --env-file=.env bench.mjs      # per-call wall / verify / settle
node gas.mjs <tx hashes>                  # gas + from per tx

# Tempo buyer = tempo CLI against the official RPC service (intent=session)
N=100 node rpc-bench.mjs                  # latency percentiles + balance-delta gas accounting
Enter fullscreen mode Exit fullscreen mode

Environment:

  • Measured 2026-06-14, ETH ≈ $1,667.
  • x402: exact scheme, USDC on Base mainnet, Coinbase CDP hosted facilitator.
  • Tempo: mainnet chain 4217, tempo CLI v1.8.2, tempo-request extension v0.5.2, token USDC.e.

x402 ran 30 settlements on Base, all submitted by relayer 0x93f6601151ccb08f333ab4b1cccfb1e188c0be44, gasUsed ~86,278 each (full tx hashes are in the bench output / EXPERIMENT-LOG). Tempo ran one 100-call session whose only on-chain transactions were open and close; the cooperative close receipt is 0x4875a71e7427b1ee2b5d6d553cbdccd446bdd63ab6329203678a980534c296d0 on explore.tempo.xyz. Service spent: $0.101 (101 paid vouchers — 100 calls plus one failed cold-start that still consumed a voucher); the unused remainder of the dynamic deposit was refunded on close.


I'm KAMO, a developer in Kyoto. I write implementation logs: working code, real costs, what broke. No theory I didn't run myself.

Top comments (0)