ERC-3009: Transfer With Authorization
ERC-3009 lets a user sign an off-chain authorization to transfer tokens at a later block, with a relayer paying the gas to broadcast. It's the meta-transaction pattern shipped by USDC (transferWithAuthorization, receiveWithAuthorization, cancelAuthorization) and is one of the most widely used signature-based authorization patterns on Ethereum despite the EIP itself sitting at "Stagnant". The newer EIP-7758 (Review) is the actively-developed successor.
Required Interface
function transferWithAuthorization(
address from, address to, uint256 value,
uint256 validAfter, uint256 validBefore,
bytes32 nonce,
uint8 v, bytes32 r, bytes32 s
) external;
function receiveWithAuthorization(
address from, address to, uint256 value,
uint256 validAfter, uint256 validBefore,
bytes32 nonce,
uint8 v, bytes32 r, bytes32 s
) external;
function cancelAuthorization(
address authorizer, bytes32 nonce,
uint8 v, bytes32 r, bytes32 s
) external;
function authorizationState(address authorizer, bytes32 nonce) external view returns (bool);The signature is an EIP-712 typed-data payload over a TransferWithAuthorization struct. The nonce is per-authorization (not sequential), preventing replay attacks and allowing parallel authorizations.
Neo Equivalent: Witness Scopes
Neo doesn't need a "transfer with authorization" extension because witness scopes already provide signed, scoped transfer authorization at the protocol level. A user signs a transaction with a witness scope that says "this signature authorizes only contract X to use my address as the from parameter". The relayer broadcasts; the contract runs Runtime.CheckWitness(from) and gets true because the witness scope validates.
| ERC-3009 Component | Neo Equivalent |
|---|---|
| EIP-712 typed-data signature | Native transaction witness (Hash160 + ECDSA over the tx body) |
validAfter / validBefore window | Transaction validUntilBlock (and signed-block enforcement) |
Per-call nonce | Transaction nonce (sender-scoped, prevents replay) |
cancelAuthorization | Don't broadcast the transaction (or invalidate by changing nonce) |
| Custom domain separator | Network magic + chain id, baked into the witness signature |
Off-chain signing → on-chain ecrecover | Off-chain signing → on-chain Runtime.CheckWitness (no ecrecover round-trip) |
The whole "signed authorization, relayer broadcasts" pattern is just a normal Neo transaction with the user as a signer (witness scope: CalledByEntry or narrower), submitted by a different relayer wallet that pays GAS.
Migration Notes
Solidity contracts using ERC-3009 should drop the *WithAuthorization methods entirely when compiled with neo-solc. The from address in NEP-17's transfer(from, to, amount, data) is verified by Runtime.CheckWitness(from) — which is the same security guarantee ERC-3009 provides via signature recovery, just enforced earlier in the call stack.
For the gasless meta-tx use case (relayer pays gas), see also our ERC-2612 Permit and ERC-2771 Trusted Forwarder mirrors — both reduce to "use a witness scope" on Neo.
