Arcveil
TypeScript

SDK. Issue receipts, publish mandates, verify both.

@arcveildev/sdk is the receipt format as code: the canonical hashing, the issuer, the five checks, viem chain definitions for Arc, and typed calls into the registries and the account. It runs in Node and in a browser — the site you are reading is its first consumer.

Install

viem is a peer dependency. The package is published from CI with provenance, so the registry holds a signed statement binding the tarball to the commit and the workflow that built it — which you can check rather than take on trust.

shell
pnpm add @arcveildev/sdk viem
checking where it came from
npm audit signatures# @arcveildev/sdk ... verified attestations

Verify

Parse first — anything arriving as text is untrusted — then run the checks against a chain reader. Arc allows cross-origin requests, so the same code runs in a browser with no backend in the path.

verify.ts
import { arc, ARC_REGISTRIES, createRpcChainReader, parseReceiptInput, verifyReceipts } from "@arcveildev/sdk"; // Anything arriving as text is untrusted until the schema says otherwise.const parsed = parseReceiptInput(json);if (!parsed.ok) return { error: parsed.errors }; const chain = createRpcChainReader({  endpoint: arc.rpcUrls.default.http[0],  chainId: arc.id,  ...ARC_REGISTRIES[arc.id],}); const report = await verifyReceipts(parsed.receipts, { chain });

Publish a mandate

Only the commitment reaches the chain. Keep the terms you hashed: without them you can prove a mandate was live, but never again show what it said. An epoch can be revoked, never overwritten — otherwise terms could change after receipts had been issued against them.

mandate.ts
import { mandateCommitment, registerMandate, revokeMandate, ARC_REGISTRIES, arc } from "@arcveildev/sdk"; const terms = [  "assets: USDC only",  "per action: 250 USDC",  "active hours: 02:00-06:00 UTC",].join("\n"); const commitment = mandateCommitment(terms); // keccak256 of the termsconst writer = { client: walletClient, registry: ARC_REGISTRIES[arc.id].mandateRegistry }; await registerMandate(writer, 1, commitment); // epoch 1await revokeMandate(writer, 1); // stops it; the epoch can never be reused

Issue receipts

An issuer holds the head of the budget chain and the mandate the receipts are checked against.

issue.ts
import { createIssuer, generateSigner } from "@arcveildev/sdk"; const signer = await generateSigner(); // stand-in: in production this key lives in the enclave let issuer = createIssuer({  chainId: arc.id,  account,  mandate: { commitment, epoch: 1 },  agent: { id, session, vision: "relative-only" },  checks: ["asset_allowlist", "per_action_cap", "window_spend"],  signer: { publicKey: signer.publicKey, privateKey: signer.privateKey },  counter: head, // last anchored budget commitment}); const { receipt, issuer: next } = await issuer.issue({ kind: "swap", userOpHash, settledTx });issuer = next; // issuing advances the chain — keep the issuer it hands back

Sign an account intent

The account executes only what two of its three keys signed, and only while the mandate named in the intent is live. The EIP-712 payload covers that mandate, so a signature gathered for one can never be replayed against another. Relaying is permissionless — authority is in the signatures, not in the sender.

intent.ts
import { encodeExecute, executeIntent, intentTypedData, signIntent } from "@arcveildev/sdk"; const intent = { call, nonce, deadline, epoch: 1, mandate: commitment }; // Two of the three keys sign the same EIP-712 payload, from wherever they live.const first = await signIntent(deviceClient, account, intent);const second = await signIntent(coSignerClient, account, intent); await executeIntent(relayClient, account, intent, [first, second]);

Exports

The public surface of @arcveildev/sdk.
SymbolWhat it does
parseReceiptInputzod parse of one receipt or a bundle. Returns { ok, receipts } or { ok: false, errors }.
verifyReceiptsRuns the five checks over a bundle and returns every verdict.
createRpcChainReaderReads mandates, anchors and transactions from an Arc RPC endpoint.
createMemoryChainReaderThe same interface over state you supply, for tests.
canonicalize · receiptBody · computeReceiptIdThe exact bytes a receipt hashes, and the hash itself.
createIssuer · nextCounterIssue receipts and advance the budget chain.
generateSigner · signBody · verifyBodySignature · issueReceiptECDSA P-256 over the canonical body.
mandateCommitment · registerMandate · revokeMandate · anchorCounterPublish and retire mandates; anchor a budget commitment.
intentTypedData · signIntent · executeIntent · encodeExecuteThe 2-of-3 account: sign one intent, relay it with two signatures.
adoptTypedData · encodeAdoptRotate the account onto a new mandate epoch, under the same quorum.
arc · arcTestnet · ARC_REGISTRIES · USDC_ERC20_ADDRESSviem chain definitions and the deployed addresses.
MANDATE_REGISTRY_ABI · ANCHOR_REGISTRY_ABI · ARCVEIL_ACCOUNT_ABIABIs, for calls the SDK does not wrap.