Lets small inference providers with no billing infrastructure charge machine customers by the second: keep paying and the inference keeps flowing; stop paying and the line gets cut.
Charging money first means building accounts, payment integration, invoicing, credit and collections, refund disputes, fraud prevention. That cost doesn't scale with the number of customers; it's a fixed barrier to entry. Big companies can amortize it; one person running a self-hosted model can't.
With this approach, a provider needs only a receiving address and a piece of middleware. The cost structure lines up too: for a small self-hosted provider, the real cost is the time the GPU is occupied, not the token count.
The barrier doesn't disappear; it changes sides: every customer needs a wallet holding USDC. For human customers that's a dealbreaker; for agents it isn't, since agents are provisioned by code in the first place.
POST /tick without payment → HTTP 402, with payment requirements in the PAYMENT-REQUIRED headerPAYMENT-SIGNATURE and resend; the facilitator submits on-chainonAfterSettle creates or extends the session and returns { sessionId, accepted }GET /stream, and tokens start flowingevent: credit shows < 300ms remaining, send another /tick to extend paidUntilevent: cut and actively closes the connection| Endpoint | x402 | Responsibility |
|---|---|---|
| POST /tick | Paid | Pay for a tick → create or extend the session |
| GET /stream | Free | SSE; emits tokens while the session is alive |
| GET /health | Free | Returns { ok, sessions }, for tests |
/stream being free isn't a loophole: a session only exists once it has been paid for.
This keeps the x402 middleware in the per-request position it's naturally good at, and the SSE path never touches payment logic.
Why a long-lived connection: if every tick were an independent request, "cutting the stream" would just be the agent breaking out of its own loop, and the server would never exercise any power at all. With a long-lived connection, the agent is still connected and still wants more, yet the line gets cut.
| Parameter | Value | Description |
|---|---|---|
| CREDIT_PER_TICK_MS | 1000 | Streaming time bought by one tick (voucher round trip measured 7–16ms) |
| TOPUP_THRESHOLD_MS | 300 | Sign the next voucher when remaining credit drops below this |
| GRACE_MS | 250 | Grace period after going overdue before the cut |
| WATCHDOG_SWEEP_MS | 100 | Cut sweep interval |
| PRICE_PER_TICK_ATOMIC | 40 | 0.00004 USDC |
| MAX_SPEND_ATOMIC | 50000 | 0.05 USDC, about 1,250 ticks |
| DEPOSIT_ATOMIC | 50000 | Opening deposit, the only transaction that waits on the chain (measured 5.6–6.9s) |
The agent tops up when its credit is about to run out, not on a fixed interval. Otherwise network and settlement latency would make the credit drift downward tick by tick,
and after a few ticks the stream would cut itself off. All parameters are validated with zod by src/config.ts before injection; nothing is hard-coded.
First Ctrl-C: the agent stops paying but stays connected and keeps reading the stream. After the remaining credit + the 250ms grace period + one 100ms sweep,
the server cuts the line; on receiving event: cut, the agent refunds its deposit and exits on its own. The first Ctrl-C not disconnecting is what makes the whole demo work: it's the server that cuts the line.
⚠ Testnet only. Get AVAX from the Core Wallet console and USDC from the Circle faucet. Never use real assets.