The payment flow
Every request your node serves moves through the same on-chain payment flow: reserve → escrow → serve → receipt → settle. The price is fixed before any work happens and the charge is provable afterward, so you can serve anonymous users without trusting them, and they can pay you without trusting you.
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
Your node derives the maximum price from an input count the client declares on its reserve call, plus the output ceiling for the request. Reserve happens before the client sends the prompt, so the client measures its own request body and declares its size.
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 caps whatever the client
declares at context_window − max_output and never raises it.
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.
The exception is a long prompt whose reserve is already at the
context_window − max_output cap. The size estimate runs about twice a real
tokenizer on prose, so it can't tell whether the prompt fits. Your node forwards
it and lets your backend decide, as long as the estimate is within three times
the budget; past that it refuses context_length_exceeded up front. If your
backend refuses the prompt as too long, the payer sees context_length_exceeded
and the request settles at zero cost. A forwarded request is not offered your
built-in tools, because a single round can already fill the window; tools the
caller defined itself are still passed through.
The three-times limit matters if you declare a context_window smaller than
your backend's real one. A forwarded prompt can then use more input than the
reserve covers, and the payer's charge stops at the escrowed amount, so you
absorb the difference, up to that limit.
An honest client sizes with the same bound your node measures with, so the two
agree within a tolerance. The check stops 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. Your node's signing key authorizes the escrow-opening transaction (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 becomes refundable to them only after its deadline passes
(expires_at plus the settlement grace period). Once you claim, the two-phase
settle 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 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.
Step 5 — Settle: the escrow pays out
Settlement is two-phase. 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.
- 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.
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 did even if both you and the user are offline when the grace period elapses.
What this means for running a node
- Leave the input-budget check on. Without
zs.reserve.enforce_input_budget, 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 measure the real rate fromzs_reserve_input_budget_over_totalbefore enforcing. - 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. The grace-period fallback is for clients that vanish, not your normal path.