Arcveil
Format

Receipt. One hash, a chain of commitments, and nothing you can read off it.

A receipt is what one settled action leaves behind. It proves the action stayed inside the account holder's mandate without revealing the mandate, the balances, or the reasoning. No amount, asset or balance appears anywhere in it — the check names are public, their thresholds are not.

One receipt

A real one, from the samples the verifier loads. Every hash in it resolves on Arc mainnet.

receipt.json
{  "v": 1,  "chain": 5042,  "account": "0x96b698308B01473E3A0041634b01f652c4608C2A",  "mandate": {    "commitment": "0x378571c07a6295730f1f0a120a955f27770e8c591b6cce7d58b9048192caf4f2",    "epoch": 1  },  "agent": {    "id": "0xa7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7",    "session": "0x22222222222222222222222222222222",    "vision": "relative-only"  },  "action": {    "kind": "swap",    "userOpHash": "0x7171717171717171717171717171717171717171717171717171717171717171",    "settledTx": "0x0dde838b393db26421538a9f60129c7c6aa6981fc147b60bfeca51056ce5950e",    "at": "2026-09-13T21:14:02.000Z"  },  "checks": ["asset_allowlist", "per_action_cap", "window_spend", "active_hours", "killswitch_clear"],  "counter": {    "prev": "0xcfb1186141e6367d9ef3ab41b5c8aa7eda456da2d947a4806f6fd295b68af2fc",    "next": "0x882c8a044982210fc414db35aca8ee4811a9a8b65277669bdc22c1265a43220e"  },  "proof": {    "type": "attestation",    "signer": "0x0411223cd026c5ba53886fc969a4ee7b6c29af4bf17e4d26e5ec8d389410261a8a054ba9faa9c0aee7d9973a6c705bae640600966f6ae7406625d4da5ab50867db",    "signature": "0xf5a29c862b05ee312699f8a8e59e6160bdfcac01dfe9878378495ef69dc905c5e7cc5da0a2b904921b8f141cd40819c9a88abcd15c19833ad6e15970c4277ffe"  },  "id": "0x47b0c8d67600d307103f50eecd386f854c48d353648c503f1c2651895257f3a1"}

Fields

Every field of receipt format v1, with the type the schema enforces.
FieldMeaning
v1 — the only version this schema accepts.
idsha256 of the canonical body. 32 bytes.
chainChain the action settled on. 5042 is Arc mainnet.
accountThe smart account that acted. Public on chain anyway.
mandate.commitment32-byte hash of the mandate terms. The terms themselves never leave the holder.
mandate.epochWhich registration of that mandate. An epoch can be revoked, never overwritten.
agent.id32 bytes identifying the agent.
agent.session16 bytes scoping one run, so two sessions never merge.
agent.vision"relative-only" or "absolute" — whether the model was given real numbers.
action.kind"transfer", "swap" or "approve".
action.userOpHashThe user operation this receipt covers. 32 bytes.
action.settledTxThe transaction it settled in. 32 bytes.
action.atISO 8601 timestamp, UTC.
checksNames of the policy clauses that ran — asset_allowlist, per_action_cap, and so on. Names only.
judgePresent only when some of those checks were semantic — answered by a model rather than by arithmetic. { model, commitment }: who answered, and a commitment binding the clauses and thresholds they were held to. Optional, and a receipt without it hashes to exactly the bytes it did before judges existed.
counter.prev / nextBudget commitments before and after this action. Consecutive receipts must join.
proofDiscriminated union: { type: "attestation", signer, signature } today, { type: "zk", system, data } in v1.

The id

The id is a sha256 over a canonical serialisation of the body. Two parties must produce identical bytes or no hash ever agrees, so the rules are fixed:

1.Object keys sorted, no whitespace, no non-finite numbers.
2.The body excludes id itself — a hash cannot cover its own value.
3.The body excludes the proof evidence: signature for an attestation, data for a zk proof.
4.It keeps the rest of the proof, so a receipt cannot be re-signed by a different signer and still hash the same.
checking it yourself
import { canonicalize, computeReceiptId, receiptBody } from "@arcveildev/sdk"; canonicalize(receiptBody(receipt)); // the exact bytes that get hashedawait computeReceiptId(receipt); // must equal receipt.id

The counter chain

Each receipt carries the budget commitment it started from and the one it left behind. The next is derived from the previous, the action, and — when an enclave supplies it — a commitment to cumulative spend. Consecutive receipts must join, which is what makes a dropped receipt visible rather than silent.

@arcveildev/sdk
// keccak256(previous ++ actionHash ++ spendCommitment)nextCounter(previous, userOpHash, spendCommitment?);

The proof

Today a receipt carries an enclave attestation. Version 1 replaces it with a zero-knowledge proof and changes nothing else in the format — which is the reason the proof is a discriminated union rather than two signature fields.

Fields of the proof union, for attestations and zk proofs.
MemberMeaning
signer65 bytes: the uncompressed P-256 public key of the policy signer. In production the private half never leaves the enclave.
signature64 raw bytes, r ‖ s. ECDSA P-256 over the canonical body, SHA-256.
systemzk proofs only: names the proving system. The in-browser verifier for these ships with v1, so the signature check reports unknown until then.