Skip to main content

Permit2 Contract

Permit2 is the shared token approval manager. Users approve tokens to Permit2 once, then grant scoped permissions to specific spenders. For the concept overview, see Permit2 Concepts.

Key Functions

approve

Grants a spender permission to transfer a specific token on behalf of the caller via Permit2.
function approve(
    address token,
    address spender,
    uint160 amount,
    uint48 expiration
) external;
ParameterTypeDescription
tokenaddressThe ERC-20 token to approve
spenderaddressThe address being granted permission (e.g., Universal Router)
amountuint160Maximum amount the spender can transfer
expirationuint48Unix timestamp when the permission expires
Example:
const permit2 = new ethers.Contract(PERMIT2_ADDRESS, PERMIT2_ABI, signer);

await permit2.approve(
  tokenAddress,
  UNIVERSAL_ROUTER,
  ethers.MaxUint160,                           // Max amount
  Math.floor(Date.now() / 1000) + 365 * 86400  // 1 year expiration
);

allowance

Queries the current permission for a given owner-token-spender tuple.
function allowance(
    address owner,
    address token,
    address spender
) external view returns (
    uint160 amount,
    uint48 expiration,
    uint48 nonce
);
ReturnTypeDescription
amountuint160Remaining allowed amount
expirationuint48Unix timestamp of expiration
nonceuint48Current nonce for this permission
Example:
const [amount, expiration, nonce] = await permit2.allowance(
  userAddress,
  tokenAddress,
  UNIVERSAL_ROUTER
);

const needsApproval = amount < requiredAmount || expiration < Math.floor(Date.now() / 1000);

ABI

[
  "function approve(address token, address spender, uint160 amount, uint48 expiration)",
  "function allowance(address owner, address token, address spender) view returns (uint160 amount, uint48 expiration, uint48 nonce)"
]

Integration Pattern

async function ensurePermit2Approval(signer, tokenAddress, spender, requiredAmount) {
  const permit2 = new ethers.Contract(PERMIT2_ADDRESS, PERMIT2_ABI, signer);
  const owner = await signer.getAddress();

  // 1. Check existing Permit2 sub-approval
  const [amount, expiration] = await permit2.allowance(owner, tokenAddress, spender);
  const now = Math.floor(Date.now() / 1000);

  if (amount >= requiredAmount && expiration > now) {
    return; // Already approved
  }

  // 2. Check ERC-20 approval to Permit2
  const token = new ethers.Contract(tokenAddress, ERC20_ABI, signer);
  const tokenAllowance = await token.allowance(owner, PERMIT2_ADDRESS);

  if (tokenAllowance < requiredAmount) {
    const tx = await token.approve(PERMIT2_ADDRESS, ethers.MaxUint256);
    await tx.wait();
  }

  // 3. Grant Permit2 sub-approval
  const tx = await permit2.approve(
    tokenAddress,
    spender,
    ethers.MaxUint160,
    now + 365 * 86400
  );
  await tx.wait();
}