Permit2 is a universal token approval contract that enables signature-based approvals and transfers. Developed by Uniswap Labs, it improves upon EIP-2612 by providing a single contract for managing token permissions across all ERC20 tokens, even those that don’t natively support permits. Contract Address: 0x000000000022D473030F116dDEE9F6B43aC78BA3 Deployment Status: Default preinstall Audit Reports: Multiple audits available

Key Benefits

  • Universal Compatibility: Works with any ERC20 token
  • Gas Savings: One-time approval to Permit2, then signature-based transfers
  • Enhanced Security: Granular permissions with expiration times
  • Better UX: Gasless approvals via signatures
  • Batching Support: Multiple token operations in one transaction

Core Concepts

Two-Step Process

  1. One-time Approval: User approves Permit2 to spend their tokens
  2. Signature Permits: User signs messages for specific transfers

Permission Types

  • AllowanceTransfer: Traditional allowance model with signatures
  • SignatureTransfer: Direct transfers using only signatures

Core Methods

Allowance Transfer

// Grant permission via signature
function permit(
    address owner,
    PermitSingle memory permitSingle,
    bytes calldata signature
) external

// Transfer with existing permission
function transferFrom(
    address from,
    address to,
    uint160 amount,
    address token
) external

Signature Transfer

// One-time transfer via signature
function permitTransferFrom(
    PermitTransferFrom memory permit,
    SignatureTransferDetails calldata transferDetails,
    address owner,
    bytes calldata signature
) external

Usage Examples

Basic Permit2 Integration

import { ethers } from "ethers";
import { AllowanceProvider, AllowanceTransfer } from "@uniswap/permit2-sdk";

const PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";

// Step 1: User approves Permit2 for token (one-time)
async function approvePermit2(tokenContract, signer) {
  const tx = await tokenContract.approve(
    PERMIT2_ADDRESS,
    ethers.MaxUint256
  );
  await tx.wait();
  console.log("Permit2 approved for token");
}

// Step 2: Create and sign permit
async function createPermit(token, spender, amount, deadline, signer) {
  const permit = {
    details: {
      token: token,
      amount: amount,
      expiration: deadline,
      nonce: 0, // Get current nonce from contract
    },
    spender: spender,
    sigDeadline: deadline,
  };

  // Create permit data
  const { domain, types, values } = AllowanceTransfer.getPermitData(
    permit,
    PERMIT2_ADDRESS,
    await signer.provider.getNetwork().then(n => n.chainId)
  );

  // Sign permit
  const signature = await signer._signTypedData(domain, types, values);

  return { permit, signature };
}

// Step 3: Execute transfer with permit
async function transferWithPermit(permit, signature, transferDetails) {
  const permit2 = new ethers.Contract(
    PERMIT2_ADDRESS,
    ["function transferFrom(address,address,uint160,address)"],
    signer
  );

  // First, submit the permit
  await permit2.permit(
    signer.address,
    permit,
    signature
  );

  // Then transfer
  await permit2.transferFrom(
    transferDetails.from,
    transferDetails.to,
    transferDetails.amount,
    transferDetails.token
  );
}

Batch Operations

Handle multiple tokens in one transaction:
async function batchTransferWithPermit2(transfers, owner, signer) {
  const permits = [];
  const signatures = [];

  // Prepare batch permits
  for (const transfer of transfers) {
    const permit = {
      details: {
        token: transfer.token,
        amount: transfer.amount,
        expiration: transfer.expiration,
        nonce: await getNonce(transfer.token, owner),
      },
      spender: transfer.spender,
      sigDeadline: transfer.deadline,
    };

    const signature = await signPermit(permit, signer);
    permits.push(permit);
    signatures.push(signature);
  }

  // Execute batch
  const permit2 = new ethers.Contract(PERMIT2_ADDRESS, abi, signer);
  await permit2.permitBatch(owner, permits, signatures);
}

Gasless Approvals

