Essay
ERC-8427: capped, revocable spending on Ethereum
ERC-8427 lets an account owner register capped, revocable spending of ETH and tokens for a delegate, with per-payment, rolling, and lifetime caps. Where it helps and why.
More software now needs to spend money on someone's behalf: an agent paying for services, an app collecting a recurring fee, an operations bot paying suppliers from a team account. The tools Ethereum offers for this are either too broad or too narrow. I have written a draft standard, ERC-8427: Portable Spend Grants, that gives account owners a better option, and I would like feedback from the people who would build on it.
Updated October 15, 2026. I first published this on September 28. The draft has changed since then, mostly because of review on the Ethereum Magicians thread. The biggest change is that the owner no longer signs a grant and hands it over. The owner now registers it on chain. This post is rewritten to match, and the section "What changed since September 28" below lists the differences. Several people have also built against the draft, and I wrote those up in ERC-8427 integrations.
The problem with approvals
The usual way to let someone else spend your tokens is an ERC-20 approve. In practice that is one token, often an unlimited amount, and no expiry. It says nothing about how much can go out per payment or per day, and it stays in place until you remember to remove it.
Some wallet permissions add recurring allowances that reset at a fixed boundary such as midnight UTC. A "100 per day" limit that resets at midnight lets someone spend 100 at 11:50 PM and another 100 at 12:10 AM. That is twice the limit in twenty minutes.
A delegate that needs ETH and two tokens means three separate permissions to sign and later revoke. Today, spending native currency needs an account adapter, and the reference executors are ERC-20 only. And wallet permission APIs leave the meaning of a permission to each implementation, so two wallets can show the same hash and still disagree on how the window is counted or whether a second asset has its own limit. The owner also needs a way to cancel that does not depend on the delegate cooperating.
What a spend grant is
A spend grant is a set of terms that an account owner (the principal) registers on chain for one address (the delegate). The terms say which assets the delegate may spend, and for each asset three limits: the most per payment, the most in a trailing window such as 24 hours, and the most over the grant's whole life. They also say who may be paid (one fixed address, or anyone) and the time range in which the grant is valid.
The owner registers the terms by calling register on an immutable registry contract. The registry stores the terms, what has been spent under them, and whether the grant has been revoked; it never moves funds. It returns a grantId, which is the EIP-712 digest of the terms. A separate executor checks that the delegate authorized each payment, has the registry record it, and moves the funds from the owner's account in the same transaction. If either step fails, the whole transaction reverts. Existing tokens need no changes, and a cap of zero is rejected, never read as unlimited.
The owner can change a grant in place with update, which keeps what has already been spent, or cancel it with revoke. Both can only be called from the owner's account, and neither needs the delegate.
Grant ids, not balances
A spend grant lets a principal keep its funds in one account and give each delegate a capped, revocable right to spend from it, instead of moving a balance into every delegate's wallet. The delegate holds a grant id, not money.
That distinction matters. Pre-funding wallets scatters capital across every agent, leaves it exposed if any one of them is compromised, and makes taking it back a recovery job. With a grant, the money never leaves the principal's account. Limits are enforced at spend time, and revoking access is a single call.
This costs the owner a transaction before the delegate can spend. The September version had the owner sign the grant and give it to the delegate, which cost nothing until the first payment. I changed it because a signed grant lives off chain until it is first used. The owner could not list the grants it had made, tightening a cap meant revoking and signing a new grant with usage starting over, and a wallet had no standard way to ask why a payment would fail.
What this makes possible
Every application below uses the same registered object. A wallet that learns to show one spend grant can show all of them, whatever app asked for it.
| Application | What is common today | What a spend grant changes |
|---|---|---|
| Agents that pay for things | Give the agent its own funded wallet, or an open-ended approval | The agent spends from your account, capped per payment, per window, and in total |
| Recurring payments to one payee | Allowances that reset at a calendar boundary | A true trailing window, locked to one recipient |
| Multi-asset budgets | One approval or permission per asset | Up to 16 assets in one grant, each with its own limits, and one revocation |
| Wallet display and review | Each wallet interprets permissions its own way | One canonical text form and JSON format that any tool can check, and registry views for what is left |
| Smart accounts and EIP-7702 accounts | Permissions tied to one account's setup or key | A Safe or other contract account can register grants as principal or authorize spends as delegate |
Agents and automation that pay on your behalf
An agent or automated service needs to make many small payments without asking each time. Today the choice is usually between handing it a separate wallet with a budget in it, which moves the money out of your account and your reporting, or an approval that has no meaningful upper bound.
A spend grant makes it possible to leave the funds where they are and still bound the damage. If the agent's key is stolen, the most it can move is what is left in the current window, never more than the lifetime cap, and the owner can revoke without the agent's help.
Example. Say I want an agent to pay for data and API usage from my account for the next 90 days. I register one grant naming the agent as delegate, with a stablecoin limited to 50 per payment, 200 in any 24 hours, and 2,000 in total. I give the agent the grant's id. The agent asks the executor to pay a vendor. The executor confirms the request came from the agent, the registry checks the three limits and records the payment, and the stablecoin moves from my account to the vendor in the same transaction. I approve the executor for no more than the 2,000 the grant allows. If the agent tries to pay 60, or tries a fifth payment of 50 inside the same 24 hours, it fails. My wallet can show me how much the agent can spend right now and when more becomes available. If I decide 2,000 was too much, I call update to lower the lifetime cap. The new cap counts what the agent has already spent, so nothing starts over.
Recurring payments to a single payee
A subscription or retainer is a delegate who should be paid a bounded amount, repeatedly, and only to one address. Setting the recipient mode to a single address means the grant cannot pay anyone else, even if the delegate is compromised.
The trailing window matters here. A payment counts against the window for exactly the window length, then drops out, so there is no boundary to spend across twice. The lifetime cap never resets, so a twelve-month engagement can have a total that cannot be exceeded.
One budget across several assets
An operations wallet often needs native currency and one or more tokens. Today that means several approvals or permissions, each signed and revoked separately. A spend grant lists up to 16 assets, and each keeps its own remaining amounts. Spending one never draws down another.
The draft does not offer a shared budget across assets ("this much across A or B"), because that needs a price reference the standard does not define. The field is reserved so a later version can add it.
Wallets, auditors, and anyone reviewing terms
The draft defines an exact plain-text rendering of every grant and a strict JSON format for passing grants between tools. Any wallet or reviewer can compute a grant's id and produce the same text as every other implementation, so a finance team or a second wallet can confirm what the terms are without trusting the app that created them.
The registry also answers the questions a wallet needs after the grant exists. grantsOf and grantsTo list every grant for an owner or a delegate, including revoked ones. grantState returns the caps, what has been spent, the owner's balance and allowance, and how much can be spent now. liveDebits returns when each recent payment leaves the window, so a wallet can show "0.30 available now, 0.10 more at 14:05". check returns the reason a spend would fail, from the same evaluation the real spend runs, and the reason numbers are part of the spec.
Smart accounts and EIP-7702 accounts
Because the owner registers by calling the registry, any account that can send a transaction can be a principal. A Safe registers, updates, and revokes through its own transaction flow, at its own signing threshold, and no signature standard is involved on that side. The other side of that is worth knowing: for a contract account, whatever can make the account call register or update controls its grants. A session key or module that can call the registry can register a grant to any delegate, with any caps, against every allowance the account has already given the executor.
A contract delegate authorizes a spend either by calling the executor itself or by signing an authorization that anyone may submit, which the executor validates with ERC-1271. An EIP-7702 delegate can also sign with its own key, so an authorization it signed does not become invalid when it adopts, changes, or clears a delegation.
A common format for delegation frameworks
ERC-7710 standardizes how delegations are redeemed but leaves the permission itself opaque. A spend grant can be the concrete terms such a system carries. The reference includes an executor and a caveat enforcer for this path. They accept a redemption of the principal's own delegation to the grant's delegate, and refuse every other delegation shape, such as one open to any delegate or one redeemed by a sub-delegate. They are tested against MetaMask's DelegationManager v1.3.0 on a fork of Arc testnet. ERC-8427 does not depend on ERC-7710.
A look at the interface
The terms are two structs, taken from the ERC with one comment reworded. They have not changed since September:
struct AssetLimit {
address asset; // ERC-20, or NATIVE (ERC-7528) for this chain's native currency
uint256 maxPerCall; // one payment, raw units
uint256 maxPerWindow; // trailing window, raw units
uint256 maxTotal; // lifetime, never resets
}
struct SpendGrant {
address principal;
address delegate;
uint8 recipientMode; // 0 = one address, 1 = any
address recipient; // required nonzero if mode is 0; MUST be address(0) if mode is 1
uint8 assetCombine; // MUST be 0 (independent per-asset caps); other values reserved
uint64 windowSeconds; // trailing lookback; 86400 = 24 hours, not a UTC day
AssetLimit[] assets; // 1 to 16, unique, strictly ascending by uint160(asset)
uint64 validAfter; // inclusive unix seconds
uint64 validUntil; // exclusive unix seconds
uint256 salt;
}
On the registry, the owner calls register(terms), update(grantId, expectedVersion, terms), and revoke(grantId). An update can change everything except the principal, the delegate, and the window length. The expectedVersion argument makes an update built from an older version revert instead of undoing a newer one. Wallets read grantState, usage, rollingUsage, liveDebits, and check to show what is left, and only the executor may call consume, the function that records a payment.
What to watch for
Anyone building on this should read the ERC's Security Considerations in full. The points I would stress:
- The token allowance is the real authority. For ERC-20s, the executor moves funds using the owner's allowance to it; the grant limits how it uses that allowance. Keep the allowance no higher than what outstanding grants can still spend.
- Choosing a registry is choosing an executor. Each registry has one fixed executor, and a faulty one could move funds without recording them. Wallets should show the executor before the owner registers or updates a grant.
- A grant id is not a secret. The registry lists it and its events carry it, so the executor must check that the delegate authorized each payment. With recipient mode "any," an executor that skips this lets anyone who knows the id send the remaining budget to themselves.
- Grants are public. The terms are visible from registration, and every update and payment is linked to the grant by its id. An owner who needs payments not to be linkable uses separate grants and, where the addresses link them, separate addresses.
- Revocation is final and has no admin. There is no pause and no way to undo a revoke. A revoke, or an update that lowers a cap, takes effect only when it is included in a block, and a delegate that sees it pending can spend ahead of it. Until then, the caps are what limit a stolen delegate.
- A contract account's call policy is its grant policy. Anything that can make the account call the registry can register or raise a grant.
- Anyone can name you as a delegate. Anyone can register a grant naming any address as its delegate, so
grantsTocan list grants you never asked for. Read it in pages and filter by owner. - Some tokens do not behave. The caps bound the amount the executor asks the token to move, the same quantity an allowance bounds. A token that charges the sender an extra fee can reduce the owner's balance by more than the caps, and a rebasing token changes balances with no transfer at all.
- The registry cannot be upgraded. A bug is permanent for that deployment, and a new registry means new registrations.
What changed since September 28
- Registered, not signed. The owner registers a grant on chain and gives the delegate only its id. The signed-grant design is gone. The only signatures left are the delegate's per-spend authorizations, which an executor may accept. The earlier deployments still run the old design.
- Changing a grant.
updatechanges a grant in place and keeps its usage, andrevokeends it. I had first described replacing a grant by revoking it and signing a new one. A question from ted0x on the thread showed my rule for sizing a replacement's lifetime cap was wrong, andupdateremoves the need for it. - The delegate must be authenticated. zexoverz pointed out that a grant and its signature stop being secret once the delegate uses them. The spec now requires every executor to authenticate who authorized each spend and pass that address to the registry, which rejects anyone but the delegate.
- What a cap bounds. SergeevDmitry asked what happens when a token charges a fee on top of the amount. The spec now says the caps bound the amount the executor requests, and spells out the three fee and rebase cases.
- Visibility for wallets.
liveDebitscame from a point Anzus_GemWallet made about "per day" versus "last 24 hours". The October 13 revision addedgrantState,grantsOf,grantsTo, andcheck. - Delegation frameworks. The ERC-7710 path is more specific, and stricter, after zexoverz walked through MetaMask's DelegationManager. I found and fixed cases where a redemption could pay twice, and a fork test checks the fixes against the live manager.
- My own review pass. I ran Slither, fuzzing, and Halmos over the reference and fixed what they found. This is not an outside audit.
Where it stands
This is a draft, not a finished standard, and it is easier to change now than after wallets and executors implement it.
| Piece | Status |
|---|---|
| Specification | Draft; number 8427 assigned by the ERC editors; under review in ethereum/ERCs#2037 |
| Reference implementation | Solidity registry, three executors (direct, delegate-signed authorization, ERC-7710 redemption) and a caveat enforcer, with Foundry tests and Halmos checks; a TypeScript package for hashing, rendering, and JSON; golden test vectors; CC0; unaudited |
| Integrations | Safe, ERC-4337, ERC-8004, ERC-7710, and an x402 mapping, tested in the repo, plus an outside experiment from ted0x. See ERC-8427 integrations |
| Wallet display | A non-normative ERC-7730 descriptor for the register and update calls, so a wallet can show the delegate, recipient, window, caps, and dates |
| Deployment | Registry 0xe8987A22a1e3fd4a63cB517A9476B9d214388287 and executor 0xB99785329AbEc9936b6EF6350015be39D3392646 on Arc testnet, both verified; the repo says to deploy to testnets only |
One limit worth saying plainly: the reference executors only spend ERC-20 tokens. Spending native currency from the owner's account needs an account adapter, and the draft does not specify one.
How to weigh in
The most useful thing you can tell me is a real need. If you build wallets, agents, payment flows, or delegation frameworks and bounded spending is part of your product, I want to know whether this fits.
Discussion is on the Ethereum Magicians thread. I would especially like to hear:
- Wallet teams: what you would need to show a grant before registering it and what is left afterward.
- Agent, app, and delegation framework builders: which assets, limits, and window lengths you would use, and whether this works as the terms your permissions carry.
- Everyone: what is missing, and what could be removed.
If you want to propose a change to the text or the reference code, fork the repository and open a pull request. The README lists integrations that other people have built against the reference.
Related service: Web3 Advisory