Standard / EIP·EVM
EIP-2612 — Permit Extension for ERC-20 Signed Approvals
Adds `permit(owner, spender, value, deadline, v, r, s)` to ERC-20 so users can approve transfers via an EIP-712 signature instead of a separate `approve` transaction — enabling gasless approvals and one-tx swaps. Status: Final (Standards Track / ERC).
- 01single-tx ERC-20 approvals
- 02gasless / sponsored approvals via meta-tx
- 03DEX / aggregator UX (one tx instead of approve+swap)
- 04lending protocols, vaults
- 05Permit2 fallback path
- pnpm add viem # signTypedData with the Permit type / readContract for nonces & DOMAIN_SEPARATOR
- pnpm add permit2-sdk # Uniswap Permit2 (preferred for tokens that don't implement 2612)
- # Solidity: OpenZeppelin ERC20Permit (extends ERC20 with permit, nonces, DOMAIN_SEPARATOR)
Use EIP-2612 to approve ERC-20 spends via an EIP-712 signature. Read `nonces(owner)` and `DOMAIN_SEPARATOR()` (or rebuild it from `name`, `version`, `chainId`, address) from the token. Build a Permit struct: `{ owner, spender, value, nonce, deadline }` with type `Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)`. Sign with `walletClient.signTypedData({ domain, types: { Permit: [...] }, primaryType: 'Permit', message })` and split the signature into `(v, r, s)` (use viem's `parseSignature` / hexToSignature). Submit via `token.permit(owner, spender, value, deadline, v, r, s)` — typically batched with the action that consumes the allowance (swap, deposit) so the approval is atomic. For tokens that DON'T implement 2612 (e.g. USDC on most chains until 2024, USDT) use Uniswap Permit2 as a universal allowance layer instead.
- ⚑Not every ERC-20 implements 2612 — USDT on Ethereum mainnet famously does not, and DAI uses a non-standard `permit(holder, spender, nonce, expiry, allowed, v, r, s)` (boolean instead of value). Always feature-detect or maintain a per-token allowlist.
- ⚑The domain separator includes `name`, `version`, `chainId`, `verifyingContract`. USDC on Polygon historically used `version='2'` while most tokens use `'1'` — fetch `version()` if exposed or read the constructor args.
- ⚑`nonce` is incremented PER PERMIT — concurrent permits race. Always read `nonces(owner)` immediately before signing and treat the signature as single-use.
- ⚑Front-running griefing: anyone who sees a pending `permit` tx can submit it themselves to consume the nonce, causing the user's bundled `permit + swap` to fail at the swap step. Mitigate by using `try/catch` around the permit call or by using Permit2 (which signs the action and the allowance together).
- ⚑`deadline` is a Unix timestamp checked against `block.timestamp` — short deadlines (60s) are common for safety; mobile wallets sometimes drift, set deadlines >= 5 min.
- ⚑Smart-account holders need EIP-1271 support in the token's permit implementation; OpenZeppelin's ERC20Permit added this in v4.9 — older deployments reject contract signatures.