Skip to content
Learn · Intermediate

Meta-transactions and permit()

Gasless UX via signed messages: EIP-2612 permit, relayer patterns, and how ERC-4337 generalizes meta-transactions.

Intermediate
11 min read

The meta-transaction idea

Users sign an intent off-chain; a relayer broadcasts an on-chain transaction and pays gas. The contract recovers the signer from the signature and executes as if msg.sender were that user — or validates a signed allowance. This separates gas payer from action authorizer.

  • Classic relayers are per-protocol — each dapp runs its own forwarder.
  • Replay protection uses nonces tracked per signer in the forwarder contract.
  • Relayers can censor — incentivize decentralization or allow self-relay.

EIP-2612 permit

ERC20Permit adds permit(owner, spender, value, deadline, v, r, s). Owner signs EIP-712 typed data; anyone submits it on-chain to set allowance. Enables approve + action in one transaction for DEX deposits, staking, etc.

  • DOMAIN_SEPARATOR includes name, version, chainId, verifyingContract.
  • Permit nonces increment per owner — order matters for pending signatures.
  • Deadline prevents stale signatures from being submitted later.
ERC20Permit.solsolidity
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";

contract MyToken is ERC20, ERC20Permit {
    constructor()
        ERC20("My Token", "MTK")
        ERC20Permit("My Token")
    {}
}

EIP-712 typed signatures

Wallets display structured fields users can read — safer than blind eth_sign. Contracts rebuild the digest with DOMAIN_SEPARATOR and struct type hashes. Wrong chain id or contract address invalidates the signature — protects cross-chain replay.

  • name and version in permit must match token metadata.
  • Hardware wallets show typed fields — verify spender address carefully.
  • EIP-5267 eip712Domain() helps integrators discover domain parameters.

ERC-2771 forwarders

OpenZeppelin ERC2771Context appends signer address to calldata for trusted forwarder contracts. _msgSender() returns the signer instead of the relayer. Only whitelisted forwarders should be trusted — a malicious forwarder can forge sender context.

  • isTrustedForwarder(forwarder) gates which relayers are accepted.
  • MinimalForwarder is simple; ERC2771Forwarder adds EIP-712 request structs.
  • Recipient contracts must inherit ERC2771Context — otherwise relayer is msg.sender.
ERC2771.solsolidity
import "@openzeppelin/contracts/metatx/ERC2771Context.sol";

abstract contract Recipient is ERC2771Context {
    constructor(address trustedForwarder) ERC2771Context(trustedForwarder) {}
}

ERC-4337 vs classic meta-tx

Account abstraction (ERC-4337) uses smart contract wallets and UserOperations submitted to bundlers via an EntryPoint contract. Validation logic lives in the wallet — social recovery, paymasters, batching. It generalizes relayers instead of one forwarder per dapp.

  • Paymasters sponsor gas or accept ERC20 fees — see AA article.
  • Bundler network is still maturing — check chain support.
  • EOAs remain dominant — permit is still the main gasless approval path today.

Integration checklist

Before shipping permit UX: confirm token implements EIP-2612, fetch nonces on-chain, set reasonable deadlines, display spender and value in human units, handle tokens without permit gracefully (fallback to approve). Never store private keys server-side to 'relay' for users.

  • Simulate permit + action with eth_call before spending gas.
  • Some tokens use legacy name in DOMAIN — match deployment, not branding.
  • Permit2 (Uniswap) centralizes allowances across tokens — different trust model.