← Protocols
EIP-2612 — Permit Extension for ERC-20 Signed Approvals
Standard / EIP·EVM

EIP-2612 — Permit Extension for ERC-20 Signed Approvals

01Description

EIP-2612 helps wallets, routers, and vaults skip the extra approve transaction by turning ERC-20 allowances into a signed permit flow. This protocol reference now frames the standard around the builder jobs searchers actually have: collecting a nonce, shaping typed data, bundling permit plus action, and choosing Permit2 when tokens do not support 2612.

02Builder workflow highlights
  • 01Read the token's permit support, nonce, and domain details before prompting the user to sign anything.
  • 02Generate the EIP-712 Permit payload with owner, spender, value, nonce, and deadline so the approval can be signed off-chain.
  • 03Bundle permit execution with the downstream swap, deposit, or vault action to keep the allowance atomic and reduce user drop-off.
  • 04Fallback to Permit2 or token-specific paths when a token lacks native EIP-2612 support.
03Best for
  • 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
04Install
  • 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)
06Prompt snippet
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.
07Gotchas
  • ⚑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.
08Related guides
09Alternatives