Abstract
This proposal extends ERC-7204 smart-wallet token managers with an off-chain authorization flow similar to permit. It introduces nonce-tracked tokenTransferWithSig, tokenApproveWithSig, and tokenSetApprovalForAllWithSig functions so that transfers, individual allowances, and global operators can all be delegated after a typed-data signature is presented by the wallet owner. The extension defines the canonical EIP-712 schemas, nonce accounting, and expected behaviour for compliant implementations.
Motivation
ERC-7204 describes a token management module for smart contract wallets, but stops short of standardising an off-chain signing workflow. Systems that wish to provide “red packet”-style transfers or account delegation flows must currently implement bespoke message encodings and nonce tracking, reducing interoperability. A shared permit definition enables wallets, relayers, and user interfaces to exchange signed transfer authorisations without bespoke integrations.
Goals:
- Allow wallets to hand out single-use, replay-protected transfer rights and operator approvals without on-chain transactions.
- Produce predictable EIP-712 schemas so that wallet UIs, SDKs, and relayers can sign and validate authorisations in a uniform manner.
- Maintain backwards compatibility with existing ERC-7204 deployments that do not implement permit extensions.
Specification
The following additions are REQUIRED for an ERC-7204 compliant module that implements this extension.
Interface
interface IERC7204Permit {
function tokenTransferNonce(address owner, address asset, address caller) external view returns (uint256);
function tokenTransferWithSig(
address owner,
address asset,
address to,
uint256 value,
uint256 deadline,
bytes calldata signature
) external returns (bool success);
function tokenApproveNonce(address owner, address asset, address operator, address caller)
external
view
returns (uint256);
function tokenApproveWithSig(
address owner,
address asset,
address operator,
uint256 value,
uint256 deadline,
bytes calldata signature
) external returns (bool success);
function tokenApprovalForAllNonce(address owner, address caller) external view returns (uint256);
function tokenSetApprovalForAllWithSig(
address owner,
address operator,
bool approved,
uint256 deadline,
bytes calldata signature
) external returns (bool success);
}
tokenTransferNonceMUST return a monotonically increasing nonce scoped to the tuple(owner, asset, caller).tokenTransferWithSigMUST:- Verify the signature using
ERC-1271at the smart wallet address. - Require the caller of the module to equal the
callerencoded in the signed message. - Reject signatures whose
deadlineis in the past unless the deadline is0. - Increment the nonce returned by
tokenTransferNonceimmediately before performing the transfer. - Perform the token transfer exactly as described in ERC-7204.
- Verify the signature using
Typed Data
Compliant implementations MUST hash the following payload and pass it to the wallet’s isValidSignature function:
- EIP-712 domain separator fields:
name = "TokenManager Permit"version = "1"chainIdas observed by the moduleverifyingContract = address(this)(the wallet executing the module)
- Primary type
TokenTransferWithSigwith the fields:
| Field | Type | Description |
|---|---|---|
wallet |
address | The smart wallet address invoking the module. |
owner |
address | Wallet owner index that approved the transfer. |
caller |
address | The account expected to submit the transaction on-chain. |
asset |
address | ERC-20 asset to transfer. |
to |
address | Recipient of the assets. |
value |
uint256 | Amount of token units to transfer. |
nonce |
uint256 | Value returned by tokenTransferNonce(owner, asset, caller) |
deadline |
uint256 | Expiry timestamp; 0 represents no expiry. |
For approvals the primary types mirror the on-chain behaviour:
| Function | Primary Type | Fields |
|---|---|---|
tokenTransferWithSig |
TokenTransferWithSig (wallet, owner, caller, asset, to, value, nonce, deadline) |
|
tokenApproveWithSig |
TokenApproveWithSig (wallet, owner, caller, asset, operator, value, nonce, deadline) |
|
tokenSetApprovalForAllWithSig |
TokenApprovalForAllWithSig (wallet, owner, caller, operator, approved, nonce, deadline) |
The module MUST wrap the owner signature in a format understood by the wallet (ERC-1271 allows arbitrary encoding). Wallets using packed owner bytes (such as SignatureWrapper{ ownerIndex, signatureData }) MUST ensure the module forwards the wrapper unchanged.
Nonce Semantics
tokenTransferNonceMUST be scoped per(owner, asset, caller).tokenApproveNonceMUST be scoped per(owner, asset, operator, caller)so that multiple relayers can manage allowances independently.tokenApprovalForAllNonceMUST be scoped per(owner, caller).
Rationale
- Caller binding prevents relayers from reusing signatures intended for other executors and mirrors the semantics of native ERC-20 permits as used in multicall payments.
- Per-function nonce scoping ensures transfers, per-operator approvals, and global approvals cannot replay one another.
- Domain name is intentionally generic (“TokenManager Permit”) so different implementations can interoperate. Wallets MAY expose additional domain separation through custom prefixes but MUST continue to accept the standard name.
Backwards Compatibility
Existing ERC-7204 modules remain valid. Wallets SHOULD feature-detect permit support by checking whether the module implements IERC7204Permit via supportsInterface or by probing the function selectors.
Security Considerations
- Wallets MUST guarantee that the
nonceincrements even if downstream transfers revert. Failing to do so could enable signature replay. - Signers SHOULD evaluate
deadlineand refuse to sign if the value is too distant in the future. - Integrators MUST validate
callerto avoid inadvertently relaying authorisations intended for other contexts.
Copyright
Copyright and related rights waived via CC0-1.0.