{"id":"ERC-2612","name":"Permit Extension for ERC-20","status":"Final","chain":"both","category":{"id":"transaction-friction","name":"Transaction Friction","description":"Reducing clicks, signatures, and mental overhead"},"journeyStages":[{"id":"approving","name":"Approving & Permissioning","description":"Granting permissions for actions"},{"id":"executing","name":"Executing Transactions","description":"Performing on-chain actions"}],"uxImpact":"ERC-2612 adds an optional permit(owner, spender, value, deadline, v, r, s) path to an ERC-20 token. A signed permit can set an allowance without an approval transaction; an integrating router or relayer may combine that permit with an action, but the permit deadline only limits submission of the signature and does not expire the resulting allowance.","officialUrl":"https://ercs.ethereum.org/ERCS/erc-2612","lastReviewed":"2026-09-18","lastUpdated":"2026-09-18","kind":"proposal","hasDetailedContent":true,"content":{"id":"ERC-2612","summary":"ERC-2612 adds an optional permit(owner, spender, value, deadline, v, r, s) path to an ERC-20 token. A signed permit can set an allowance without an approval transaction; an integrating router or relayer may combine that permit with an action, but the permit deadline only limits submission of the signature and does not expire the resulting allowance.","designerTakeaways":["Show token, allowance value, spender, and the permit submission deadline in the signature preview.","Present one action only when the selected router or relayer actually combines permit and execution.","Keep allowance amount and lifetime separate from the signature deadline, and offer reduce/revoke controls."],"applicability":{"whenToUse":["The token implements permit() and a selected router or relayer can combine it with the action.","Users hesitate at separate approve transactions.","You batch permit signature with the main action via relayer or router."],"whenToAvoid":["The token does not support ERC-2612 (fall back to classic approve).","Hardware wallet flows cannot sign typed data reliably.","Regulatory or policy requirements mandate on-chain approve visibility."]},"designDecisions":[{"question":"Do users see one action or two?","recommendation":"Present one Swap or Deposit action only when a router or relayer submits permit and execution together; otherwise show the separate approval step.","rationale":"Exposing permit as a separate step recreates the confusion ERC-2612 removes."},{"question":"What appears in the signature preview?","recommendation":"Show token, allowance value, spender, and label deadline as the latest time the permit may be submitted.","rationale":"ERC-2612's deadline is not an automatic expiry for the allowance created by permit."},{"question":"What if permit is unsupported?","recommendation":"Fall back to classic approve with the same UI labels where possible.","rationale":"Graceful fallback avoids dead ends on older tokens."},{"question":"How do you handle expired permits?","recommendation":"Refresh deadline and ask for a new signature with clear expiry copy.","rationale":"Silent failures on stale permits look like app bugs."}],"statesToDesign":[{"state":"Permit signing","trigger":"Wallet prompts for EIP-712 permit signature.","userNeed":"Understand that the signature sets an allowance up to a value and that the deadline only limits permit submission.","designResponse":"Show token, spender, value, and the submission deadline; do not call the deadline an allowance expiry."},{"state":"Permit rejected","trigger":"User rejects typed data signature.","userNeed":"Retry or use classic approve path.","designResponse":"Offer retry and \"Approve in wallet\" fallback."},{"state":"Executing with permit","trigger":"Relayer or router submits permit + action.","userNeed":"Single progress indicator.","designResponse":"Show one progress state only when a router or relayer combines permit and action; otherwise show the separate transaction."},{"state":"Deadline expired","trigger":"Permit past deadline on chain.","userNeed":"Know to sign again.","designResponse":"Explain that the signature cannot be submitted now; regenerate it and separately show any existing allowance."},{"state":"Non-permit token","trigger":"Token lacks DOMAIN_SEPARATOR / permit.","userNeed":"Complete flow without dead end.","designResponse":"Auto-route to standard approve flow with same CTA label."}],"problemsSolved":[{"problem":"Two transactions for any token interaction","oldWay":"Approve → wait for confirm → swap → wait for confirm","newWay":"Sign a permit to set allowance, then execute the action separately or through an integrating router","impact":"critical"},{"problem":"Users pay gas for approval transactions","oldWay":"Pay $5 gas just to approve, before the actual swap","newWay":"Permit is a signature and may be submitted together with the action by an integrating router or relayer.","impact":"high"},{"problem":"Approval UX is confusing","oldWay":"Why do I need to \"approve\" before I can \"swap\"?","newWay":"A selected router or relayer can present a single action; otherwise show the separate approval step.","impact":"high"}],"uxPatterns":[{"name":"Sign + Execute Pattern","description":"Approve and action in one user flow when a selected router or relayer supports combining them.","userFlow":["User enters swap details","Click \"Start Swap\"","Wallet shows permit signature request","User signs permit (free)","Wallet shows transaction confirmation","User confirms transaction","The selected router or relayer may execute the action with permit when supported; the permit itself can be submitted by any caller."]},{"name":"Permit vs Approve Comparison","description":"Educate users on the improvement","userFlow":["First-time user sees \"Sign Permit\"","Clicks \"Why?\" or info icon","Modal explains gas savings","User understands and continues"]}],"uiComponents":[{"name":"PermitSignButton","description":"Button that initiates permit signature","states":["idle","signing","signed","error"],"props":["token","amount","spender","deadline","onSign"]},{"name":"TwoStepFlow","description":"Indicator showing sign → execute progress","states":["not-started","signing","signed","executing","complete"],"props":["currentStep","steps[]"]},{"name":"GasSavingsDisplay","description":"Shows estimated gas difference when the selected integration can compare permit and approval paths.","states":["calculating","ready"],"props":["withoutPermit","withPermit","savings"]}],"antiPatterns":[{"pattern":"Not explaining why there's a signature step","why":"Users confused by \"Sign\" before \"Confirm\"","instead":"Clear UI: \"Sign permit (free) then confirm swap\"","severity":"high"},{"pattern":"Falling back to approve without notice","why":"User suddenly has extra transaction, pays more gas","instead":"Warn: \"This token doesn't support permit, using approval\"","severity":"medium"},{"pattern":"Calling the permit deadline an allowance expiry","why":"After a valid permit is submitted, its allowance can remain until changed by the token's allowance rules.","instead":"Label deadline as \"Permit must be submitted by\" and show allowance amount and revoke/reduce controls separately.","severity":"critical"},{"pattern":"Not supporting permit when available","why":"Users pay unnecessary approval gas","instead":"Check for permit support, use when available","severity":"medium"}],"keyTakeaways":["ERC-2612 can set an ERC-20 allowance through a signature.","A permit deadline limits submission; it does not automatically expire the allowance.","One-step UX requires a separate router or relayer integration.","Any address may submit permit; the signed spender and allowance are the authority, not the selected relayer.","Verify the deployed ABI, domain, nonce, and permit semantics on the selected network.","Keep exact ERC-2612 separate from DAI-style permit variants."],"technicalNotes":"ERC-2612 defines permit(owner, spender, value, deadline, v, r, s), nonces(owner), and DOMAIN_SEPARATOR. Any address can call permit; the signature binds the owner, spender, value, nonce, and deadline, not a selected router or relayer. The token checks block.timestamp <= deadline when permit is submitted, then writes the allowance value; the allowance does not automatically expire at that deadline. Combining permit with an action is separate integration logic. DAI uses a different permit shape with holder, spender, nonce, expiry, and allowed. Support is opt-in per deployment and network.","lastUpdated":"2026-09-18","specConstraints":[{"id":"permit-contract-wallet-ui","constraint":"The permit pattern still needs interfaces that support contract wallets and their distinction between owner and wallet address.","appliesTo":"application","normativity":"rationale","specQuote":"**_Note:_** EIP-20 must be performed by an EOA unless the address owning the token is actually a contract wallet. Although contract wallets solves many of the same problems that motivates this EIP, they are currently only scarcely adopted in the ecosystem. Contract wallets suffer from a UX problem -- since they separate the EOA `owner` of the contract wallet from the contract wallet itself (which is meant to carry out actions on the `owner`s behalf and holds all of their funds), user interfaces need to be specifically designed to support them. The `permit` pattern reaps many of the same benefits while requiring little to no change in user interfaces.","specSection":"Abstract","specUrl":"https://ercs.ethereum.org/ERCS/erc-2612#abstract","designImplication":"Do not assume every permit signer is an EOA. Keep owner, contract wallet, and spender roles clear in approval UI."},{"id":"permit-invalid-reverts","constraint":"A permit call reverts when its required conditions are not met.","appliesTo":"application","normativity":"normative","specQuote":"If any of these conditions are not met, the `permit` call must revert.","specSection":"Specification","specUrl":"https://ercs.ethereum.org/ERCS/erc-2612#specification","designImplication":"Keep an invalid-signature or expired-permit state and do not display the allowance as changed until the transaction succeeds."},{"id":"permit-front-run-deadline","constraint":"A relayer can submit a signed permit before the intended party, and the deadline is one mitigation against indefinite withholding.","appliesTo":"application","normativity":"security-considerations","specQuote":"another party can always front run this transaction and call `permit` before the intended party.","specSection":"Security Considerations","specUrl":"https://ercs.ethereum.org/ERCS/erc-2612#security-considerations","designImplication":"Show the relayer role and deadline. Treat a signed permit as a spend authorization that can be submitted by another party before expiry."},{"id":"permit-zero-owner","constraint":"Permit validation must prevent an approval for the zero address when signature recovery fails.","appliesTo":"account-implementation","normativity":"security-considerations","specQuote":"Since the ecrecover precompile fails silently and just returns the zero address as `signer` when given malformed messages, it is important to ensure `owner != address(0)` to avoid `permit` from creating an approval to spend \"zombie funds\" belong to the zero address.","specSection":"Security Considerations","specUrl":"https://ercs.ethereum.org/ERCS/erc-2612#security-considerations","designImplication":"No new UI is required, but an approval screen should never report a recovered zero address as a valid owner."},{"id":"permit-approval-race","constraint":"The ordinary ERC-20 approval race condition also applies to permit.","appliesTo":"application","normativity":"security-considerations","specQuote":"The standard EIP-20 race condition for approvals (SWC-114) applies to `permit` as well.","specSection":"Security Considerations","specUrl":"https://ercs.ethereum.org/ERCS/erc-2612#security-considerations","designImplication":"Show the spender and amount in signed approval review and provide a safe allowance replacement path."},{"id":"permit-chain-replay","constraint":"A deployment-time domain separator containing chain ID can permit replay across chains after a chain split.","appliesTo":"wallet","normativity":"security-considerations","specQuote":"there is a risk of possible replay attacks between chains in the event of a future chain split.","specSection":"Security Considerations","specUrl":"https://ercs.ethereum.org/ERCS/erc-2612#security-considerations","designImplication":"Show the chain context in permit review and invalidate or re-sign approvals when the chain identity changes."},{"id":"permit-variant-compatibility","constraint":"Existing permit variants differ in arguments and semantics.","appliesTo":"application","normativity":"backwards-compatibility","specQuote":"## Backwards Compatibility\n\nThere are already a couple of `permit` functions in token contracts implemented in contracts in the wild, most notably the one introduced in the `dai.sol`.\n\nIts implementation differs slightly from the presentation here in that:\n\n- instead of taking a `value` argument, it takes a bool `allowed`, setting approval to 0 or `uint(-1)`.\n- the `deadline` argument is instead called `expiry`. This is not just a syntactic change, as it effects the contents of the signed message.\n\nThere is also an implementation in the token `Stake` (Ethereum address `0x0Ae055097C6d159879521C384F1D2123D1f195e6`) with the same ABI as `dai` but with different semantics: it lets users issue \"expiring approvals\", that only allow `transferFrom` to occur while `expiry >= block.timestamp`.\n\nThe specification presented here is in line with the implementation in Uniswap V2.\n\nThe requirement to revert if the permit is invalid was added when the EIP was already widely deployed, but at the moment it was consistent with all found implementations.","specSection":"Backwards Compatibility","specUrl":"https://ercs.ethereum.org/ERCS/erc-2612#backwards-compatibility","designImplication":"Detect the deployed permit interface before rendering a signing request. Do not copy a DAI-style approval model onto an allowance-value permit."},{"id":"permit-domain-separation","constraint":"The permit DOMAIN_SEPARATOR SHOULD be unique to the contract and chain.","appliesTo":"account-implementation","normativity":"normative","specSection":"Specification","specUrl":"https://ercs.ethereum.org/ERCS/erc-2612#specification","designImplication":"Include the contract and chain domain in signing previews and reject or explain mismatches before a permit is submitted.","specQuote":"where `DOMAIN_SEPARATOR` is defined according to EIP-712. The `DOMAIN_SEPARATOR` should be unique to the contract and chain to prevent replay attacks from other domains,\nand satisfy the requirements of EIP-712, but is otherwise unconstrained.\nA common choice for `DOMAIN_SEPARATOR` is:"}],"sources":[{"label":"ERC-2612 official specification","url":"https://ercs.ethereum.org/ERCS/erc-2612","type":"official-spec"}],"lastReviewed":"2026-09-18"},"sources":[{"label":"Official specification","url":"https://ercs.ethereum.org/ERCS/erc-2612","type":"official-spec"},{"label":"Discussion search","url":"https://ethereum-magicians.org/search?q=ERC-2612","type":"discussion"}],"urls":{"canonical":"https://www.eipsfordesigners.com/standards/ERC-2612","markdown":"https://www.eipsfordesigners.com/standards/ERC-2612/content.md","agent":"https://www.eipsfordesigners.com/standards/ERC-2612/agent.md","api":"https://www.eipsfordesigners.com/api/standards/ERC-2612","official":"https://ercs.ethereum.org/ERCS/erc-2612","discussion":"https://ethereum-magicians.org/search?q=ERC-2612"},"freshness":{"lastReviewed":"2026-09-18","lastUpdated":"2026-09-18"}}