The payment flow
Every request your node serves moves through the same on-chain payment flow: reserve → escrow → serve → receipt → settle. The flow is designed so the price is fixed before any work happens and the charge is provable afterward — which means you can serve anonymous users without trusting them, and they can pay you without trusting you.
This page describes the flow from your node's side. The user-facing summary is in The operator network.
The happy path, end to end:
Step 1 — Reserve, you quote a price ceiling
A client calls your node's reserve endpoint before it sends a prompt. Your node returns a ticket: a signed quote that names a price ceiling for the request.
The ticket is signed by your node's signing key (the one registered on-chain) and includes, at minimum:
| Field | What it carries |
|---|---|
| Ticket id | Identifies this request. |
| Operator id and node id | Your on-chain identity. |
| Maximum price and minimum price | The most this request can cost, and the floor charged on any non-zero usage. |
| Expiry | Clients reject a ticket that's too close to expiring, so quote enough validity to cover the request. |
| Cryptographic material | What the client needs to encrypt its prompt to you and to verify the reply (covered in Encryption & keys). |
Because the ceiling is signed, you can't raise the price after serving. Because the client checks the signature against your on-chain key, a relay in the middle can't forge a ticket in your name.
How the ceiling gets sized
The maximum price is derived from an input count the client declares on its reserve call, plus the output ceiling for the request. The client hasn't sent you the prompt yet — it's still encrypted, and reserve happens first — so it measures its own request body and tells you how big it is.
That count is sized from the actual body, not the model's context window. A
short question against a 1M-context model reserves a few hundred tokens, so the
payer locks cents rather than dollars. Your node clamps whatever the client
declares: capped at context_window − max_output, floored at
zs.reserve.min_input_count.
Sizing from the real body only works if someone checks the claim, so once the
prompt arrives your node re-measures the decrypted body against what was
reserved. That check is on by default (enforce_input_budget). Over budget, a
plain request is refused before you spend anything upstream — sealed, zero-cost,
fully refunded — and a tool loop stops at the boundary while still returning a
final answer from what it already gathered.
An honest client is never caught by this: it sizes with the same bound your node
measures with, so the two agree within a tolerance. What the check stops is a
payer who under-declares to reserve less than their prompt actually costs you.
Configuration is in
zs.reserve; the metric to watch is
zs_reserve_input_budget_over_total.
Step 2 — Escrow: funds are locked on-chain
The client locks the ticket's maximum price in the escrow contract, opening an on-chain ticket record that names you as the payee. The escrow-opening transaction is authorized by your node's signing key — your node pre-signs it as part of the reserve response — so only a node you control can open a ticket that pays you.
Once the ticket is open you can confirm on-chain that the funds are real before
spending compute. The user can't pull the funds back during the live window — an
unclaimed ticket only becomes refundable to them after its deadline passes
(expires_at plus the settlement grace period). Once you claim, the two-phase
settle (below) governs.
Step 3 — Serve: you run the model
The client sends its encrypted prompt to your inference endpoint. Your node decrypts it, runs the model, and streams the reply back encrypted (see Encryption & keys). You track the actual resources used — input tokens, output tokens, and any images produced — because that's what you'll bill for.
Step 4 — Receipt, you sign what it actually cost
When the response is complete, your node issues a receipt, signed by the same node key. The receipt states:
- The ticket id it settles.
- The actual input and output counts (and any auxiliary output, such as images produced by an in-loop image tool).
- The amount charged — which must fall within the ticket's
[minimum, maximum]range. - A hash of the response body you returned.
The client verifies all of this before it will settle:
- The signature matches your on-chain node key.
- The amount doesn't exceed the ticket's maximum (and meets the minimum on non-zero usage).
- The body hash matches the bytes the client actually received.
If any check fails, the client refuses to settle. A receipt that over-charges, isn't signed by your key, or doesn't match the delivered response is worthless — which is exactly why the price a user sees is trustworthy.
Step 5 — Settle: the escrow pays out
Settlement is two-phase and atomic, so neither side can finalize a number the other didn't agree to:
On the happy path both halves ride in one atomic two-transaction group: your node pre-signs its first-half claim and hands it back in the response, and the client signs the matching acknowledgement and broadcasts the group. (The standalone first-half your node can submit on its own — described below — is the fallback, not the normal path.)
- You claim. The first half of the settlement, authorized by your signing key, carries the amount charged and a digest of the receipt.
- The client acknowledges. The client submits the matching second half. If
its receipt matches your claim, the contract releases funds in one atomic
group:
- You receive the amount charged.
- The protocol fee goes to the treasury (see Staking & economics).
- The remainder of the escrow is refunded to the user.
When the two sides disagree
If the client's view and your claim don't match, the ticket freezes into a dispute state rather than paying out. Both signed claims are on-chain, so the disagreement can be arbitrated off-chain from cryptographic evidence — there's no "he said, she said," only two signatures.
When the client never acknowledges
If you've served a request and claimed, but the client never submits its half (it went offline, say), you aren't stuck. After a grace period that the ticket carries, your pending claim can be force-finalized on the terms you claimed. This call is permissionless — your node normally posts it, but a watcher bot (or anyone) can finalize a lapsed claim on your behalf, so you get paid for work you actually did even if both you and the user are offline when the grace period elapses.
What this means for running a node
- Quote honestly and generously enough. The ticket's maximum is your hard ceiling; size it to cover the worst case of the request, because you can't raise it later. Anything unused is refunded to the user, so a generous ceiling doesn't cost the user — it just protects you.
- Leave the input-budget check on.
zs.reserve.enforce_input_budgetis what makes realistic reserve sizing safe for you: without it, a payer can declare a smallinput_count, send a large prompt, and have you serve the difference. Turn it off only for a trusted single-user node, or temporarily to size the real rate fromzs_reserve_input_budget_over_totalbefore enforcing. - Bill exactly what you served. The body hash binds your receipt to the bytes you sent. You can't be paid for a reply the client can't verify it received, and you can't quietly pad the count.
- Keep your signing key online and safe. It authorizes opens, signs tickets and receipts, and claims settlements. It is the identity users pay. Treat it as a hot key with real money behind it — see Encryption & keys.
- Settle promptly. Claim settlements as responses complete; rely on the grace-period fallback only for clients that vanish, not as your normal path.