Abstract
This ERC defines a standard interface for registering and updating bounded agent actions. Each action is represented by an on-chain envelope containing a principal, an immutable capability commitment, a mutable state commitment called a cursor, an expiry, and lifecycle status.
Unlike a per-call check or a predicate recomputed from public state, an envelope meters the authority an agent has consumed across many actions and across protocols. The interface specifies the docking surface for a substrate; non-bypassability and atomic enforcement are properties a substrate establishes, for example by owning the assets or gating the account's only execution path, not guarantees of the interface itself.
Consumers can query an envelope and its current cursor, while substrate-specific implementations validate and apply cursor transitions. The standard defines envelope registration, reads, cursor advancement, lifecycle transitions, events, and ERC-165 discovery. It does not prescribe the cursor representation, witness format, proof system, or invariant enforced by an implementation.
The resulting envelope reference can be passed across applications, allowing multiple consumers to query and update the same registry-maintained state without recreating the agent's bounds or policy context in each protocol.
Motivation
Envelopes provide a docking primitive between two zones of Ethereum infrastructure that currently have no common interface.
Upstream zone: authorization, validation, and execution routing.
-
ERC-4337 specifies how a UserOperation reaches the chain through an alternative mempool, validator, and paymaster.
-
ERC-7579 standardizes how a modular smart account composes validators, executors, and hooks.
-
ERC-7710 defines how a delegation is structured and how a delegate redeems it.
-
ERC-7715 defines how a dApp requests a scoped permission from a wallet.
-
A concurrent draft defines how multiple operations are composed and batched.
These standards determine who may call what, under whose authority, and through which execution path. They do not define a common object that binds later actions to aggregate protocol-maintained state after authorization has completed.
Downstream zone: invariant-maintaining contracts.
The downstream side maintains properties that must remain true across multiple calls, such as a remaining budget, an identity binding, a reputation score, a privacy commitment, or a dispute state. These properties are typically implemented in separate contracts, oracles, or registries, and each integration must be able to read and update them consistently.
Without a shared primitive, each upstream system needs bespoke integration with each downstream substrate, producing O(upstream x downstream) integration complexity. A uniform envelope interface reduces this to O(upstream + downstream): upstream systems create or reference envelopes, while downstream substrates implement the state and validation semantics behind the cursor.
This standard is motivated by three observations:
-
Invariant enforcement is structurally distinct from call authorization. A hook, caveat, validator, or paymaster check runs on a particular execution path, whereas an envelope persists across calls and can be consulted by every later action that must respect the same invariant.
-
A narrow interface enables horizontal substrate ecosystem growth. Different implementations can compete on cursor semantics, proof systems, invariants, and trust assumptions while exposing the same docking surface.
-
Account abstraction and capability standards benefit from a substrate-side counterpart designed for transient, capability-scoped autonomous agents rather than only persistent user accounts.
The envelope is not itself a substrate. It is the common interface through which substrate-specific registries expose persistent state.
Concepts (Non-normative)
This section provides an informal description of envelopes, references, substrates, and registries. Normative requirements begin in the Specification section.
Envelope
An envelope is an on-chain record that an agent, or a principal acting on the agent's behalf, registers at the start of a bounded course of action. Downstream contracts read and update the envelope as the action proceeds.
-
Identifier (id): a value unique within the registry that created the envelope. An id alone MUST NOT be treated as globally unique.
-
Principal: the address that authorized the envelope, typically the user or smart account whose authority the agent is using.
-
Capability root (capabilityRoot): a commitment to the bounded set of actions permitted under the envelope.
-
Cursor (cursorRoot): a 32-byte commitment to the current aggregate state maintained by the substrate-specific registry.
-
Expiry and status: lifecycle metadata.
An envelope is identified on-chain by the pair (registry, id). For off-chain or cross-chain references, the tuple (chainId, registry, id) SHOULD be used.
Envelope Reference
An envelope reference is a conceptual interoperability type. It need not be stored inside each envelope:
struct EnvelopeRef {
uint256 chainId;
address registry;
bytes32 id;
}Within a same-chain contract integration, the pair (registry, id) is sufficient because the chain is already known from execution context.
Substrate
A substrate is the contract or contract system that maintains the state committed to by an envelope cursor and validates cursor advances. Different substrates may maintain different invariants.
-
A budget substrate may track aggregate spend and reject an advance that exceeds a committed bound.
-
An identity substrate may bind actions to an attested identity without trusting self-reported claims.
-
A reputation substrate may update a score based on completed or failed actions.
-
A privacy substrate may maintain a nullifier set or commitment tree.
-
A contestation substrate may track challenges and bonded resolution.
This ERC does not define substrate internals. Implementations may choose any storage model, proof system, or invariant model, provided they conform to the interface and semantics below.
Registry
A registry is a substrate-specific contract that stores envelopes and implements IBoundedAgentAction. Each conforming registry validates cursor advances according to one substrate implementation or substrate family.
The registry address identifies both the envelope namespace and the implementation responsible for validating cursor transitions. Envelope identifiers are therefore unique only within that registry.
Upstream callers register envelopes with the registry, and downstream consumers query that same registry using (registry, id). A generic registry that routes envelopes to multiple independent substrate implementations is outside the scope of this standard and MAY be defined by a separate extension.
Terminology (non-normative)
The term envelope here denotes the on-chain, persistent, stateful record defined by this standard. It is unrelated to other uses of the same word in concurrent proposals, including off-chain carriers of prepared or encrypted transactions and per-message serialization or attestation wrappers. Those carry a message to a signer or a payload to a verifier; they neither persist agent state nor constrain a principal's later actions. Implementers integrating across these standards SHOULD treat the names as unrelated.
Two terms carry the weight in this standard: cursor, the advancing aggregate-state commitment, and substrate, the implementation that maintains and validates it. These identify the primitive more precisely than envelope and are used consistently throughout.
Specification
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174.
1. Interface
A conforming implementation MUST implement the IBoundedAgentAction interface:
// SPDX-License-Identifier: CC0-1.0
pragma solidity ^0.8.24;
interface IBoundedAgentAction is IERC165 {
enum Status {
None, // 0: nonexistent / not registered
Active,
Completed,
Contested,
Revoked,
Expired
}
struct Envelope {
bytes32 id;
address principal;
bytes32 capabilityRoot;
bytes32 cursorRoot;
uint64 createdAt;
uint64 expiresAt;
Status status;
}
event EnvelopeRegistered(
bytes32 indexed id,
address indexed principal,
bytes32 indexed capabilityRoot
);
event EnvelopeAdvanced(
bytes32 indexed id,
bytes32 prevCursor,
bytes32 newCursor
);
event EnvelopeStatusChanged(
bytes32 indexed id,
Status fromStatus,
Status toStatus
);
function registerEnvelope(
address principal,
bytes32 capabilityRoot,
uint64 expiresAt,
bytes calldata initData
) external returns (bytes32 id);
function getEnvelope(bytes32 id)
external view returns (Envelope memory);
function getCursor(bytes32 id)
external view returns (bytes32);
function getStatus(bytes32 id) external view returns (Status);
function isActive(bytes32 id) external view returns (bool);
function advanceCursor(
bytes32 id,
bytes calldata witness
) external returns (bytes32 newCursor);
function setStatus(bytes32 id, Status newStatus) external;
}2. Semantics
2.1 registerEnvelope
-
The envelope identifier id MUST be unique within the registry and MUST NOT collide with the id of any prior envelope, including envelopes in terminal states. Implementations SHOULD derive id deterministically, for example id = keccak256(abi.encode(registry, principal, capabilityRoot, salt)), and SHOULD expose a view to precompute id before registration so the reference can be embedded upstream.
-
Implementations MUST emit EnvelopeRegistered(id, principal, capabilityRoot) upon successful registration.
-
The initial status of a newly registered envelope MUST be Active.
-
The initial cursorRoot is implementation-defined and is typically derived from initData. A conforming registry MUST document the encoding and semantics of initData, and the initial cursorRoot MUST be readable via getCursor immediately after registration.
-
expiresAt MUST be greater than block.timestamp at registration, unless expiresAt is zero.
-
An expiresAt value of zero indicates that the envelope does not expire automatically.
-
Implementations MAY enforce additional preconditions, including capability validity and implementation-specific initialization checks. If principal is not the caller, the implementation MUST verify that the principal authorized the registration, for example via an EIP-712 signature, an ERC-1271 check, or an upstream delegation or permission grant, and MUST revert otherwise. A consumer relies on the principal field denoting a party that consented.
-
If a precondition fails, the implementation MUST revert.
2.2 getEnvelope / getCursor
-
These functions MUST be free of side effects.
-
They MUST return the current stored state of the envelope at the time of call. getCursor(id) MUST equal getEnvelope(id).cursorRoot in the same block. getStatus(id) MUST return the effective status, and isActive(id) MUST return true only when the envelope is Active and, where expiresAt is nonzero, block.timestamp is less than expiresAt; both MUST fold expiry into their result so that a stored Active status on an expired envelope cannot mislead a consumer.
-
If the envelope id does not exist, implementations MUST revert rather than return a zero-initialized value.
2.3 advanceCursor
-
Cursor advances MUST be atomic with the substrate state changes they represent.
-
Implementations MUST reject advanceCursor when the envelope's stored status is not Active.
-
Implementations MUST also reject advanceCursor when expiresAt != 0 and block.timestamp >= expiresAt, even if the stored status remains Active.
-
Implementations MUST emit EnvelopeAdvanced(id, prevCursor, newCursor) upon successful advancement.
-
The witness parameter is opaque to this standard. The implementation defines its structure and MUST validate it before changing any state. A conforming registry MUST publish the ABI of its witness. The witness MUST bind to at least the envelope id and a value that uniquely identifies the cursor state or the individual draw, for example prevCursor or a per-draw nonce, so that it cannot be replayed across envelopes or across cursor states.
-
A successful advanceCursor MUST be authorized: an implementation MUST bind advancement to a validated witness, a pre-authorized consumer, or a substrate-approved transition, and MUST NOT advance the cursor on behalf of an arbitrary caller. Because an advance consumes headroom against the bound, an implementation that permits unauthorized advancement would let any party exhaust a principal's mandate without performing an authorized action. The standard does not prescribe the authorization mechanism, only that advancement is never open to arbitrary callers.
2.4 setStatus
-
Status transitions MUST follow the state machine in Section 2.5.
-
Implementations MUST emit EnvelopeStatusChanged(id, fromStatus, toStatus) upon a successful transition.
-
Authorization for each transition is implementation-defined and MUST be documented. At minimum, a transition from Active to Revoked MUST be restricted to the principal or a controller the principal authorized, the same restriction applies to a transition from Contested to Revoked, and an implementation MUST NOT permit the metered party to transition an envelope out of Active or Contested in order to escape an unmet bound. Escape here means reaching a state in which an obligation the envelope meters is treated as concluded, for example Completed or Expired; a post-deadline resolution to Active under Section 2.5 is not an escape, since it returns the envelope to service under the same bound. Because the authorization for Contested to Revoked is implementation-defined, a registry MAY give the principal that route through setStatus without making the principal a resolver.
-
A transition from Active to Expired MUST only succeed when expiresAt != 0 and block.timestamp >= expiresAt.
-
Any caller MAY initiate a valid transition from Active to Expired after the expiry timestamp has been reached.
-
An implementation is not required to update stored status automatically as time passes.
-
Consumers MUST treat an envelope as inactive when expiresAt != 0 and block.timestamp >= expiresAt, regardless of its stored status.
2.5 Status State Machine
The following transitions are valid. All other transitions MUST be rejected:
Active -> Completed
Active -> Contested
Active -> Revoked
Active -> Expired
Contested -> Active
Contested -> Revoked
Completed, Revoked, and Expired are terminal states. Implementations MUST NOT permit transitions out of terminal states.
Contested is operationally suspensive, not merely descriptive. Because Section 2.3 rejects advanceCursor on any stored status other than Active, entering Contested suspends cursor advancement until the envelope is resolved, and isActive returns false for its duration. An implementation MUST NOT overload lifecycle status with an informational review signal: under Section 2.3 any stored status other than Active suspends advancement, so a signal meant to inform a consumer rather than to halt the envelope MUST be carried outside status, where it changes neither isActive nor advanceCursor.
The Contested state and its transitions are provided by an optional extension, IContestableEnvelope, discoverable via ERC-165. A base registry preserves the Contested enum value for vocabulary uniformity but is not required to support entering or leaving it. A registry that supports contestation MUST implement IContestableEnvelope and MUST preserve the Contested transitions defined above.
The optional extension interface is defined as:
interface IContestableEnvelope is IBoundedAgentAction {
event EnvelopeContested(bytes32 indexed id, address indexed challenger);
event EnvelopeResolved(bytes32 indexed id, Status outcome);
// Active -> Contested. Authorization is implementation-defined,
// for example a bonded challenger. MUST revert unless status is Active.
function contest(bytes32 id, bytes calldata evidence) external;
// Contested -> Active or Revoked. MUST be restricted to a documented
// resolver until resolutionDeadline has passed, after which any caller
// MAY resolve, with outcome restricted to Active. MUST revert unless
// status is Contested and outcome is Active or Revoked.
function resolve(bytes32 id, Status outcome, bytes calldata resolution) external;
// The instant from which any caller MAY resolve. MUST be set when contest
// succeeds and MUST NOT be extended while that contest is pending; a
// subsequent contest sets a fresh deadline. MUST revert unless status is
// Contested.
function resolutionDeadline(bytes32 id) external view returns (uint64);
}contest and resolve MUST also emit the base EnvelopeStatusChanged event, so that a consumer tracking only the base lifecycle observes the transition. A transition out of Contested through setStatus emits only EnvelopeStatusChanged; a consumer that needs every exit from Contested MUST track the base event, not EnvelopeResolved alone.
An envelope whose expiresAt is zero has no clock of its own, so a resolver that never acts leaves it suspended indefinitely and a challenger, rather than the principal, decides how long the bound stays frozen. An implementation MUST therefore set a resolution deadline when contest succeeds, MUST NOT extend it while that contest is pending, and MUST permit any caller to resolve the envelope once it has passed. The outcome available to that caller MUST be Active, because the stall is the challenger's: an unresolved contest returns the envelope to service rather than terminating it. The deadline MUST equal the contest time plus a resolution window that is fixed and documented before any contest is raised; a window so long that the exit is never reachable in practice provides no exit, and a consumer SHOULD treat it as absent. Contested to Expired remains invalid. Expired is terminal, so an any-caller edge into it would let a metered party contest its own envelope and run out the clock to foreclose the verdict. A subsequent contest on the same envelope sets a fresh deadline; repeated contests can therefore suspend an envelope serially, and the contest authorization, for example an escalating bond, is the control on that repetition.
Where an implementation models an advance as a reservation, in the sense of the Reservation Binding consideration, an IContestableEnvelope implementation MUST define what becomes of reservations already open when the envelope enters Contested: whether each may still be confirmed, may still be cancelled, or is locked until resolution. Section 2.3 blocks new advances while the envelope is suspended, but an open reservation already holds headroom down, so this answer determines the pooled headroom other venues read and cannot be left implicit. Cancellation SHOULD remain available to the reserver, since it only returns headroom and Section 2.3 blocks the redraw while the envelope is suspended.
2.6 ERC-165 Support
-
supportsInterface(type(IBoundedAgentAction).interfaceId) MUST return true.
-
supportsInterface(type(IERC165).interfaceId)MUST return true. -
supportsInterface(0xffffffff) MUST return false.
-
Implementations MAY return true for additional substrate-specific extension interfaces. Conformance to the base interface alone carries no guarantee of semantic interoperability; because the cursor is opaque, a consumer MUST confirm a named profile, for example via supportsInterface for IBudgetSubstrate, before interpreting a cursor it did not define.
The four-byte ERC-165 identifiers, computed from the reference implementation, are 0x3985961d for IBoundedAgentAction, 0x021ca455 for IBudgetSubstrate, 0xd79116b5 for IContestableEnvelope, and 0xc7cabe86 for IAggregateBudget (Section 5). These MUST be regenerated if any function signature changes.
3. Composability with Existing Standards
These examples are non-normative integration patterns. Integrations SHOULD carry the registry address together with the envelope id, and SHOULD use (chainId, registry, id) when the reference may leave the current chain context.
The standards below span two tiers. Sections 3.1, 3.2, 3.4, 3.5, 3.8, 3.9, and 3.13 compose with established or Final standards. Sections 3.3, 3.6, 3.7, and 3.10 through 3.12 compose with concurrent agent-stack drafts, including ERC-8004 and ERC-8183; drafts not yet merged into this repository are referenced by function rather than number, and their interfaces reflect their state as of 2026.
3.1 Composition with ERC-7710
An ERC-7710 caveat enforcer MAY carry an envelope reference in its terms. The enforcer reads getCursor(id) and calls advanceCursor in its afterHook, so the cursor advances only when the delegated execution actually occurs, since a caveat enforcer cannot assume execution happened in its beforeHook. The reference belongs in the caveat terms, not in the DelegationManager permissionContext, which carries the signed delegation chain.
3.2 Composition with ERC-7715
A wallet MAY return an envelope reference as opaque permission context. A dApp MAY query the referenced registry before exercising the permission.
3.3 Composition with batched-operation flows
A fetcher or batch step MAY query getCursor(id) for aggregate state used across a composed execution flow. Any cursor advancement intended to bind the whole flow SHOULD occur atomically with settlement.
3.4 Composition with ERC-7579
A modular smart account MAY install a module that queries or advances an envelope during execution. The module acts as a consumer of the substrate-specific registry rather than defining its own cursor semantics.
3.5 Composition with ERC-4337
A UserOperation or paymaster flow MAY carry an envelope reference for aggregate budget gating. The paymaster MAY reject the operation when the referenced envelope is inactive, expired, or inconsistent with the requested action. A paymaster that reads an external registry cursor during validation MUST be staked, since ERC-7562 restricts unstaked entities from accessing storage outside their own and account-associated slots; implementations that cannot stake SHOULD perform the cursor check after execution rather than during validation.
3.6 Composition with ERC-8004
An ERC-8004 registration or validation request MAY include an envelope reference to associate agent identity or validation activity with substrate-maintained state. Off-chain metadata SHOULD use the full (chainId, registry, id) tuple.
3.7 Composition with ERC-8183
A job or hook MAY reference an envelope whose lifecycle or cursor records state associated with the job. A contestation-aware substrate MAY use the Contested status in an integration-specific dispute flow. This ERC does not define arbitration or modify ERC-8183's lifecycle.
3.8 Composition with x402
An x402 integration MAY include an envelope reference in an implementation-defined HTTP field to associate a payment request with substrate-maintained state, such as an aggregate budget. The encoding and the atomic relationship between payment settlement and cursor advancement are outside the scope of this standard.
3.9 Composition with ERC-7521
An intent-based flow MAY include an envelope reference as bounded-action context. Solvers and settlement contracts MAY query the referenced registry before execution and advance its cursor as part of an atomic settlement flow.
3.10 Composition with inference verification
A consumer that verifies a single agent action through an inference-proof-verification interface MAY use that verification result as the witness, or as input to the witness, for advanceCursor. The verifier establishes that one action is valid; the cursor records that action against the agent's aggregate bound. Verification is per action; the cursor is the aggregate across actions.
3.11 Composition with service discovery and escrow
A service-discovery and escrow standard settles, and where necessary disputes, an individual job. A bounded agent action MAY bound the aggregate value an agent commits across many such jobs, with each job referencing the envelope. Where such a standard defines its own resolution, the Contested status is used only for an integration-specific lane and does not replace that arbitration. This ERC does not define arbitration.
3.12 Composition with workflow orchestration
A workflow-orchestration contract MAY consult an envelope at each step gate. The orchestrator enforces that a task progresses through its stages; an envelope enforces that the cumulative authority those stages consume stays within a committed bound. The gate reads getCursor(id) and calls advanceCursor within the same step, so orchestration and the across-step bound are enforced together.
3.13 Composition with ERC-8001
Where a coordination standard such as ERC-8001 carries an agreed bound as a term of a multi-party agreement, an envelope carries the headroom that bound leaves at any moment. The agreement records what was accepted; the cursor records what remains. A consumer MAY register an envelope whose capabilityRoot commits to the agreed bound and whose cursor meters consumption against it, so enforcement reads live headroom rather than re-deriving it from the agreement at each action. The agreement gives the cursor the authority it measures against and nothing beyond it: the capabilityRoot binding fixes which accepted mandate a cursor is metered against, and it does not ask the agreement to trust the cursor as an oracle. Because cumulative consumption against that mandate is recomputable from the public record of advances (see Security Considerations, Reservation Binding), any party confirms the bound held from contract state alone, without trusting the agent, the provider, or the cursor to report it.
4. Budget Substrate Profile
This section specifies one concrete substrate so that the interface is not vacuous. It is normative for any registry that advertises the budget profile and informative for any other registry. A registry MAY conform to the base interface without implementing this profile.
Under the budget profile, capabilityRoot commits to the bound: capabilityRoot = keccak256(abi.encode(uint256 cap, address asset)), where cap is the maximum value the agent may consume of asset under this envelope.
cursorRoot commits to a single non-negative integer, spent, the cumulative value consumed: cursorRoot = keccak256(abi.encode(uint256 spent)). At registration spent is zero, so the initial cursorRoot equals keccak256(abi.encode(uint256(0))).
The witness is abi.encode(uint256 amount, bytes authorization). advanceCursor MUST reject the advance unless spent + amount <= cap, MUST set spent to spent + amount, and MUST recompute cursorRoot. The authorization field is the evidence that binds the spend to a settled action and MUST commit to the replay-binding required by Section 2.3.
A budget-profile registry MUST maintain spent <= cap while the envelope is Active, and spent MUST be monotonically non-decreasing. These two properties are the interoperable guarantee a consumer of this profile relies on: a getCursor read under this profile denotes remaining headroom equal to cap minus spent, with the same meaning in every conforming registry.
A registry advertises the profile via the ERC-165 interface identifier of IBudgetSubstrate, or an equivalent published identifier, so that a consumer can confirm the cursor semantics before relying on them.
The budget profile is exposed through the following extension interface, so a consumer reads remaining headroom directly rather than interpreting the opaque cursor:
interface IBudgetSubstrate is IBoundedAgentAction {
// Configured bound: maximum cap of asset consumable under the envelope.
function bound(bytes32 id) external view returns (uint256 cap, address asset);
// Cumulative value consumed under the envelope.
function spent(bytes32 id) external view returns (uint256);
// Remaining headroom (cap - spent), or 0 if the envelope is not active.
function remaining(bytes32 id) external view returns (uint256);
}remaining(id) MUST equal cap minus spent for an Active envelope and MUST be consistent with the committed cursor: keccak256(abi.encode(cap - remaining(id))) MUST equal getCursor(id) while Active, so the typed accessor and the commitment cannot diverge. bound, spent, and remaining MUST revert on an unknown id, as the base reads do. A remaining value of zero is not self-disambiguating, since it denotes either an exhausted bound or an inactive envelope; a consumer MUST consult isActive to tell them apart.
Granularity beyond the flat profile (informative). This profile is intentionally flat: one cap, one asset, one spent value, one principal. It is the minimal instance, not the ceiling. Because capabilityRoot and cursorRoot commit to structure rather than to a scalar, and advanceCursor takes an opaque witness, a richer profile can express per-account or per-scope sub-limits without changing the base interface: a tree of sub-budgets committed in capabilityRoot, per-leaf consumption committed in cursorRoot, and a witness identifying the leaf an advance draws from. Such a profile carries two things this one does not, which leaf an advance belongs to and what authorizes it; both come from composition rather than the cursor, with identity and agreement scoping the leaves and a verification result supplying the per-leaf witness (Section 3). This document specifies only the flat profile; a hierarchical sub-budget profile is left to a follow-on specification. The Aggregate Budget Profile of Section 5 is not that follow-on: it conserves one sum across a delegation tree and reserves nothing per subtree.
5. Aggregate Budget Profile
The budget profile of Section 4 meters one envelope's consumption. This section specifies an optional companion profile that meters the aggregate consumption of a delegation tree: an agent delegates to sub-agents, each acting under its own key and free to re-delegate further, and the sum of admitted draws across every node in a period stays within a single root cap. The section is normative for any registry that advertises the profile.
A delegation tree is not an envelope, so IAggregateBudget does not extend IBoundedAgentAction. An implementation MAY expose both interfaces. The base interface and the Section 4 profile are unchanged by this section.
interface IAggregateBudget is IERC165 {
event RootCreated(bytes32 indexed rootId, address indexed issuer, address indexed agent, uint256 cap, uint64 periodLength);
event NodeDelegated(bytes32 indexed rootId, uint64 indexed parentId, uint64 indexed nodeId, address agent, uint256 nodeCap);
event Drawn(bytes32 indexed rootId, uint64 indexed nodeId, uint64 indexed periodIndex, uint256 amount);
event NodeRevoked(bytes32 indexed rootId, uint64 indexed nodeId);
// Create a tree whose nodes share a conserved budget of `cap` per period.
// `agent` is node 0; the caller is the issuer. `periodLength` of 0 denotes
// a single non-resetting period.
function createRoot(address agent, uint256 cap, uint64 periodLength, uint64 periodAnchor, bytes32 salt) external returns (bytes32 rootId);
// Delegate a child node under `parentId`. The child shares the root meter
// and never receives a fresh budget. `nodeCap` (0 = none) attenuates the
// child's own draws.
function delegate(bytes32 rootId, uint64 parentId, address agent, uint256 nodeCap) external returns (uint64 nodeId);
// Meter a spend of `amount` by `nodeId` against the root meter.
function draw(bytes32 rootId, uint64 nodeId, uint256 amount) external;
// Revoke a node and, transitively, every draw path through its subtree.
function revoke(bytes32 rootId, uint64 nodeId) external;
function rootOf(bytes32 rootId) external view returns (address issuer, uint256 cap, uint64 periodLength, uint64 periodAnchor, uint64 nodeCount);
function nodeOf(bytes32 rootId, uint64 nodeId) external view returns (uint64 parent, uint8 depth, bool revoked, address agent, uint256 nodeCap);
// The conserved meter: realized aggregate spend of the whole tree in a period.
function spentRoot(bytes32 rootId, uint64 periodIndex) external view returns (uint256);
function remainingRoot(bytes32 rootId) external view returns (uint256);
function currentPeriod(bytes32 rootId) external view returns (uint64);
// True iff every node from `nodeId` up to the root is unrevoked.
function isPathActive(bytes32 rootId, uint64 nodeId) external view returns (bool);
}5.1 Conservation
For every root and every period, a conforming implementation MUST keep the sum of amount over all admitted draw calls at or below cap. The property MUST hold regardless of fan-out, depth, or re-delegation.
The meter MUST be keyed on the root, not on the delegation edge: spentRoot(rootId, periodIndex) is the sum over the whole tree, and every draw on every path advances it. Edge-keyed accounting, in which each grant or re-delegation carries a fresh counter, does not satisfy this section. Sibling counters do not observe each other, so a root that opens k sibling paths capped at B admits an aggregate of k times B while every per-edge check passes. Such an implementation is non-conformant to this profile even where each edge enforces its own limit correctly.
An implementation MUST NOT expose an owner, upgrade, or administrative authority able to reset, decrement, or replace the root meter. A register behind such an authority is conserved only until the authority is exercised: resetting the meter between draw paths admits the same amplification inside a nominally root-keyed contract. The prohibition covers an upgradeable proxy whose administrator can replace the meter's code, not only an explicit reset function.
The period index MUST be a stored key of the meter rather than a value derived at read time, and cap, periodLength, and periodAnchor MUST NOT change after a root is created. Period addressing is part of the meter: an implementation able to re-anchor or re-length a period directs a draw at a different register without resetting any counter, reaching the amplification the preceding paragraph forbids by another route. The same immutability is what makes the property auditable. Conformance is asserted against a (root, period) pair, with the block chosen only to observe it, so that two verifiers naming the same period agree once that period has closed.
Finer time windows layered over the tree, per-node or per-leaf epochs, are attribution, not addressing. Every draw admitted while a root period is current advances spentRoot(rootId, periodIndex) for that period, however local windows tile across it. A meter keyed to a window that resets on its own schedule readmits the amplification through the clock: a node exhausting a window-local allowance in each of several windows inside one root period passes every window-local check while the root sum exceeds cap. The conservation key is the root period. A local window MAY bound a node's own draws, as nodeCap does, and MUST NOT carry the conserved sum.
Attenuation caps are allocations, not carriers. nodeCap bounds the draws attributed to one node and holds no conserved quantity of its own; no second conserved carrier appears anywhere in the tree. Where every capped node is a leaf and the leaf caps sum to at most cap, each leaf holds a guaranteed allocation of the root budget, and where the caps sum above cap they overbook by explicit choice, with the root register remaining the only bound that cannot be exceeded. The two inequalities, leaf caps summing to at most cap at issuance and each node's spend staying within its own cap at admission, factor the single root-keyed invariant through the allocation tree; they never replace it.
5.2 Draws, delegation, and revocation
-
draw MUST revert unless the caller is the node's agent, every node on the path from the node to the root is unrevoked, the node's attenuation cap is respected where nodeCap is nonzero, and the advance keeps spentRoot within cap.
-
delegate MUST be restricted to the parent node's agent and MUST revert when the parent's path to the root is revoked.
-
A node with a nonzero nodeCap MUST NOT delegate.
-
revoke MUST be restricted to the tree's issuer or the revoked node's parent agent, and MUST NOT decrement the root meter. Realized spend stays realized; revocation shrinks future headroom and never restores it.
-
nodeCap attenuates the node's own draws only. It does not reserve budget for the node's subtree, and an implementation MUST NOT present it as a subtree cap: an unattenuated node elsewhere in the tree may draw the entire remaining headroom.
5.3 Scope
The conservation property is a safety property, enforced by the serialized check-then-increment of one register on a single chain. Aggregating one live root cap across chains is outside this profile, per the Cross-chain Composition consideration. Draws are final within a period; a reservation extension in the sense of the Reservation Binding consideration is likewise outside this profile. Non-bypassability remains a substrate obligation: the meter holds the sum of the draws routed through it, and a deployment in which value can move without a draw does not obtain the conservation property for that value.
5.4 Discovery
supportsInterface(type(IAggregateBudget).interfaceId) MUST return true for a conforming implementation. The four-byte identifier appears in Section 2.6.
Rationale
Why Opaque Cursor Semantics
cursorRoot is bytes32 so implementations may use a Merkle root, zero-knowledge commitment, hash chain, counter commitment, or another fixed-size representation. The standard defines how the cursor is registered, queried, and advanced without prescribing its internal structure.
Why Separate capabilityRoot and cursorRoot
capabilityRoot is fixed at registration and commits to the bounded set of authorized actions. cursorRoot changes over time and commits to running aggregate state. Keeping them separate allows authorization consumers to rely on a stable commitment while policy consumers read live state.
Why Opaque Witness Data
witness is bytes calldata interpreted entirely by the substrate-specific registry. This permits zero-knowledge proofs, TEE attestations, signatures, Merkle proofs, or other validation mechanisms without coupling the base interface to one proof system.
Why No Witness Type Discriminator
The registry receiving advanceCursor already defines the witness format. A witnessType field would add interface surface without enabling a consumer to construct witnesses for an unknown implementation.
Why a First-class Status State Machine
A shared status vocabulary enables consumers to reason consistently about active, completed, contested, revoked, and expired envelopes. All conforming implementations preserve the Contested enum value so that the vocabulary is stable across registries. Supporting entry into and exit from that state is optional and is provided by IContestableEnvelope.
Why Separate getEnvelope and getCursor
Many consumers need only a single 32-byte cursor, while wallets and audit consumers need lifecycle metadata. The split supports narrower reads without changing the underlying semantics.
Why No Internal State Model
Implementations may use mappings, Merkle structures, off-chain state commitments, or rollup state. Conformance depends on the seven functions, three events, and lifecycle semantics rather than a particular storage design.
Why a Narrow Interface
The interface defines seven functions and three events. It deliberately omits capability issuance, intent submission, execution routing, and auditing. Those concerns belong to upstream or downstream systems; this standard defines only the docking surface.
Why a Written Cursor, Not a Recomputed Predicate
An aggregate bound can be expressed two ways: as a predicate recomputed from public state at each action, or as a counter the action advances. They are not equivalent.
A recomputed predicate reads public state and checks a condition. Within a single settlement surface this is sufficient: on-chain execution serializes, so a predicate that reads and writes in the same atomic frame sees every prior committed action. The difficulty is cross-surface. When an agent acts across protocols, no single contract holds the aggregate, and no action-time check on one surface can reconstruct what was spent on another. There is nothing to recompute the bound from.
advanceCursor is a write. It records consumption against one object that every surface advances, so the aggregate exists where no single protocol maintains it. A recomputed predicate is sufficient for a single settlement surface; a bound that must hold across surfaces requires a written, shared cursor.
This boundary distinguishes the cursor from the other invariants in a composed agent flow. Input provenance, the binding of an action to its task, output fidelity, and precedence are each read-recomputable, re-derived from public bytes after the fact. The aggregate cap is the one invariant a read cannot establish: it is a sum across steps, and a check-time read sees an incomplete surface, since a sibling action has committed nothing the read can observe. Verifying a value is a read; holding a sum across siblings that have not committed yet is not.
The write supplies the missing order. Because advanceCursor records against one object, the next advance reads the prior committed advance, so two concurrent draws cannot both pass a bound their sum would exceed. That serialization is what a recomputed predicate cannot reproduce across surfaces. The public record of advances then keeps the result auditable: any party recomputes whether cumulative consumption ever exceeded the cap from the advance transactions and their witnesses, decoded with the published witness ABI, with no trust in the registry.
Why a Pre-action Gate
The advance belongs before the action that draws, not folded into it and not deferred to settlement. A drawing step calls the advance before it spends: the advance reserves against the bound, reverts if the draw would cross it, and is confirmed or cancelled when the action settles or reverts. Settlement is too late, since it records an outcome the bound was meant to gate before it occurred; settlement tallies, it does not gate. Folding the check into the executing step couples the bound to whatever that step computes, while the bound is orthogonal to it, and the gate has to meter the agreed terms the envelope commits to rather than an amount the step supplies. A dedicated pre-action gate keeps the bound independent of any single step. It is a constraint checked before execution, in the block-in-line class, distinct from a post-outcome check that records or penalizes after the fact.
Why the Aggregate Profile Is Not an Envelope Extension
An envelope records one bounded course of action under one principal, and its cursor is that envelope's own aggregate. A delegation tree is a different object: many keys, one conserved sum, no single course of action. Extending IBoundedAgentAction would force tree state through single-envelope reads, with getCursor answering for an object the Envelope struct does not describe. A separate interface keeps each read surface truthful. An implementation that is both an envelope registry and a tree meter advertises both identifiers.
Why a Root-Keyed Meter
Edge-keyed limits are the native form of existing delegation primitives: each grant carries its own cap, and re-delegation attenuates it. Along a single chain of delegation that form is sound. It fails at fan-out, because sibling grants are mutually invisible: a root that issues k grants of B has issued k times B, and no individual check ever fails. The aggregate across a tree exists only where every draw advances one shared register. Keying that register on the root makes the sum well-defined, and forbidding administrative authority over it makes the sum binding. Hierarchical rate limiters and permission-boundary systems maintain the same invariant under an enforcer the delegator does not control; in a delegation tree the only candidate enforcer is the root itself, so the register has to be state no tree key can rewrite.
Why an Attenuated Node Cannot Delegate
A capped node that could delegate would escape its cap by minting an uncapped child and drawing through it. Restricting capped nodes to leaves makes nodeCap a hard per-counterparty bound. It also gives per-leaf allocation a clean discipline: leaves whose caps sum to no more than the root cap each hold a guaranteed allocation, while caps summing above it overbook by explicit choice.
Relationship to Adjacent and Prior Standards (non-normative)
Several existing and concurrent standards address parts of this problem space. This subsection records how a bounded agent action relates to the closest of them. References to draft standards reflect their state as of 2026 and may be renumbered.
Authorization versus metering. A family of authorization primitives expresses who may act, independent of execution: ERC-7710 structures delegated authority and its redemption; ERC-7715 requests scoped permission from a wallet; concurrent authorization-object drafts define portable, revocable EIP-712 consent with an opaque scope and no execution semantics, and a further draft adds usage-limited delegation with automatic revocation to the same family. These answer the question "may this party act?" A bounded agent action answers a different question: "how much of a bounded mandate has been consumed across all actions so far, and does the next action remain within it?" Authorization is a precondition checked per grant; the cursor is aggregate state metered across calls. The two compose: an authorization object or delegation MAY be the upstream grant that registers an envelope, after which the cursor meters the activity that grant bounded. Even where such a primitive carries a limit, the limit is scoped to one relationship: an ERC-4337 account sequences its own operations, an ERC-7710 caveat bounds one delegation. Neither holds a single cap across a principal's many delegations and venues, which is the aggregate the cursor maintains.
Per-call validation versus persistent state. A concurrent universal-policy-engine draft composes reusable rule artifacts as method-level pre- and post-hooks that gate individual calls. A bounded agent action does not gate a single call; it persists a commitment that later calls consult and advance. A policy engine MAY invoke a registry as one of its checks, but the object defined here is the state that survives the call, not the hook that runs on it.
Owner-binding accumulators. A concurrent coercion-resistant-vault draft maintains an on-chain spend accumulator that constrains the account owner's own key across transactions, the property a budget substrate enforces. It does so as a single monolithic account with the limit fixed as an epoch rate, exposing no queryable cursor for arbitrary consumers and no lifecycle beyond the wallet's own. A bounded agent action generalizes the property into a substrate-neutral interface: the accumulator is one possible cursor, exposed for any consumer to read, advanced through an opaque witness, with a defined lifecycle including contestation.
Aggregate trust state. A concurrent trust-infrastructure draft maintains aggregate cross-call agent state and uses overlapping vocabulary, including bounded action, accumulator, and commitment. Its state is a third-party trust signal that downstream protocols consult at their discretion. A bounded agent action is the principal's own self-imposed bound; where a substrate binds it, the principal's native key path cannot exceed it. The distinction is consultative assessment versus non-bypassable self-binding established by a substrate.
Invariant-after-every-transition. A concurrent invariant-first reserve-receipt draft enforces a persistent accounting invariant checked after every state change. The enforcement shape is similar, but the subject is the solvency of a token's reserves, not an agent's mandate, and there is no principal, capability commitment, expiry, or status. It is prior art for the pattern, not the object.
Policy-bound wallets. ERC-8196 (AI Agent Authenticated Wallet) binds an agent's transactions to an owner-registered policy that the wallet checks before it executes, carrying a required per-transaction value ceiling and an optional daily one. That policy is scoped to one agent and one owner inside a single wallet, and its accessor returns the per-transaction ceiling without any consumption to date, so the ceiling is checked where the transaction executes and is not exposed as state another party can read. A bounded agent action addresses the complementary case: consumption accumulated across actions, held in a registry any consumer may query and recompute, and pooled across the mandates and venues a single principal delegates. The two compose in the direction the wallet already faces, since a policy check MAY consult an envelope before executing, leaving the wallet to enforce the per-action ceiling while the cursor carries the aggregate.
Regulated mandates. ERC-8226 (Regulated Agent Mandate) is a compliance delegation layer for AI agents acting on ERC-7943 regulated assets. Its cumulative value cap advances atomically with the ERC-7943 transfer, so a reverted transfer reverts its own advance, and the counter is governed together with freeze and compliance controls in one contract, capping each accepted mandate separately, which for regulated delegation is the design goal: each mandate is a distinct authority. The fused counter is one binding of the kind the Security Considerations describe for a substrate, enforcement and metering in a single frame, specialized to a regulated asset class. A bounded agent action addresses the complementary case: a general aggregate that may pool a principal's consumption across mandates, venues, and settlement frames, exposed behind a discoverable read surface, with non-bypassability supplied by whichever substrate binds it. Both counters are recomputable from the state root, so a consumer verifies either from contract state alone.
Relationship to the agent-service standards. A set of concurrent drafts standardizes layers of an agent stack: identity (ERC-8004), task execution and workflow orchestration, input provenance, proof anchoring, inference verification, and service discovery, escrow, and dispute resolution. These specify how an agent is identified, how a task is dispatched and advanced, how a single action is verified, and how a job is settled. None maintains the aggregate bound an agent operates under across the many actions a workflow dispatches. A bounded agent action is intended to sit alongside these as the layer that meters that aggregate, composing through the patterns in Section 3.
Backwards Compatibility
This ERC introduces a new interface and does not modify any existing standard. Existing systems may adopt it incrementally by storing or passing an envelope reference and querying the corresponding registry.
Test Cases
A conforming implementation SHOULD be tested for the following behaviors:
-
registerEnvelope creates a unique envelope with status Active and emits EnvelopeRegistered.
-
getEnvelope(id).cursorRoot equals getCursor(id).
-
getStatus(id) and isActive(id) reflect Expired once expiresAt is reached, even while the stored status remains Active.
-
A valid advanceCursor call updates the cursor and emits EnvelopeAdvanced.
-
Under the Budget Substrate Profile, the cap-conservation verdict, whether cumulative consumption ever exceeded the cap, is recomputable by any party from the public record of advances, yielding one determinate result per envelope. Because EnvelopeAdvanced carries commitments rather than values, the recomputation reads the witness of each advance, decoded with the witness ABI that Section 2.3 requires a registry to publish, and uses the cursor chain to bind each decoded advance to the state it committed.
-
The same verdict is recomputable from the registry's stored state under a block state root, without calling a view function, and the two derivations agree.
-
Where an implementation reserves in-flight consumption, a confirm or cancel submitted by a party other than the reserver reverts.
-
Where an implementation supports contestation, resolve from a non-resolver reverts before the resolution deadline; after it, resolve from any caller succeeds with outcome Active and reverts with outcome Revoked.
-
Where a witness commits to the consumption amount or to a chain identifier, it is rejected when presented with a different amount or a different chain identifier.
-
Under the Aggregate Budget Profile, across randomized sequences of delegations, draws, and revocations, spentRoot never exceeds cap in any period, and a draw that would cross the cap reverts.
-
Under the Aggregate Budget Profile, revoke does not decrement spentRoot, a node with a nonzero nodeCap cannot delegate, and a draw by a caller other than the node's agent, or through a revoked path, reverts.
-
Invalid witnesses, unknown envelope identifiers, non-Active envelopes, and expired envelopes revert without modifying state.
-
Only the status transitions defined in this specification succeed.
-
Terminal states cannot be advanced or transitioned.
-
Cursor advances are atomic with the substrate state changes they represent.
-
supportsInterface returns the values required by ERC-165.
Executable test cases SHOULD be published with the reference implementation. An implementation SHOULD also publish the storage layout of the values a cap-conservation recomputation reads, so that a third party can locate and prove the corresponding slots.
Reference Implementation
A CC0 reference implementation, EnvelopeRegistry.sol, is available in the bounded-agent-actions repository (commit 728310a). It implements the base interface and the Budget Substrate Profile over a mapping-based store with an ECDSA/EIP-712 witness, and ships an executable conformance suite that exercises registration, cursor advancement, the spent <= cap invariant, the lifecycle state machine, expiry folding, witness-replay rejection, and ERC-165 discovery. The same commit carries the aggregate and contestation profiles from which the identifiers in Section 2.6 are computed, asserted by a test so the two cannot drift.
The reference is intentionally minimal. It meters but does not enforce: it binds no assets and gates no execution path, so non-bypassability, which this standard places on the substrate, is out of scope for the reference. The conformance suite does not cover the two recomputation behaviors in Test Cases; those are properties of the record a deployed registry leaves in public state, established by a party re-deriving it rather than by the implementation's own tests.
The same repository ships AggregateBudgetCursor.sol (commit 728310a), a reference for the Aggregate Budget Profile, with its own conformance suite, unit tests, and a stateful invariant suite that holds spentRoot at or below cap across randomized trees of delegations, draws, and revocations. The unit tests include an edge-keyed counter double that realizes twice the cap through two sibling paths while every per-edge check passes; it is the non-conformance witness for Section 5.1.
Security Considerations
Atomicity
advanceCursor MUST be atomic with the substrate state changes represented by the witness. A non-atomic implementation may violate its invariant through reentrancy, partial updates, or inconsistent external calls.
Enforcement Is a Substrate Property
This interface meters; it does not enforce. A registry that claims its bound is non-bypassable MUST document the binding mechanism that forces the principal's actions through advanceCursor, for example an escrow or vault that holds the assets, or a module installed as the account's only execution path. Absent such a mechanism the cursor is advisory: a principal MAY act outside the envelope, and consumers MUST NOT treat an un-advanced cursor as evidence of remaining authority unless settlement is gated on the advance.
The ordering the cursor provides and the non-bypassability a substrate provides are distinct. The write orders the advances that pass through it, which is what holds the aggregate across surfaces (see the Rationale, Why a Written Cursor). Forcing every value-moving action to pass through an advance is the substrate's responsibility. A correct cursor holds the sum of the draws it sees; it does not by itself guarantee that every draw is one it sees.
Opacity Does Not Imply Trust
Opaque cursor and witness fields do not make an implementation trustworthy. Consumers SHOULD evaluate the registry's source code, validation mechanism, audits, and threat model before relying on its state.
Status-transition Authorization
Implementations MUST document authorization for each transition. A principal may control revocation, a substrate controller may control completion, a resolver may control contested outcomes until the resolution deadline, after which any caller may resolve to Active, and any caller may trigger expiry after the timestamp. Unauthorized transitions MUST revert.
Adversarial Substrates
A consumer's guarantees are no stronger than the registry it trusts. Consumers SHOULD identify the registry address explicitly and SHOULD avoid treating an envelope id alone as sufficient evidence of provenance.
Replay Protection
Envelope identifiers MUST be unique within a registry and MUST NOT be reused after terminal status. Reuse could cause stale references to resolve to unrelated state.
Witness Validity
Implementations MUST validate witnesses before changing the cursor or substrate state. Invalid witnesses MUST revert without side effects.
Unauthorized Advancement (Griefing)
Because an advance consumes headroom against the bound, an implementation that lets an unauthorized party advance the cursor lets that party exhaust a principal's mandate without performing any authorized action. Cursor advancement MUST therefore be bound to a validated witness, a pre-authorized consumer, or a substrate-approved transition, per Section 2.3. The witness format and the authorization mechanism are substrate-specific; the requirement is only that advancement is never open to arbitrary callers.
Reservation Binding
An implementation MAY model an advance as a reservation that is later confirmed or cancelled, so that a draw whose action ultimately reverts returns its headroom rather than consuming the bound for a spend that never settled. Where it does, confirm and cancel MUST be bound to the party that created the reservation. If any caller can confirm or cancel another party's reservation, a third party frees in-flight headroom and the public record of advances no longer recomputes to a sound cap-conservation verdict, so the audit trail becomes forgeable. The reserver binding is what makes that record recomputable, the property described in the Rationale, and is more than a griefing defense. This case is distinct from the base budget profile, whose spent is monotonically non-decreasing and which returns headroom only through the atomicity of advanceCursor with the action it meters.
Reversal Crediting
Money on deployed payment rails moves backward: refunds, reversals, chargebacks, sometimes months after settlement. An implementation that extends its meter to credit returned value, which the base budget profile's monotonically non-decreasing spent deliberately does not, takes on three obligations. A credit MUST be bounded by the realized spend of the party being credited, since an unbounded credit mints headroom that was never consumed. Once value can return, the cap binds net exposure rather than gross turnover, and an implementation MUST NOT present a reversible meter as bounding total throughput; where consumers need the un-netted figure, gross drawn SHOULD be exposed alongside net spent. Non-forgeability of a credit is a substrate obligation on the same footing as non-bypassability of a draw: authority is credited only after the substrate observes that the value has actually returned, and crediting on an assertion that a refund occurred is a headroom mint with extra steps. A crediting meter is a distinct profile: it MUST NOT advertise IBudgetSubstrate or IAggregateBudget, whose meters this document defines as non-decreasing, and is discovered by an identifier of its own.
Aggregate Meter Reset
For the Aggregate Budget Profile, the holder of any authority able to reset, decrement, or replace the root meter can realize the amplification Section 5.1 excludes: a reset between sibling draw paths turns one cap into many. The threat is not limited to an explicit reset function. An upgradeable proxy administrator, an owner role able to rewrite storage, and a migration path that re-deploys the meter each reopen it. A consumer relying on conservation SHOULD verify that the deployed meter exposes no such authority.
Attenuation Is Not Partitioning
nodeCap bounds one node's draws. It reserves nothing for a subtree, so a consumer that reads a counterparty's nodeCap as that counterparty's available budget can be starved by any unattenuated node drawing first, unless the tree follows the leaf-cap discipline described in the Rationale. A consumer MUST NOT treat nodeCap as a subtree budget.
Ordering and Backdating
Where a cursor advance establishes that one action preceded another, the ordering MUST anchor to the on-chain time at which the advance was recorded, not to a timestamp the agent supplies. An agent-supplied time is bounded only from above and can be backdated; the recording time is not chosen by the agent. Implementations that expose ordering claims MUST document which time the claim reads.
Frontrunning and MEV
Publicly submitted cursor advances may be reordered or observed before inclusion. Implementations MAY recommend private order flow or other mitigation, but no mechanism can be assumed to eliminate all ordering or trust risks.
Cross-chain Composition
A global bound MAY be partitioned across chains as independent per-chain envelopes whose caps sum to the bound. Each registry enforces its slice locally, with no cross-chain synchronization, and the global bound holds because no slice can be exceeded.
Aggregating a single live bound across chains, where unspent authority on one chain may be drawn on another, is outside the scope of this standard. It requires synchronizing shared state across chains, which means a bridge or light-client read and a trust model the single-registry design does not assume. The (chainId, registry, id) reference resolves which registry on which chain an id points at; it does not synchronize the state it points at, so it enables cross-chain reference, not cross-chain metering. A substrate that provides such synchronization MUST document its messaging trust assumptions.
Privacy
The cursorRoot, capabilityRoot, and witness fields are intentionally opaque. A substrate MAY use commitments or zero-knowledge proofs to validate an action without revealing the underlying capability set or aggregate state.
Where a substrate does not shield it, the record is transparent. Cursor advances are recorded in public transactions, the Section 4 profile exposes bound, spent, and remaining as views and carries each advance's amount in witness calldata, and the Section 5 profile exposes spentRoot and remainingRoot and emits each draw's amount, so aggregate consumption, per-advance amounts, and their timing are observable by any party. Only data absent from that surface, such as a payee or a purpose, is undisclosed by this interface; whether it appears elsewhere in witness calldata is an implementation choice. A substrate that shields any of this state replaces recomputation from the public record with verification against a proof. These are different guarantees: an implementation MUST document which one it provides, and MUST NOT describe a transparent meter as confidential.
This ERC does not define a privacy protocol and does not guarantee unlinkability. Privacy properties depend on the substrate implementation, calldata, events, registry usage, and upstream and downstream integrations.
Copyright
Copyright and related rights waived via CC0.
