Draft spec: Add ERC-8248: Keyed Transfer With Authorization by BrianBland · Pull Request #1715 · ethereum/ERCs · GitHub
TL;DR
ERC-3009 spends one persistent storage slot per signed authorization. That works fine for one-shot payments but accumulates indefinitely under repeat-counterparty patterns, which now dominate high-volume usage (agentic payments, M2M). ERC-8248 defines a sibling scheme that uses a two-dimensional nonce: a 192-bit nonceKey selecting an independent sequence, and a 64-bit nonceSequence consumed in order. All authorizations between the same payer and counterparty collapse to a single storage slot. The encoding is bit-for-bit identical to ERC-4337’s “Semi-abstracted Nonce Support”. Signatures are passed as bytes and validated through ERC-1271, so EOAs and smart-contract accounts share one entry point.
Why bother: the data
I pulled 60 days of USDC transferWithAuthorization traffic on Base: 4,403,045 authorizations across 80,374 unique authorizers. Looking at the top 20 authorizers by volume:
- 17 of 20 were “loyal-customer”: each paid a small fixed set of counterparties, with 13 paying exactly one counterparty.
- The top single authorizer issued 81,731 authorizations to one payee over the period.
- 3 of 20 were “explorer”: marketplaces or aggregators paying thousands of distinct counterparties.
Storage cost for those 81,731 single-payee authorizations:
| Scheme | Slots | vs. ERC-3009 |
|---|---|---|
ERC-3009 (random bytes32) |
81,731 | 1× |
| Bitmap (256 nonces/slot) | 320 | 256× |
| Keyed sequential (this ERC) | 1 | 81,731× |
Across all 17 loyal-customer payers in the sample, the keyed scheme would consume fewer than 60 total slots for over 780,000 authorizations (vs ~2,800 for a bitmap). The win compounds with continued use of the same key.
What it looks like
function transferWithKeyedAuthorization(
address from,
address to,
uint256 value,
uint256 validAfter,
uint256 validBefore,
uint192 nonceKey,
uint64 nonceSequence,
bytes calldata signature
) external;
function keyedAuthorizationNonce(address authorizer, uint192 nonceKey)
external view returns (uint256); // packed (uint256(key) << 64) | sequence, matches ERC-4337
Storage is one mapping(address => mapping(uint192 => uint64)) _nonceSequences. The first authorization under any new key uses nonceSequence = 0; subsequent ones increment by one. The recommended convention is nonceKey = uint192(uint160(payeeAddress)) with the upper 32 bits as a per-channel subkey, but the field is opaque and payers may use any value.
A cancelKeyedAuthorization(authorizer, nonceKey, nonceSequence, signature) is included as OPTIONAL, and operates on the counter directly (advances to nonceSequence + 1, invalidating everything below).
Out of scope (intentionally)
- Bitmap variant for the explorer pattern. Could be a companion ERC if there’s demand.
- Migration tooling for existing ERC-3009 deployments. The two coexist fine; nothing to migrate.
- Multi-chain replay protection beyond standard EIP-712 domain separation.
Specific feedback I’m soliciting
- Field name
nonceSequence. ERC-4337 calls this “sequence” or “key”; I went withnonceSequencefor clarity inside an EIP-712 type that already binds anonce-shaped concept. Better names? - Packed
uint256nonce as the canonical form. Currently the EIP-712 type uses split fields and the view function returns the packed form. ERC-4337 uses packed throughout. Should this ERC mirror that more aggressively, or is the split form clearer for hand-construction of EIP-712 messages? - Counter-first ordering. Is the spec wording (counter-advance as MUST step 1, signature verification as MUST step 2 with revert-unwinds-advance) clear and tight enough? Anyone see an angle this misses?