ERC: Asset-Enforced Spend Mandate

Putting this up for early feedback before opening a PR.

Agents are starting to move money on their own. The safe primitive for that is the asset itself bounding what a delegate may spend, per-transaction cap, expiry, allowed token, plus an instant revoke that cuts the agent off, all enforced by the token rather than by the agent’s good behavior. Even if the agent’s key or session is fully owned, every transfer stays within the per-transaction cap and the principal can revoke to stop it, and the constraint travels with the token instead of living in a particular wallet or session key. (Bounding total spend over a period is a companion extension, noted below.) Treasury sub-accounts with a department budget, allowance wallets, and custodial accounts under a withdrawal limit are the same shape.

Tokens can already refuse a transfer: ERC-7943 and ERC-3643 put a pre-transfer check in the token and enforce it. What they answer with is a bare boolean or a custom error. So the real gap isn’t enforcement, it’s that the refusal carries no machine-readable cause, and that the policy lives inside each token instead of in one reusable place. When a transfer is blocked, the integrator needs to know why (no mandate on file, over the per-transaction cap, expired, revoked, or wrong asset) because each needs a different reaction, and today everyone infers that off-chain against a specific token’s internals. A reusable gate that many assets can share, answering with a shared machine-readable reason vocabulary, is what’s missing.

One way to place it in the existing stack:

ERC-20            what you hold
ERC-3643 / 7943   who may hold it
ERC-4337 / 7702   who may sign for it
ERC-8004          which agent is which
this proposal     what a holder may spend, enforced at the asset

ERC-8226 is the regulated, identity-bound take on that last row; this is the minimal, identity-agnostic gate (more below).

The interface is deliberately small. A transfer-eligibility gate is a separately deployable contract with two views:

interface ISpendGate {
    // true whenever the gate holds any record for `subject` (active, lapsed,
    // revoked, or no-mandate). Sticky and constant-gas.
    function isGated(address subject) external view returns (bool);

    // side-effect-free verdict. ok == (reason == OK).
    function checkTransfer(address from, address token, uint256 amount)
        external view returns (bool ok, bytes32 reason);
}

Two functions, but the signatures aren’t the proposal. What the EIP standardizes is the reserved, byte-pinned reason vocabulary (below), the asset’s enforcement obligations (when to consult the gate, when to revert with TransferBlocked, which movements are exempt, STATICCALL and fail-closed handling), ERC-165 discovery, and the IGatedAsset surface. The interoperability lives there, not in the two views.

A compliant ERC-20 exposes spendGate(), and inside its transfer path: if isGated(from), it reads checkTransfer and reverts TransferBlocked(reason) unless reason == OK. If isGated(from) is false, the transfer is plain ERC-20. Mints, address(0) burns, zero-value and self-transfers are exempt.

reason is a bytes32 short string from a reserved, byte-pinned set, evaluated in this precedence:

OK
NO_MANDATE
REVOKED
EXPIRED
TOKEN_NOT_ALLOWED
OVER_TX_CAP

Because the gate is a separate contract, one policy engine can back many assets, and because the codes are byte-pinned, independent assets and gates compare them for equality. Gates may add their own codes; every non-OK code is a denial.

What it deliberately leaves out: how a mandate is granted and who grants it (out of scope), recipient eligibility (the ERC-7943 side, layered alongside), and cumulative per-period caps (a companion extension, since they need shared accounting state).

Where it sits. ERC-7943 already mandates enforcement with a boolean canTransfer plus typed errors; this keeps that enforcement but externalizes the verdict and makes the rejection self-describing. ERC-8226 (Regulated Agent Mandate) is the closest neighbor: it bundles identity/compliance, and as I read its integration section the mandate registry enforces through the token’s existing pre-transfer hook, with the spend path surfaced as a boolean (isActiveForAmount). That hook is the seam this fills, so the two compose: an 8226-style registry could enforce through a gate like this and get a spend-reason vocabulary instead of a bare bool. Curious whether that matches how the 8226 authors see it.

I’ll post the full draft (full interface incl. IGatedAsset, reserved codes with exact byte values, asset obligations, STATICCALL/fail-closed rules, security considerations) alongside the PR. Wanted to surface the shape here first.

Keen on feedback on: the reason vocabulary and its precedence, and the gate-as-separate-contract seam.

1 Like

Hello Vlad, agreed! This is probably the closest neighboring proposal, and I think your framing is right.

Most of what you describe, such as per-transaction caps, expiry, allowed assets, instant revoke, and enforcement through the token’s pre-transfer hook, is already part of ERC-8226’s core model. ERC-8226 also covers the part your proposal intentionally leaves out: how a mandate is granted and revoked, including EIP-712 / EIP-1271 signed grants, per-principal nonces, and operator delegation.

The main new piece I see in your proposal is the machine-readable reason vocabulary and the gated-asset rules. RAMS / ERC-8226 already returns structured reasons at the compliance layer through IComplianceProvider.ReasonCode, so there may be a clean way to align both efforts instead of ending up with two overlapping specs.

Would be happy to sync and see how we can make the pieces compose cleanly!

1 Like

Have you looked into ERC-7710 or MetaMask’s Delegation Framework? It will save you a bunch of time.

My main concern is the enforcement boundary. If this is framed as the general primitive for agent spend, asset-level enforcement has a significant backward-compatibility problem: every token that wants to participate has to expose the gated-asset surface and call the spend gate from its transfer path. That can be useful for new regulated assets or issuer-controlled assets, but it doesn’t help the huge universe of existing ERC-20s or native-token flows unless each asset opts in.

A more general layer for this already exists in ERC-7710, with ERC-7715 as the wallet-facing request layer. In that model, the user grants a bounded delegation from their smart account to a session account / agent / app. The Delegation Manager validates the permission and caveats before executing ordinary calldata, e.g. a normal ERC-20 transfer. That means the token contract does not need to know anything about the mandate. The asset sees a standard transfer; the account/delegation layer enforces token address, amount, expiry, redeemer, call count, per-period allowance, revocation, etc.

That distinction feels important for agents. The question is usually not “does this particular asset implement a spend-mandate hook?” but “did this principal authorize this delegate to perform this exact class of execution under bounded terms?” ERC-7710 is designed around that capability-delegation shape, and MetaMask’s Delegation Framework already has caveat enforcers for fine-grained restrictions. ERC-7715 then gives wallets a standard way to present/request those permissions in a human-readable way.

So I’d separate the two layers:

• Asset-level gates: useful for assets that need issuer/compliance/policy enforcement in the token transfer path.
• Account/delegation-level mandates: better default for general agent spend because they work with existing assets and standard execution calldata.

In other words: I’d lean toward composing this with ERC-7710 / ERC-7715 rather than creating a parallel general-purpose mandate layer at the asset boundary. Let token hooks handle asset-specific policy where they’re needed, but keep general delegated spend authority at the account/delegation layer so it works with today’s assets. I guarantee you can achieve everything you want by leveraging the Delegation Framework.

• ERC-7710: ERC-7710: Smart Contract Delegation
• ERC-7715: ERC-7715: Request Permissions from Wallets
• MetaMask Delegation overview: Delegation | MetaMask developer documentation
• MetaMask Advanced Permissions: Advanced Permissions (ERC-7715) | MetaMask developer documentation