Skip to main content

What is a receipt?

A receipt is a cryptographically signed, gateway-issued document that records the outcome of a policy guard request. It is the trust artifact of the AgentChain system — independently verifiable without a network call.

Receipt lifecycle

Receipt fields

Verifier Note — executionTxHash is self-reported The EIP-712 ECDSA signature covers the receipt body at issue time, when executionTxHash = bytes32(0). After reportExecution(), the executionTxHash field is updated in the database but the original signature is not re-issued. Verifiers must use the original receiptHash (computed at issue time) for ECDSA verification — not a re-hashed post-execution body.
Deny Receipts are Signed deny and approval_required receipts are signed with the same ECDSA key as allow receipts. A signed deny receipt is a tamper-evident proof that the system evaluated and blocked the transaction. The decision field is included in the signed EIP-712 struct — it cannot be altered.

Signature scheme

Receipts are signed with EIP-712 (keccak256(0x1901 ‖ domainSeparator ‖ structHash)).
The chainId in the domain separator binds the signature to a specific chain. A receipt signed for Base Sepolia (84532) cannot be replayed on Base mainnet (8453) — the domain hash will differ.

Local verification (no network)

Uses @agentchain/core verifyReceiptSignature — pure ECDSA recover, no HTTP.

On-chain verification (trustless)

verifyWithAnchor performs two independent checks:
  1. ECDSA verifysignerAddress correctly recovers from receiptHash + signature
  2. On-chain checkSignerAnchor.isAuthorized(signerAddress) returns true
The SDK degrades gracefully (IC-30): if the RPC call fails, anchorState is 'unavailable' and ECDSA verification alone is used. Never a silent pass.

Remote verification

The gateway checks signature, expiry, state, and SignerAnchor revocation status.

Fetch by ID

Report execution

After guardTransaction returns allow, call this after you send the transaction:
guardAndExecute calls this automatically.

decisionExpiresAt

The gateway sets decisionExpiresAt to issuedAt + 300s (configurable). If your execution logic takes more than 5 minutes, call guardTransaction again for a fresh receipt. The SDK throws AgentChainExpiredDecision if decisionExpiresAt < now when guardAndExecute tries to send.
Policy revocation and the 5-minute window A receipt issued just before a policy is revoked remains valid for up to 300 seconds (5 minutes). This is by design — in-flight, already-authorized transactions must be allowed to complete. After decisionExpiresAt, reportExecution returns 410 DECISION_EXPIRED. The window cannot be shortened without breaking in-flight transactions.

Anchoring

When a policy has anchoring = "receipt_hash_onchain", the gateway’s ReceiptAnchorJob batch-writes the receiptHash to ReceiptAnchor.sol on the declared chain. The receipt transitions from executedanchored after 2 confirmations. ReceiptAnchor on Base Sepolia: 0x1F98D953785047f25a4886D12D103F4D67F1D8B3