A standard interface for on-chain envelopes — records that meter the authority an autonomous agent consumes across many actions and across protocols, not just one call at a time.
Ethereum has rich standards for authorization, but they assume delegation to a single agent and a single dApp. Modern systems may have multiple agents using a wide range of standards, executing dynamic workflows across multiple protocols.
ERC-8312 is delegation for the modern era: it allows agents to be maximally autonomous while maintaining robust guardrails, acting as a single, shared substrate that connects upstream standards to downstream protocols in a way that maintains an agent's bounds over time.
Without a shared primitive, every upstream system needs bespoke integration with every downstream substrate.
O(upstream × downstream) collapses to O(upstream + downstream)Upstream systems create or reference envelopes; downstream substrates implement the state and validation semantics behind the cursor.
An aggregate bound can be a predicate re-checked from public state, or a counter each action advances. A protocol using ERC-8312 can even allow two or more agents to co-create mutual delegation logic, moving the delegation logic to the asset itself, and away from any one specific account.
An envelope records how much of a bounded mandate an agent has consumed across all actions — where no single protocol maintains the aggregate.
advanceCursor records against one object, so two concurrent draws cannot both pass a bound their sum would exceed. A read cannot reproduce that serialization across surfaces.
The public EnvelopeAdvanced log lets any party recompute whether cumulative consumption ever exceeded the cap — with no trust in the registry.
A narrow docking surface, not a substrate. The interface defines seven functions and three events. It deliberately omits capability issuance, intent submission, execution routing, and auditing. Different implementations compete on cursor semantics, proof systems, invariants, and trust assumptions while exposing the same surface.
Two terms carry the weight — the cursor, the advancing aggregate-state commitment, and the substrate, the implementation that maintains and validates it.
An on-chain record a principal registers at the start of a bounded course of action: an id, a principal, an immutable capabilityRoot, a mutable cursorRoot, an expiry, and lifecycle status.
A interoperability type — (chainId, registry, id) — that can be passed across applications and chains, letting many consumers query and update the same registry-maintained state.
The contract system that maintains the state a cursor commits to and validates cursor advances. A budget substrate tracks spend; identity, reputation, privacy, and contestation substrates maintain their own invariants. Anyone can create a substrate.
A substrate-specific contract that stores envelopes and implements IBoundedAgentAction. Its address identifies both the envelope namespace and the implementation responsible for validating cursor transitions.
A conforming implementation MUST implement IBoundedAgentAction and support ERC-165 discovery. The cursor representation, witness format, and enforced invariant are left to the substrate.
// SPDX-License-Identifier: CC0-1.0 interface IBoundedAgentAction is IERC165 { enum Status { None, Active, Completed, Contested, Revoked, Expired } struct Envelope { bytes32 id; address principal; bytes32 capabilityRoot; // fixed bytes32 cursorRoot; // advances uint64 createdAt; uint64 expiresAt; Status status; } // — reads (side-effect free) — function getEnvelope(bytes32 id) external view returns (Envelope memory); function getCursor(bytes32 id) external view returns (bytes32); function isActive(bytes32 id) external view returns (bool); // — writes — function registerEnvelope( address principal, bytes32 capabilityRoot, uint64 expiresAt, bytes calldata initData ) external returns (bytes32 id); function advanceCursor( bytes32 id, bytes calldata witness ) external returns (bytes32 newCursor); function setStatus(bytes32 id, Status s) external; }
Creates a unique envelope with status Active. If the principal is not the caller, the implementation MUST verify authorization (EIP-712, ERC-1271, or an upstream grant) and revert otherwise.
Atomic with the substrate state it represents. Rejects non-Active or expired envelopes. Validates an opaque witness bound to at least (id, prevCursor) so it cannot be replayed. Never open to arbitrary callers.
Reads that fold expiry into their result: a stored Active status on an expired envelope cannot mislead a consumer. Unknown ids MUST revert, never return a zero value.
Transitions follow the lifecycle state machine and emit EnvelopeStatusChanged. The metered party MUST NOT be able to move an envelope out of Active to escape an unmet bound.
0x3985961d for the base interface, 0x021ca455 for the Budget Substrate, 0xe664d441 for the Contestable extension. A consumer confirms a named profile before interpreting a cursor it did not define.
Carry the registry address together with the envelope id — and use (chainId, registry, id) when the reference may leave the current chain context. Non-normative integration patterns:
A caveat enforcer carries a reference in its terms and advances the cursor in its afterHook — only when execution actually occurs.
A wallet returns an envelope reference as opaque permission context; a dApp queries the registry before exercising it.
A paymaster gates aggregate budget, rejecting operations against inactive or exhausted envelopes. Reading a cursor in validation requires staking.
A modular smart account installs a module that queries or advances an envelope during execution — as a consumer, not an author of cursor semantics.
An intent flow carries the reference as bounded-action context; solvers query and advance the cursor as part of atomic settlement.
A fetcher or batch step queries getCursor for aggregate state used across a composed execution flow.
A registration or validation request associates agent identity or validation activity with substrate-maintained state.
An inference-proof verification result becomes the witness for advanceCursor: per-action verification, aggregate metering.
An integration includes an envelope reference in an implementation-defined HTTP field to bind a payment request to an aggregate budget.
Any registry that advertises the budget profile must follow it. That way, reading the cursor always means the same thing — how much of the agent's spending limit is left — no matter which registry you ask.
interface IBudgetSubstrate is IBoundedAgentAction { // capabilityRoot = keccak256(cap, asset) function bound(bytes32 id) external view returns (uint256 cap, address asset); // cursorRoot = keccak256(spent) function spent(bytes32 id) external view returns (uint256); // cap - spent, or 0 if not active function remaining(bytes32 id) external view returns (uint256); }
The witness is abi.encode(amount, authorization). advanceCursor rejects the advance unless it stays within the cap, then sets spent to spent + amount and recomputes the cursor.
A budget registry MUST maintain spent ≤ cap while Active, and spent MUST be monotonically non-decreasing — the interoperable guarantee consumers rely on.
keccak256(cap − remaining(id)) MUST equal getCursor(id) while Active — the accessor and the commitment cannot diverge.
A remaining of zero denotes either an exhausted bound or an inactive envelope; a consumer MUST consult isActive to tell them apart.
ERC-8312 is a Draft. Read the specification, review the reference implementation, and share feedback on Ethereum Magicians.