Every tier. Every curve.
Nothing merged.
The headline is ECDSA-per-payment, fully-verified payments per second on the hub. Every other tier below is its own shape with its own axis — labelled, sourced, reproducible with one command.
Add a 24-core hub, add half a million a second. Add a 48-core hub, add a million.
Fully-verified payments/s against hub count. Solid: measured (1, 2, 6, 11 hubs). Dashed: the same per-hub slope continued — 20 hubs is ten million a second, 200 hubs is a hundred million.
points: 11-hub fleet 5.84M/s · 11-hub fleet 5.74M/s · 6-hub fleet 3.11M/s · 4-hub fleet 2.21M/s · 2-hub fleet 1.27M/s · projection = hubs × 500,000
How we count.
A payment counts once it is verified, journaled and applied.
Every voucher is an EIP-712 signature over a cumulative balance. The hub recovers the signer (ECDSA), checks the channel, appends the payment to an append-only journal and applies it to the ledger. The timed window starts after the corpus is signed and ends when the last voucher is applied — signing is setup, never counted.
Corpus.
Per hub: 10,000 payers × 100 merchants, 1,000,000 unique pre-signed vouchers per distribution (uniform and hot-payer skew both run; the headline is uniform). Fleet hubs get disjoint payer sets and share the merchants. Every archive JSON carries the cardinality, the hardware, the core count and a scaling curve.
Per-process microseconds.
Where a row quotes µs per payment, it is Δ process CPU time over Δ payments applied across a clean window, read from ps — no profiler attached. The hub's 32 µs is what one core spends to verify, journal and apply one payment.
The tiers, and why they never touch.
- Engine-direct — the corpus goes straight into one hub process. The headline unit.
- Aggregate fleet — the sum of each hub's engine-direct rate across a fleet; one ClaimRouter settles it.
- Hub-ingress — a driver on the hub's gRPC wire with pre-encoded binary items. What a facilitator delivers; no HTTP.
- Verified-by-cache — one signature per K payments, the rest served from the signature cache. Its own curve, its own K=1 reference.
- Facilitator-boundary — completed
/settleround trips through the reference facilitator. Excludes the merchant hop. - Full path — agent → site → facilitator → hub over HTTP. What a client observes end to end.
- Component-isolated — one component against a stub. Diagnostics, never payments.
Binding rules.
- Fully-verified and optimistic benchmark modes are ALWAYS reported separately and labeled; never conflated.
- Raw apply-loop rate may only be reported as "ledger throughput", NEVER as payment TPS.
- Every benchmark discloses voucher-corpus cardinality, hardware, and core count; multi-core results include a scaling curve.
- The headline number is fully-verified payments/sec; testnet full-path numbers are NEVER conflated with local engine-direct numbers.
- Verified-by-cache runs (one signature authorising K>1 payments, requests 2..K verified by the hub's signature cache) are a separate tier from ECDSA-per-payment runs; they are labelled `verified-by-cache`, disclose signatures-per-payment and unique-signature count, and are never merged with or compared to ECDSA-per-payment numbers. The headline stays ECDSA-per-payment fully-verified.
- Hub-ingress runs (a driver speaking the hub's gRPC ingress directly, pre-encoded binary items, no facilitator, no HTTP) are their own tier, labelled `hub-ingress`; they disclose streams, items per frame, verify threads and a verify-thread scaling curve, and are never merged with or compared to facilitator-boundary, full-path, or engine-direct in-process numbers. The headline stays ECDSA-per-payment fully-verified.
Run it yourself.
Every figure on this page came out of a just recipe in the repository. The fleet recipes take a hosts file of bare Ubuntu boxes and do the rest.
just demo # Anvil → deploy → fund → x402 payments → epoch settlement
# → site → facilitator → hub, with exact reconciliationjust demo-local 4 # ports 8700–8799; HOLD=1 keeps it up for page work
# nginx + indexer + facilitators + hubs, no hardware neededjust bench-gas # postEpochRoot + claimEpoch + one-tx router-claimjust sp2-gate && just sp3-gate && just sp4-gate && just sp5-gate
just rearch-gate # replay, envelopes + x402 SDK, 402 flow, reorgs, async full stack# every archived report, matrix and study lives under demo/fleet/archives/
# the transcribed findings live in docs/perf-notes.mdEngine-direct — one hub engine-direct · HEADLINE
A pre-signed voucher corpus fed straight into one hub engine (T3): every payment ECDSA-verified, journaled and applied. This is the headline tier and the unit the fleet multiplies.
| run | headline | hardware | cores | corpus / shape | date | curve (verify workers) | archive |
|---|---|---|---|---|---|---|---|
| laptop baseline (main @52063f4), T3 fully-verified payment TPS (fully verified, HEADLINE) | 749k fully-verified payments/s | Apple M5 Max | 18 | 256 x 100 payers×merchants, 100,000 unique vouchers | 2026-09-08 | baseline-main-20260908/bench-1788842952.json | |
| laptop baseline (main @52063f4), T3 fully-verified payment TPS (fully verified, HEADLINE) | 733k fully-verified payments/s | Apple M5 Max | 18 | 2000 x 100 payers×merchants, 200,000 unique vouchers | 2026-09-08 | baseline-main-20260908/bench-1788842900.json | |
| single CCX63 hub, T3 fully-verified payment TPS (fully verified, HEADLINE) | 533k fully-verified payments/s | Hetzner CCX63 (AMD EPYC-Milan, 48 vCPU / 24 physical) | 48 | 10000 x 100 payers×merchants, 1,000,000 unique vouchers | 2026-09-01 | hetzner-ccx63-hub-bench-20260901.json | |
| phase2-hub2-168.119.99.99.bench: single Milan hub, T3 fully-verified payment TPS (fully verified, HEADLINE) | 489k fully-verified payments/s | x86_64 | 24 | 10000 x 100 payers×merchants, 1,000,000 unique vouchers | 2026-07-03 | phase2-hub2-168.119.99.99.bench.json | |
| phase2-hub1-46.225.17.94.bench: single Milan hub, T3 fully-verified payment TPS (fully verified, HEADLINE) | 465k fully-verified payments/s | x86_64 | 24 | 10000 x 100 payers×merchants, 1,000,000 unique vouchers | 2026-07-03 | phase2-hub1-46.225.17.94.bench.json | |
| phase2-hub3-204.168.150.195.bench: single Milan hub, T3 fully-verified payment TPS (fully verified, HEADLINE) | 449k fully-verified payments/s | x86_64 | 24 | 10000 x 100 payers×merchants, 1,000,000 unique vouchers | 2026-07-03 | phase2-hub3-204.168.150.195.bench.json | |
| engine HTTP x402 path (T4, informational) informational only (NOT a thesis gate) | 73k paid requests/s (fully-verified, server-side) | Apple M5 Max | 18 | 256 payers × 78 vouchers/payer, 19968 unique payment ids, 18 HTTP workers (driver shares the box) | 2026-09-08 | single point | baseline-main-20260908/bench-1788842952.json |
Aggregate fleet — add a hub, add half a million a second engine-direct · fleet sum
One hub per host, disjoint payer sets, one shared ClaimRouter. The fleet figure is the sum of every hub's max-workers fully-verified rate; the projection line continues the measured per-hub slope.
| run | headline | hardware | cores | corpus / shape | date | curve (hubs) | archive |
|---|---|---|---|---|---|---|---|
| 11-hub fleet, T3 fully-verified (sum) payment TPS (fully verified, HEADLINE) | 5.84M fully-verified payments/s | 10× AMD EPYC-Milan Processor, 1× Apple M5 Max | 258 | per hub: 10000 x 100 payers×merchants, 1,000,000 unique vouchers | 2026-07-03 | 11 hubs · per-hub 370k–798k/s | fleet-report-phase4-11hubs.json |
| 11-hub fleet, T3 fully-verified (sum) payment TPS (fully verified, HEADLINE) | 5.74M fully-verified payments/s | 10× AMD EPYC-Milan Processor, 1× Apple M5 Max | 258 | per hub: 10000 x 100 payers×merchants, 1,000,000 unique vouchers | 2026-07-03 | 11 hubs · per-hub 409k–760k/s | fleet-report-phase4-11hubs-run2.json |
| 6-hub fleet, T3 fully-verified (sum) payment TPS (fully verified, HEADLINE) | 3.11M fully-verified payments/s | 5× AMD EPYC-Milan Processor, 1× Apple M5 Max | 138 | per hub: 10000 x 100 payers×merchants, 1,000,000 unique vouchers | 2026-07-03 | 6 hubs · per-hub 423k–787k/s | fleet-report-phase3-6hubs.json |
| 4-hub fleet, T3 fully-verified (sum) — phase 2 payment TPS (fully verified, HEADLINE) — provenance: session log only (report JSON lost to out/ wipe) | 2.21M fully-verified payments/s | Apple M5 Max (local) + Hetzner 24c Milan hubs (SMT off) | 90 | per hub: 10,000 payers × 100 merchants, 1,000,000 unique vouchers | 2026-07-03 | single point | SCALING-SUMMARY.md (aggregate from session log; per-hub Milan bench JSONs rescued (phase2-hub*.json): 465k/489k/449k, local ≈808k) |
| 2-hub fleet, T3 fully-verified (sum) — phase 1 payment TPS (fully verified, HEADLINE) — provenance: session log only (report JSON lost to out/ wipe) | 1.27M fully-verified payments/s | Apple M5 Max (local) + Hetzner 24c Milan hubs (SMT off) | 42 | per hub: 10,000 payers × 100 merchants, 1,000,000 unique vouchers | 2026-07-03 | single point | SCALING-SUMMARY.md (session log only (report JSON lost to out/ wipe)) |
Hub-ingress — the wire the facilitators speak hub-ingress
A driver on the hub's gRPC ingress with pre-encoded binary items: no facilitator, no HTTP, one process. Scales with verify threads; the batch size is the lever.
| run | headline | hardware | cores | corpus / shape | date | curve (verify threads) | archive |
|---|---|---|---|---|---|---|---|
| Wave 4 — wave4-laptop (run-matrix.sh) — hub-ingress ladder hub-ingress — a driver on the hub's gRPC ingress, pre-encoded binary items, no facilitator, no HTTP; its own tier, never merged with HTTP or in-process numbers | 424k items/s, fully verified, at 12 verify threads (34 µs of hub CPU per payment) | Apple M5 Max, 18 cores, Darwin 25.6.0, 26.6.2 | 18 | 512 payers, 8 streams × 256 in flight, 64 items per frame; verify-thread ladder 1/2/4/8/12 | 2026-09-09 | after-wave4-20260909/matrix-summary.json | |
| Wave 3b — fixes + rerun — hub-ingress ladder hub-ingress — a driver on the hub's gRPC ingress, pre-encoded binary items, no facilitator, no HTTP; its own tier, never merged with HTTP or in-process numbers | 416k items/s, fully verified, at 12 verify threads (34.2 µs of hub CPU per payment) | Apple M5 Max, 18 cores, 64 GiB, macOS 26.6.2 — release binaries, idle box (wait_idle_box) | 18 | 512 payers, 8 streams × 256 in flight, 64 items per frame; verify-thread ladder 1/2/4/8/12 | 2026-09-09 | after-wave3b-20260908/README.md | |
| Wave 3 — gate-phase After (hub ingress) — hub-ingress ladder hub-ingress — a driver on the hub's gRPC ingress, pre-encoded binary items, no facilitator, no HTTP; its own tier, never merged with HTTP or in-process numbers | 259k items/s, fully verified, at 12 verify threads (39.5 µs of hub CPU per payment) | Apple M5 Max, 18 cores, 64 GiB, macOS 26.6.2 — release binaries, idle box (wait_idle_box) | 18 | 512 payers, 8 streams × 256 in flight, 64 items per frame; verify-thread ladder 1/2/4/8/12 | 2026-09-08 | after-wave3-20260908/README.md |
Verified-by-cache — one signature, K payments verified-by-cache
One ECDSA signature authorises a window of K payments; requests 2..K hit the hub's (digest, signature) → signer cache. Its own tier with its own curve — the K=1 reference below runs through the same harness.
| run | headline | hardware | cores | corpus / shape | date | curve (verify workers) | archive |
|---|---|---|---|---|---|---|---|
| T3c verified-by-cache, K=8 verified-by-cache — 8 signatures per payment window, requests 2..8 verified by the hub's signature cache (hit ratio 87.5 %); its own tier, never compared to ECDSA-per-payment | 3.75M payments/s, verified-by-cache — one ECDSA per 8 payments (208k per core) | Apple M5 Max (18 cores) | 18 | 2000 x 100 payers × merchants, 200,000 payments, 25,000 unique signatures, cache 262,144 entries | 2026-09-08 | after-phase0-20260908/bench-1788855830.json tiers.t3c_verified_by_cache.uniform | |
| same-harness ECDSA reference, K=1 ecdsa-per-payment reference inside the verified-by-cache harness (not the T3 headline harness) | 741k payments/s, ECDSA per payment, through the SAME mirror harness — the only in-tier apples-to-apples | Apple M5 Max (18 cores) | 18 | 2000 x 100 payers × merchants, 200,000 unique vouchers, one signature each | 2026-09-08 | after-phase0-20260908/bench-1788855830.json tiers.t3c_verified_by_cache.ecdsa_reference_same_harness |
Facilitator-boundary — through the reference facilitator facilitator-boundary
Completed POST /settle round trips at the facilitator's listener (fresh applies only). Excludes the agent→merchant hop and merchant-server overhead. Not comparable to site-path or engine-direct numbers. The reference facilitator is unoptimised on purpose: it is the surface platforms replace with their own.
| run | headline | hardware | cores | corpus / shape | date | curve (workers) | archive |
|---|---|---|---|---|---|---|---|
| Wave 4 — wave4-laptop (run-matrix.sh) — settle-only ramp facilitator-boundary — completed POST /settle round trips at the facilitator's listener; excludes the agent→merchant hop and merchant-server overhead | 60.6k fresh payments/s at 256 workers (hub 32.3 µs, facilitator 34.8 µs of CPU per payment) | Apple M5 Max, 18 cores, Darwin 25.6.0, 26.6.2 | 18 | ramp --layer merchant-boundary, fresh stack per worker count {32,128,256}; A/B --ingress-window-us {0,100} @ 256 | 2026-09-09 | after-wave4-20260909/matrix-summary.json | |
| Wave 4 — Hetzner three-role fleet — cross-box knee (facilitator host → hub host) facilitator-boundary, cross-box — the facilitator on one host streaming into a hub on another over the private network; excludes the agent→merchant hop | 59.9k fresh payments/s at 512 workers (hub 97.9 µs, facilitator 74.6 µs of CPU per payment) | Hetzner CCX63 ×2 (48 vCPU, 184 GB) + CCX33 (8 vCPU), Ubuntu, private net 0.5 ms RTT | — | Hetzner: facilitator on the multi host → engine on the hub host, single-step ramps 32/128/256/512, measurement corpus, reference paused | 2026-09-09 | hetzner-wave4-20260909/matrix | |
| Hosted devnet, scenario H — reference facilitator knee (one 8-core hub + facilitator pair) facilitator-boundary — loadgen merchant ramp through the unoptimised reference facilitator into one hub; the landing footnote's 60,000/s per 8-core pair | 59.6k payments/s at 512 workers, 100 % success (p95 2 ms at 32 workers) | Hetzner CCX33 pair (AMD EPYC-Milan, 8 vCPU per role), private network | 8 | worker ladder 32/128/256/512/1024; knee rule: first step with p95 > 3 × max(step-1 p95, 1 ms), or success < 0.995, or marginal Δtps/Δworkers < 5 % of the step-1 slope; ceiling = best tps strictly before it | 2026-09-10 | hetzner-wave5-20260910/scenarios/H/knee/knee.json | |
| Wave 3b — fixes + rerun — settle-only ramp facilitator-boundary — completed POST /settle round trips at the facilitator's listener; excludes the agent→merchant hop and merchant-server overhead | 59.5k fresh payments/s at 256 workers (hub 39.9 µs, facilitator 37.7 µs of CPU per payment) | Apple M5 Max, 18 cores, 64 GiB, macOS 26.6.2 — release binaries, idle box (wait_idle_box) | 18 | Facilitator-boundary settle-only, ramp --layer merchant-boundary, one fresh stack per worker count (32 / 128 / 256) | 2026-09-09 | after-wave3b-20260908/README.md | |
| Wave 3 — gate-phase After (hub ingress) — settle-only ramp facilitator-boundary — completed POST /settle round trips at the facilitator's listener; excludes the agent→merchant hop and merchant-server overhead | 56.5k fresh payments/s at 256 workers (hub 40.6 µs, facilitator 41 µs of CPU per payment) | Apple M5 Max, 18 cores, 64 GiB, macOS 26.6.2 — release binaries, idle box (wait_idle_box) | 18 | Facilitator-boundary settle-only, ramp --layer merchant-boundary, one fresh stack per worker count (32 / 128 / 256) | 2026-09-08 | after-wave3-20260908/README.md | |
| G0 — wave 3 pre-change baseline — settle-only ramp facilitator-boundary — completed POST /settle round trips at the facilitator's listener; excludes the agent→merchant hop and merchant-server overhead | 48.4k fresh payments/s at 32 workers (hub 99.2 µs, facilitator 52.8 µs of CPU per payment) | Apple M5 Max, 18 cores, 64 GiB, macOS 26.6.2 — release binaries, idle box | 18 | Facilitator-boundary settle-only (ramp --layer merchant-boundary, direct /settle, no shim, no site), one fresh stack per worker count (32 / 128 / 256), 3 M vouchers registered on-chain | 2026-09-08 | baseline-wave3-20260908/README.md | |
| merchant-boundary soak, driver prebuilt-replay (384 workers) fully-verified (server-side), facilitator-boundary direct /settle — EXCLUDES the agent→merchant hop and merchant-server overhead; NOT comparable to full-path site→facilitator→hub numbers and NEVER to engine-direct numbers | 39.9k fully-verified payments/s (facilitator-boundary) | Hetzner CCX63 (AMD EPYC-Milan Processor, 48 vCPU) — hub + facilitator share the box | 48 | 512 payers × 8 merchants × 4000000 unique pre-signed vouchers; batch-settlement scheme, direct /settle, payer-partitioned across 384 worker(s) | 2026-09-01 | single point | hetzner-suite-20260901/merchant-replay-final.json |
| merchant-boundary soak, driver merchant-http (256 workers) fully-verified (server-side), facilitator-boundary direct /settle — EXCLUDES the agent→merchant hop and merchant-server overhead; NOT comparable to full-path site→facilitator→hub numbers and NEVER to engine-direct numbers | 33.3k fully-verified payments/s (facilitator-boundary) | Hetzner CCX63 (AMD EPYC-Milan Processor, 48 vCPU) — hub + facilitator share the box | 48 | 512 payers × 8 merchants × 2000000 unique pre-signed vouchers; batch-settlement scheme, direct /settle, payer-partitioned across 256 worker(s) | 2026-09-01 | single point | hetzner-suite-20260901/merchant-http-final.json |
| facilitator-layer ramp probe (laptop baseline) — ceiling before the knee facilitator layer (via a minimal in-process resource shim; excludes site middleware; NOT full-path) — uncapped max-rate probe | 13k best sustained TPS before the knee (ceiling probe, NOT a headline) | Apple M5 Max | 18 | 512 demo payer(s) (one per max-worker slot, engine::demo derivation) x 1 merchant x 1 endpoint; channel scheme only, uncapped (no rate limiting, no retries) | 2026-09-08 | baseline-main-20260908/ramp-facilitator-1788842830.json |
Full path — agent → site → facilitator → hub full-path / site boundary
Agent-shaped: driver → site → facilitator → hub over HTTP, rate-limited, retrying. The only tier whose headline is a payment TPS an external client observed end to end.
| run | headline | hardware | cores | corpus / shape | date | curve (workers) | archive |
|---|---|---|---|---|---|---|---|
| full-path soak at target 200/s (64 wallets, 2 hubs) fully-verified (server-side), full-path site→facilitator→hub over HTTP — reported separately from engine-direct local numbers | 200 fully-verified payments/s (client-observed, site boundary) | Hetzner CCX33 stack (AMD EPYC-Milan Processor, 48 vCPU reported by the driver box) | 48 | 64 wallets × 2 hub(s) (sticky) × 3 endpoint(s); channel-dominant + exact trickle | 2026-09-01 | single point | hetzner-suite-20260901/amoy-fullpath-final.json |
Component-isolated — diagnostics, not payments component · NOT payment TPS
One component against a stubbed neighbor — a diagnostic ceiling for that component alone. Never a payment TPS, never comparable to any tier above.
| run | headline | hardware | cores | corpus / shape | date | curve (workers) | archive |
|---|---|---|---|---|---|---|---|
| site isolated component-isolated (stubbed neighbor; NOT full-path; NOT payment TPS) — site middleware overhead (stub facilitator) | 10.1k paid req/s (stub facilitator) | AMD EPYC-Milan Processor | 48 | {"endpoint":"/api/paid/time","mode":"site","requests":3000,"workers":8} | 2026-09-01 | single point | hetzner-suite-20260901/component-site-1788266808.json |
| indexer isolated component-isolated (stubbed neighbor; NOT full-path; NOT payment TPS) — indexer ingest rate + query latency under synthetic load | 14.5k ingest rows/s | AMD EPYC-Milan Processor | 48 | {"expectRows":200000,"querySecs":30,"workers":4} | 2026-09-01 | single point | hetzner-suite-20260901/component-indexer-1788266884.json |
| facilitator isolated component-isolated (stubbed neighbor; NOT full-path; NOT payment TPS) — facilitator verify/settle overhead (stub engine) — routing + serde + HTTP | 6.48k settle req/s (stub engine) | AMD EPYC-Milan Processor | 48 | {"mode":"facilitator","requests":5000,"syntheticPayers":64,"workers":8} | 2026-09-01 | single point | hetzner-suite-20260901/component-facilitator-1788266797.json |
| chain path: exact-settle latency + dispenser funding chain-path bench: exact-settle latency + confirmation distribution + dispenser funding rate — on-chain path, NOT payment TPS | 2.02k exact-settle RTT p50 ms (on-chain path) | AMD EPYC-Milan Processor | 48 | 5 exact settles, confirmation depth 15 | 2026-09-01 | single point | hetzner-suite-20260901/chain-anvil-1788266963.json |
The files.
Every row above links to its archive under demo/fleet/archives/ — report JSONs, matrix summaries, knee ladders, READMEs with the raw logs.
- baseline-main-20260908/bench-1788842952.json
- baseline-main-20260908/bench-1788842900.json
- hetzner-ccx63-hub-bench-20260901.json
- phase2-hub2-168.119.99.99.bench.json
- phase2-hub1-46.225.17.94.bench.json
- phase2-hub3-204.168.150.195.bench.json
- fleet-report-phase4-11hubs.json
- fleet-report-phase4-11hubs-run2.json
- fleet-report-phase3-6hubs.json
- SCALING-SUMMARY.md
- after-wave4-20260909/matrix-summary.json
- after-wave3b-20260908/README.md
- after-wave3-20260908/README.md
- after-phase0-20260908/bench-1788855830.json
- hetzner-wave4-20260909/matrix
- hetzner-wave5-20260910/scenarios/H/knee/knee.json
- baseline-wave3-20260908/README.md
- hetzner-suite-20260901/merchant-replay-final.json
- hetzner-suite-20260901/merchant-http-final.json
- baseline-main-20260908/ramp-facilitator-1788842830.json
- hetzner-suite-20260901/amoy-fullpath-final.json
- hetzner-suite-20260901/component-site-1788266808.json
- hetzner-suite-20260901/component-indexer-1788266884.json
- hetzner-suite-20260901/component-facilitator-1788266797.json
- hetzner-suite-20260901/chain-anvil-1788266963.json