The network
Receipts and proofs
The part that makes this different from trusting a stranger's GPU: every response leaves a signed, committed trace you can check without asking anyone.
What a receipt is for
A receipt lets you establish three things after the fact, without trusting Vacuum, the router or the operator:
- a specific node committed to having produced this exact response
- it did so under a specific model build, not a cheaper substitute
- it was billed for the token count that response actually contains
It does not, on its own, prove the response came from the declared model. Nothing cheap can. That is what the audit layer is for, and the receipt is what makes auditing possible at all — it is the commitment an auditor later checks against.
Fetching one
curl https://api.vacuumfi.com/v1/receipts/0x9f3c1e7a2b8d40f6 \ -H "Authorization: Bearer $VACUUM_KEY"{ "id": "0x9f3c1e7a2b8d40f6", "epoch": 2926, "node": "vac1q7f4mk2xw9d3hs8p", "model": "qwen2.5-32b-instruct", "registry_id": "0x3f8b21c7e94a0d6528bf17ae3c904d11", "prompt_hash": "0x2b7e...c401", "response_hash": "0x8d14...9fa2", "tokens_in": 214, "tokens_out": 96, "created_at": 1789517810, "signature": "0x4c8f...11ed", "commitment": { "status": "committed", "root": "0x88c1a2cf87b4...fdfd996d", "block": 21067200, "chain_id": 5042, "proof": ["0x1a2b...", "0x3c4d...", "0x5e6f..."], "index": 1843 }}| Field | Type | Description |
|---|---|---|
| prompt_hash | bytes32 | keccak256 of the canonical request. Your prompt is not stored — only its hash. |
| response_hash | bytes32 | keccak256 of the response text. This is what makes the token count recomputable. |
| signature | bytes | The node's signature over the leaf. Recoverable to its registered address. |
| commitment.status | string | pending until the epoch closes, then committed. A proof exists only once committed. |
| commitment.root | bytes32 | The Merkle root published on Arc for this epoch. |
| commitment.proof | bytes32[] | Sibling hashes from your leaf up to the root. |
commitment.status, or fetch the receipt again later.Verifying it yourself
Three checks, none of which require Vacuum to be online or honest.
import { keccak256, toBytes, recoverMessageAddress } from "viem"; // 1. The node really signed this receipt.const signer = await recoverMessageAddress({ message: { raw: leafHash(receipt) }, signature: receipt.signature,});if (signer.toLowerCase() !== nodeAddress.toLowerCase()) throw new Error("wrong signer"); // 2. The response you hold is the response that was committed.if (keccak256(toBytes(responseText)) !== receipt.response_hash) { throw new Error("response does not match the receipt");} // 3. The receipt is in the tree whose root is on Arc.let node = leafHash(receipt);let index = receipt.commitment.index;for (const sibling of receipt.commitment.proof) { node = index % 2 === 0 ? keccak256(concat([node, sibling])) : keccak256(concat([sibling, node])); index = Math.floor(index / 2);}if (node !== receipt.commitment.root) throw new Error("not in the committed tree");Then confirm the root you just matched is the root Arc actually holds for that epoch. Read it straight from the chain — that is the step that makes the rest mean anything.
cast call 0x... "epochRoot(uint64)(bytes32)" 2926 \ --rpc-url https://rpc.mainnet.arc.ioArc is chain ID 5042 and its RPC is rpc.mainnet.arc.io. Once the root matches on-chain, the receipt cannot be altered or withdrawn by anyone, including us.
How fraud is caught
Receipts commit; audits check. A share of each epoch’s receipts is selected at random, using a seed published on-chain after receipts close, so an operator can never know in advance which of its requests will be examined.
What the auditor actually does
Not a re-run. Regenerating word for word would fail honest nodes, because different GPUs produce slightly different floating-point results. Instead the verifier runs the prompt and the submitted response through the declared model in a single forward pass and measures how likely that model was to produce this exact sequence.
- An honest response scores in line with the model.
- A fabricated one, or one from a smaller model, scores far below the floor.
- The check costs a fraction of the original generation, which is why it can be run often.
Alongside this, canary requests — indistinguishable from real traffic, with known expected output — run continuously. They catch truncation, generic filler and model substitution without waiting for a sampling round.
logprobs is not exposed on the API. Handing callers the same signal the audit scores against would let an operator tune a forgery to pass.Opening a dispute
If your own verification fails — the signature does not recover, the response hash does not match what you were sent, or the proof does not land on the on-chain root — you can escalate it. The receipt is the entire evidence package.
curl -X POST https://api.vacuumfi.com/v1/disputes \ -H "Authorization: Bearer $VACUUM_KEY" \ -H "Content-Type: application/json" \ -d '{ "receipt": "0x9f3c1e7a2b8d40f6", "reason": "response_hash_mismatch" }'The operator answers with its own evidence and a second, independent set of verifiers settles it. If fraud is confirmed, the operator’s collateral is slashed, its unclaimed earnings are voided, affected builders are refunded and the node is removed from the network.
Disputes are only accepted while the receipt’s challenge window is open — six epochs, roughly six hours, from the moment its epoch closed. After that the earnings have already unlocked and there is nothing left to slash. If you care about verification, verify promptly.