Enable gasless token approvals using meta-transactions:
// User signs permit off-chain
async function createGaslessPermit(token, spender, amount, signer) {
  const deadline = Math.floor(Date.now() / 1000) + 3600; // 1 hour

  const permit = {
    details: {
      token: token,
      amount: amount,
      expiration: deadline,
      nonce: 0,
    },
    spender: spender,
    sigDeadline: deadline,
  };

  const signature = await signPermit(permit, signer);

  // Return data for relayer
  return {
    permit,
    signature,
    owner: signer.address,
  };
}

// Relayer submits transaction
async function relayPermit(permitData, relayerSigner) {
  const permit2 = new ethers.Contract(PERMIT2_ADDRESS, abi, relayerSigner);

  const tx = await permit2.permit(
    permitData.owner,
    permitData.permit,
    permitData.signature
  );

  return tx.wait();
}

Advanced Features

Witness Data

Include additional data in permits for complex protocols:
struct PermitWitnessTransferFrom {
    TokenPermissions permitted;
    address spender;
    uint256 nonce;
    uint256 deadline;
    bytes32 witness; // Custom data hash
}

Nonce Management

Permit2 uses unordered nonces for flexibility:
// Invalidate specific nonces
await permit2.invalidateNonces(token, spender, newNonce);

// Invalidate nonce range
await permit2.invalidateUnorderedNonces(wordPos, mask);

Expiration Handling

Set appropriate expiration times:
const expirations = {
  shortTerm: Math.floor(Date.now() / 1000) + 300,      // 5 minutes
  standard: Math.floor(Date.now() / 1000) + 3600,      // 1 hour
  longTerm: Math.floor(Date.now() / 1000) + 86400,     // 1 day
  maximum: 2n ** 48n - 1n,                             // Max allowed
};

Security Considerations

Best Practices

  1. Validate Signatures: Always verify signature validity
  2. Check Expiration: Ensure permits haven’t expired
  3. Nonce Tracking: Prevent replay attacks
  4. Amount Limits: Set reasonable amount limits
  5. Deadline Checks: Validate signature deadlines

Common Attack Vectors

  • Signature Replay: Mitigated by nonces
  • Front-running: Use commit-reveal or deadlines
  • Phishing: Educate users about signing
  • Unlimited Approvals: Set specific amounts

Integration Patterns

DEX Integration

contract DEXWithPermit2 {
    function swapWithPermit(
        SwapParams calldata params,
        PermitSingle calldata permit,
        bytes calldata signature
    ) external {
        // Get tokens via permit
        permit2.permit(msg.sender, permit, signature);
        permit2.transferFrom(
            msg.sender,
            address(this),
            permit.details.amount,
            permit.details.token
        );

        // Execute swap
        _performSwap(params);
    }
}

Payment Processor

class PaymentProcessor {
  async processPayment(order, permit, signature) {
    // Verify order details
    if (!this.verifyOrder(order)) {
      throw new Error("Invalid order");
    }

    // Process payment via Permit2
    await this.permit2.permitTransferFrom(
      permit,
      {
        to: this.treasury,
        requestedAmount: order.amount,
      },
      order.buyer,
      signature
    );

    // Fulfill order
    await this.fulfillOrder(order);
  }
}

Comparison with Alternatives

FeaturePermit2EIP-2612Traditional Approve
Universal SupportYesNo (per-token)Yes
Gas for ApprovalOnce per tokenNone (signature)Every time
Granular ControlExcellentLimitedBasic
Batch OperationsYesNoNo
Signature RequiredYesYesNo

Common Issues

IssueSolution
”PERMIT_EXPIRED”Increase deadline or request new signature
”INVALID_SIGNATURE”Verify signer address and signature data
”INSUFFICIENT_ALLOWANCE”Ensure Permit2 is approved for token
”NONCE_ALREADY_USED”Use a new nonce for the permit

Libraries and SDKs

Further Reading