ERC-8248: Keyed Transfer With Authorization

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
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

  1. Field name nonceSequence. ERC-4337 calls this “sequence” or “key”; I went with nonceSequence for clarity inside an EIP-712 type that already binds a nonce-shaped concept. Better names?
  2. Packed uint256 nonce 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?
  3. 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?