ERC-8064: Permit Extension for Smart Wallet

Abstract

This proposal extends ERC-7204 smart-wallet token managers with an off-chain permit flow for approvals. It introduces nonce-tracked tokenPermit, tokenPermitForAll functions, allowing individual allowances and global operator approvals to be set via typed-data signatures presented by relayers. The extension defines canonical EIP-712 schemas, nonce accounting, and execution requirements for compliant implementations.

Motivation

ERC-7204 defines a token management module for smart contract wallets but does not standardize an off-chain signing workflow for approvals. Applications requiring gasless approvals (e.g., delegated spending in DeFi protocols or batch operations) currently need custom encodings and nonce schemes, reducing interoperability. A standardized permit extension enables wallets, relayers, and user interfaces to handle signed approval authorizations uniformly.

Goals:

  • Enable wallet owners to delegate individual allowances and operator approvals without on-chain transactions.
  • Provide predictable EIP-712 schemas for uniform signing and validation across wallets and tools.
  • Preserve backwards compatibility with existing ERC-7204 deployments that do not implement this extension.

This proposal extends ERC-7204 directly to introduce a canonical EIP-712 permit flow within the existing token management model. By defining scoped nonces and standardized typed-data structures, it enables interoperable off-chain approvals without introducing a separate approval mechanism or fragmenting wallet integrations.

Specification

The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119 and RFC 8174.

Compliant contract must implement the ERC-165 interfaces

The following additions are REQUIRED for an ERC-7204 compliant module that implements this extension.

Interface

interface IERC8064 {
    /// @notice Returns the current nonce for individual approvals of a specific asset and spender
    function tokenApproveNonce(address asset, address spender) external view returns (uint256);

    /// @notice Returns the current nonce for operator (tokenApproveForAll) approvals
    function tokenApprovalForAllNonce(address spender) external view returns (uint256);

    /// @notice Sets approval for a specific amount via off-chain signature
    function tokenPermit(
        address asset,
        address spender,
        uint256 value,
        uint256 invalidAfter,
        bytes calldata signature
    ) external returns (bool success);

    /// @notice Sets or revokes operator status via off-chain signature
    function tokenPermitForAll(
        address spender,
        bool approved,
        uint256 invalidAfter,
        bytes calldata signature
    ) external returns (bool success);
}
  • tokenApproveNonce MUST return a monotonically increasing nonce scoped to (asset, spender).
  • tokenApprovalForAllNonce MUST return a monotonically increasing nonce scoped to spender.
  • tokenPermit MUST mirror tokenApprove, using a nonce scoped to (asset, spender).
  • tokenPermitForAll MUST mirror tokenApproveForAll, using a nonce scoped to (spender).

Implementations MUST support wallets that validate signatures through ERC-1271. Wallets MAY wrap signatures but the module MUST unwrap them prior to verification.

Typed Data

Function Primary Type Fields
tokenPermit TokenPermit wallet, asset, spender, value, nonce, invalidAfter
tokenPermitForAll TokenPermitForAll wallet, spender, approved, nonce, invalidAfter

Every permit MUST use the EIP-712 domain:

  • name = "TokenManager Permit"
  • version = "1"
  • chainId = the executing chain
  • verifyingContract = address(this)

Nonce Semantics

  • tokenApproveNonce MUST be scoped per (asset, spender).
  • tokenApprovalForAllNonce MUST be scoped per (spender).

Each permit function MUST increment its nonce immediately before other state changes and MUST revert on signature reuse.

Execution Requirements

Implementations MUST:

  1. Treat invalidAfter == 0 as non-expiring; otherwise require block.timestamp <= invalidAfter.
  2. Determine the wallet address explicitly, either from the execution context or from the signed payload, and validate the EIP-712 digest against that wallet using ERC-1271. Implementations MUST NOT assume that a contract signer can be recovered from the signature itself.
  3. Verify the provided nonce matches the current stored nonce for the corresponding scope.
  4. Increment the scoped nonce before invoking the underlying ERC-7204 approval function. The nonce MUST remain incremented even if the downstream approval call reverts.
  5. Emit the same events as the corresponding ERC-7204 functions.

Rationale

  • Naming functions tokenPermit and tokenPermitForAll aligns with the widely adopted ERC-2612 pattern while distinguishing the two operations.
  • Scoped nonces provide strong replay protection and allow independent management of different assets and operators.
  • Including the wallet owner in the signed message prevents cross-wallet replay attacks.
  • Internally delegating to existing ERC-7204 approval functions ensures full compatibility with events, storage, and existing tooling.

Backwards Compatibility

Existing ERC-7204 modules remain valid. Clients should feature-detect permit support by checking for IERC8064 via ERC-165 or probing the new function selectors.

I’m a bit of a proponent for using validBefore or invalidAfter instead of deadline, because they avoid any ambiguity between < and <= (respectively).

1 Like

If the recovered address is a contract, validate the digest using ERC-1271 on the wallet address. The recovered/validated signer MUST equal the wallet owner.

This is not how ERC-1271 is supposed to work. You can’t “recover” a contract address, it has to be provided separately.

You’ll note that Uniswap’s Permit2 requires an owner argument, and its verify implementation does a bunch of work to figure out if it should use ecrecover or ERC-1271.

1 Like

This looks like a solid extension for ERC-7204. Standardizing the permit flow for smart account modules is definitely needed.

I have a question about the tokenTransferNonce scoping. You mention it must be scoped to the tuple (owner, asset, caller).

While this offers granular replay protection, doesn’t it create a significant state bloat issue? If a user interacts with 50 different assets across 3 different relayers (callers), the contract has to initialize and store 150 separate storage slots.

Have you considered a simpler nonce model (like a single nonce per owner or per owner + caller)? It might sacrifice some concurrency but would be much cheaper gas-wise for the module to track.

1 Like

What you said makes a lot of sense. I’ve also noticed this issue, and I’ve applied for a separate ERC for tokenTransferWithSig (ERC-8112 ERC-8112: Token Transfer With Signature). The new one has been changed to function tokenTransferNonce(address asset, address to) external view returns (uint256);

On deadline: fair point. I used deadline mainly for consistency with ERC-2612 and existing permit conventions. invalidAfter is more explicit.

Yes, that was a mistake in my wording.
In this proposal, that verifier is the wallet itself, determined from the execution context or signed payload, and the module should validate the EIP-712 digest directly against that wallet via ERC-1271.
I have updated the draft accordingly.