> ## Documentation Index
> Fetch the complete documentation index at: https://docs.li.fi/llms.txt
> Use this file to discover all available pages before exploring further.

# Permit & Permit2 Approval Flow

LI.FI supports three token approval strategies on EVM chains. In addition to the classic `approve()` transaction, integrators can use **EIP-2612 Permits** or **Uniswap Permit2** to authorize token transfers via off-chain signatures, reducing the number of on-chain transactions required.

<Tip>
  If you use the **LI.FI SDK** or **Widget**, Permit2 is handled automatically. This guide is for integrators building directly against the API who want to understand or implement the permit flow themselves.
</Tip>

## Overview of Approval Strategies

| Strategy                   | On-chain Transactions                                   | How It Works                                                                                                                                                                                                         |
| -------------------------- | ------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Classic `approve()`**    | 1 approval tx + 1 swap/bridge tx                        | User sends an ERC-20 `approve()` to the LI.FI Diamond, then submits the swap/bridge transaction.                                                                                                                     |
| **EIP-2612 Native Permit** | 1 swap/bridge tx only                                   | User signs an off-chain EIP-712 message. The signature is submitted alongside the swap calldata to the `Permit2Proxy`, which calls `permit()` on the token contract. Only works with tokens that implement EIP-2612. |
| **Uniswap Permit2**        | 1 one-time approval + 1 swap/bridge tx (signature only) | User approves the Permit2 contract once (unlimited). For each subsequent transaction, the user signs an off-chain EIP-712 message authorizing a specific transfer. Works with any ERC-20 token.                      |

### Why Permit2?

With the classic approval model, every new dApp interaction requires a separate `approve()` transaction. Permit2 replaces this with a single, one-time unlimited approval to the canonical Permit2 contract. All subsequent authorizations happen through gasless EIP-712 signatures with per-transfer granularity (exact amount, deadline, nonce).

## Architecture

All permit-based flows go through the **Permit2Proxy** periphery contract, which acts as an intermediary between the user and the LI.FI Diamond:

<img src="https://mintcdn.com/lifi/N2nNfjtFXlJg4AHt/images/permit2.png?fit=max&auto=format&n=N2nNfjtFXlJg4AHt&q=85&s=f802fd8b064b093a5b59cc5dd6d5a819" alt="Permit2 Architecture" width="2110" height="1346" data-path="images/permit2.png" />

### Key Addresses

| Contract            | Address                                      | Notes                                                                                                                                                                                 |
| ------------------- | -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **LI.FI Diamond**   | `0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE` | Most EVM chains. Some networks use a different address — always query `GET /v1/chains` or check [Smart Contract Addresses](/introduction/lifi-architecture/smart-contract-addresses). |
| **Uniswap Permit2** | `0x000000000022D473030F116dDEE9F6B43aC78BA3` | Most EVM chains. Several networks use a different deployment (e.g. zkSync, Abstract, Lens, Flare, Sophon, XDC) — always read `permit2` from `GET /v1/chains`.                         |
| **Permit2Proxy**    | Chain-specific                               | Query `GET /v1/chains` — each chain object includes `permit2` and `permit2Proxy` fields.                                                                                              |

### Discovering Addresses via the API

```ts theme={"system"}
const response = await fetch('https://li.quest/v1/chains');
const { chains } = await response.json();

const arbitrum = chains.find((c) => c.id === 42161);
console.log(arbitrum.permit2);      // "0x000000000022D473030F116dDEE9F6B43aC78BA3"
console.log(arbitrum.permit2Proxy); // chain-specific Permit2Proxy address
console.log(arbitrum.diamondAddress); // "0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE"
```

## API Flow: Permit2 (Standard Self-Execute)

This is the most common permit flow. It works with **any ERC-20 token** on chains where Permit2 is deployed.

<Steps>
  <Step title="Get a quote">
    Request a quote as usual. The response includes `estimate.approvalAddress` (the Diamond address for classic approve) and the chain metadata you need.

    ```ts theme={"system"}
    const quote = await fetch('https://li.quest/v1/quote?' + new URLSearchParams({
      fromChain: '42161',
      toChain: '10',
      fromToken: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831', // USDC on Arbitrum
      toToken: '0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85', // USDC on Optimism
      fromAmount: '10000000', // 10 USDC
      fromAddress: '0xYourWalletAddress',
    })).then(r => r.json());
    ```
  </Step>

  <Step title="Check and set Permit2 allowance (one-time)">
    The user needs to approve the **Permit2 contract** (not the Diamond) once. This only needs to happen if the user hasn't already approved Permit2 for this token.

    Resolve the Permit2 and Permit2Proxy addresses from the chains API — do not hardcode them, as several networks use non-canonical deployments.

    ```ts theme={"system"}
    import { createPublicClient, createWalletClient, http, maxUint256, parseAbi } from 'viem';
    import { arbitrum } from 'viem/chains';

    const publicClient = createPublicClient({ chain: arbitrum, transport: http() });
    const walletClient = createWalletClient({ chain: arbitrum, transport: http(), account });

    const chainsResponse = await fetch('https://li.quest/v1/chains').then(r => r.json());
    const fromChain = chainsResponse.chains.find((c) => c.id === quote.action.fromChainId);
    const permit2Address = fromChain.permit2;
    const permit2ProxyAddress = fromChain.permit2Proxy;

    const erc20Abi = parseAbi([
      'function allowance(address owner, address spender) view returns (uint256)',
      'function approve(address spender, uint256 amount) returns (bool)',
    ]);

    const tokenAddress = quote.action.fromToken.address;

    const allowance = await publicClient.readContract({
      address: tokenAddress,
      abi: erc20Abi,
      functionName: 'allowance',
      args: [account.address, permit2Address],
    });

    if (allowance < BigInt(quote.action.fromAmount)) {
      const hash = await walletClient.writeContract({
        address: tokenAddress,
        abi: erc20Abi,
        functionName: 'approve',
        args: [permit2Address, maxUint256],
      });
      await publicClient.waitForTransactionReceipt({ hash });
    }
    ```
  </Step>

  <Step title="Read the next available nonce">
    Permit2 uses unordered nonces. The `Permit2Proxy` contract provides a `nextNonce()` helper that finds the next unused nonce for the signer.

    ```ts theme={"system"}
    const permit2ProxyAbi = parseAbi([
      'function nextNonce(address owner) view returns (uint256)',
      'function callDiamondWithPermit2(bytes diamondCalldata, ((address token, uint256 amount) permitted, uint256 nonce, uint256 deadline) permit, bytes signature) external',
    ]);

    const nonce = await publicClient.readContract({
      address: permit2ProxyAddress,
      abi: permit2ProxyAbi,
      functionName: 'nextNonce',
      args: [account.address],
    });
    ```
  </Step>

  <Step title="Build and sign the PermitTransferFrom message">
    Construct the EIP-712 typed data for `PermitTransferFrom`. The `spender` is the **Permit2Proxy** (not the Diamond).

    ```ts theme={"system"}
    const deadline = BigInt(Math.floor(Date.now() / 1000) + 30 * 60); // 30 minutes

    const permitTransferFrom = {
      permitted: {
        token: tokenAddress,
        amount: BigInt(quote.action.fromAmount),
      },
      spender: permit2ProxyAddress,
      nonce,
      deadline,
    };

    const signature = await walletClient.signTypedData({
      account,
      primaryType: 'PermitTransferFrom',
      domain: {
        name: 'Permit2',
        chainId: arbitrum.id,
        verifyingContract: permit2Address,
      },
      types: {
        TokenPermissions: [
          { name: 'token', type: 'address' },
          { name: 'amount', type: 'uint256' },
        ],
        PermitTransferFrom: [
          { name: 'permitted', type: 'TokenPermissions' },
          { name: 'spender', type: 'address' },
          { name: 'nonce', type: 'uint256' },
          { name: 'deadline', type: 'uint256' },
        ],
      },
      message: permitTransferFrom,
    });
    ```
  </Step>

  <Step title="Encode and send the transaction to Permit2Proxy">
    Wrap the Diamond calldata inside a `callDiamondWithPermit2` call targeting the **Permit2Proxy**, not the Diamond.

    ```ts theme={"system"}
    import { encodeFunctionData } from 'viem';

    const diamondCalldata = quote.transactionRequest.data;

    const txData = encodeFunctionData({
      abi: permit2ProxyAbi,
      functionName: 'callDiamondWithPermit2',
      args: [
        diamondCalldata,
        [
          [permitTransferFrom.permitted.token, permitTransferFrom.permitted.amount],
          permitTransferFrom.nonce,
          permitTransferFrom.deadline,
        ],
        signature,
      ],
    });

    const txHash = await walletClient.sendTransaction({
      to: permit2ProxyAddress,
      data: txData,
      value: BigInt(quote.transactionRequest.value ?? 0),
      gasLimit: BigInt(quote.transactionRequest.gasLimit ?? 0),
    });
    ```
  </Step>

  <Step title="Track the transfer status">
    Track the transaction status as you normally would using the `/status` endpoint.

    ```ts theme={"system"}
    const getStatus = async (txHash) => {
      const result = await fetch(`https://li.quest/v1/status?txHash=${txHash}`);
      return result.json();
    };

    let status;
    do {
      status = await getStatus(txHash);
      if (status.status === 'PENDING') await new Promise(r => setTimeout(r, 5000));
    } while (status.status !== 'DONE' && status.status !== 'FAILED');
    ```
  </Step>
</Steps>

## API Flow: EIP-2612 Native Permit

EIP-2612 permits are only available for tokens that implement the `permit()` function (e.g., USDC, AAVE, UNI). No prior `approve()` transaction is needed at all.

<Warning>
  Not all tokens support EIP-2612. DAI uses a non-standard permit signature that LI.FI does not currently support. If the token does not implement EIP-2612, fall back to classic `approve()` or Permit2.
</Warning>

### Detecting EIP-2612 Support

There is no on-chain registry or ERC-165 interface for EIP-2612. The only reliable method is to probe the token contract for the required functions. If `nonces()` and `DOMAIN_SEPARATOR()` both return successfully, the token supports EIP-2612.

```ts theme={"system"}
const eip2612DetectAbi = parseAbi([
  'function nonces(address owner) view returns (uint256)',
  'function DOMAIN_SEPARATOR() view returns (bytes32)',
]);

async function supportsEIP2612(tokenAddress: string): Promise<boolean> {
  try {
    await Promise.all([
      publicClient.readContract({
        address: tokenAddress,
        abi: eip2612DetectAbi,
        functionName: 'nonces',
        args: [account.address],
      }),
      publicClient.readContract({
        address: tokenAddress,
        abi: eip2612DetectAbi,
        functionName: 'DOMAIN_SEPARATOR',
      }),
    ]);
    return true;
  } catch {
    return false;
  }
}
```

Tokens deployed with OpenZeppelin v4.9+ or v5.x also expose `eip712Domain()` ([EIP-5267](https://eips.ethereum.org/EIPS/eip-5267)), which returns all domain fields in a single call. For older tokens, read `name()`, `version()`, and `DOMAIN_SEPARATOR()` separately and recompute the separator to validate it.

<Steps>
  <Step title="Get a quote and retrieve diamond calldata">
    Same as the standard flow: request a quote, then use the `transactionRequest.data` as your diamond calldata.
  </Step>

  <Step title="Read the token's permit nonce">
    EIP-2612 tokens track nonces per-owner. Read the current nonce from the token contract.

    ```ts theme={"system"}
    const eip2612Abi = parseAbi([
      'function nonces(address owner) view returns (uint256)',
      'function name() view returns (string)',
      'function version() view returns (string)',
      'function DOMAIN_SEPARATOR() view returns (bytes32)',
    ]);

    const [nonce, name, version] = await Promise.all([
      publicClient.readContract({
        address: tokenAddress, abi: eip2612Abi,
        functionName: 'nonces', args: [account.address],
      }),
      publicClient.readContract({
        address: tokenAddress, abi: eip2612Abi, functionName: 'name',
      }),
      publicClient.readContract({
        address: tokenAddress, abi: eip2612Abi, functionName: 'version',
      }),
    ]);
    ```
  </Step>

  <Step title="Sign the EIP-2612 Permit message">
    The `spender` is the **Permit2Proxy** address.

    ```ts theme={"system"}
    const deadline = BigInt(Math.floor(Date.now() / 1000) + 30 * 60);

    const permitSignature = await walletClient.signTypedData({
      account,
      primaryType: 'Permit',
      domain: {
        name,
        version,
        chainId: arbitrum.id,
        verifyingContract: tokenAddress,
      },
      types: {
        Permit: [
          { name: 'owner', type: 'address' },
          { name: 'spender', type: 'address' },
          { name: 'value', type: 'uint256' },
          { name: 'nonce', type: 'uint256' },
          { name: 'deadline', type: 'uint256' },
        ],
      },
      message: {
        owner: account.address,
        spender: permit2ProxyAddress,
        value: BigInt(quote.action.fromAmount),
        nonce,
        deadline,
      },
    });
    ```
  </Step>

  <Step title="Encode and send via Permit2Proxy">
    ```ts theme={"system"}
    import { parseSignature, encodeFunctionData } from 'viem';

    const { v, r, s } = parseSignature(permitSignature);

    const permit2ProxyEip2612Abi = parseAbi([
      'function callDiamondWithEIP2612Signature(address tokenAddress, uint256 amount, uint256 deadline, uint8 v, bytes32 r, bytes32 s, bytes diamondCalldata) external payable',
    ]);

    const txData = encodeFunctionData({
      abi: permit2ProxyEip2612Abi,
      functionName: 'callDiamondWithEIP2612Signature',
      args: [
        tokenAddress,
        BigInt(quote.action.fromAmount),
        deadline,
        Number(v),
        r,
        s,
        quote.transactionRequest.data,
      ],
    });

    const txHash = await walletClient.sendTransaction({
      to: permit2ProxyAddress,
      data: txData,
      value: BigInt(quote.transactionRequest.value ?? 0),
    });
    ```
  </Step>
</Steps>

## SDK Usage

The `@lifi/sdk` handles Permit2 automatically during route execution. No manual signature construction is needed.

```ts theme={"system"}
import { createConfig, EVM, executeRoute } from '@lifi/sdk';
import { createWalletClient, http } from 'viem';
import { arbitrum } from 'viem/chains';

createConfig({
  integrator: 'your-integrator-id',
  providers: [
    EVM({
      getWalletClient: () => Promise.resolve(walletClient),
    }),
  ],
});

// The SDK automatically:
// 1. Checks if Permit2 is deployed on the source chain
// 2. Approves the Permit2 contract if needed (one-time)
// 3. Signs a PermitTransferFrom message per transaction
// 4. Encodes the calldata for Permit2Proxy
await executeRoute({ route });
```

To disable Permit2 and force classic `approve()` transactions:

```ts theme={"system"}
await executeRoute({
  route,
  executionOptions: {
    disableMessageSigning: true,
  },
});
```

## When Permit2 Is Not Used

The SDK skips Permit2 and falls back to classic `approve()` when:

* The source chain does not have Permit2 deployed (`chain.permit2` is not set)
* The source chain does not have a Permit2Proxy (`chain.permit2Proxy` is not set)
* The source token is the chain's native token (ETH, MATIC, etc.)
* Message signing is disabled (`disableMessageSigning: true`)
* The transaction uses batched execution (EIP-5792)
* The step's estimate has `skipApproval: true` or `skipPermit: true` (rare, optional fields only present on certain chain-specific steps such as Hyperliquid)

## Reference

### Permit2Proxy Contract Functions

| Function                                                                             | Description                                                                                            |
| ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------ |
| `callDiamondWithPermit2(diamondCalldata, permit, signature)`                         | Transfers tokens via Permit2 `permitTransferFrom`, approves the Diamond, and forwards calldata.        |
| `callDiamondWithEIP2612Signature(token, amount, deadline, v, r, s, diamondCalldata)` | Calls `permit()` on the EIP-2612 token, transfers tokens, approves the Diamond, and forwards calldata. |
| `nextNonce(owner)`                                                                   | Returns the next available Permit2 nonce for the given address.                                        |

### EIP-712 Type Definitions

**PermitTransferFrom** (Permit2 standard flow):

```json theme={"system"}
{
  "TokenPermissions": [
    { "name": "token", "type": "address" },
    { "name": "amount", "type": "uint256" }
  ],
  "PermitTransferFrom": [
    { "name": "permitted", "type": "TokenPermissions" },
    { "name": "spender", "type": "address" },
    { "name": "nonce", "type": "uint256" },
    { "name": "deadline", "type": "uint256" }
  ]
}
```

**Permit** (EIP-2612 native permit):

```json theme={"system"}
{
  "Permit": [
    { "name": "owner", "type": "address" },
    { "name": "spender", "type": "address" },
    { "name": "value", "type": "uint256" },
    { "name": "nonce", "type": "uint256" },
    { "name": "deadline", "type": "uint256" }
  ]
}
```

### EIP-712 Domain

**Permit2** (for `PermitTransferFrom`):

```json theme={"system"}
{
  "name": "Permit2",
  "chainId": "<source chain ID>",
  "verifyingContract": "<Permit2 contract address>"
}
```

**EIP-2612** (for native `Permit` -- domain varies per token):

```json theme={"system"}
{
  "name": "<token name>",
  "version": "<token version>",
  "chainId": "<source chain ID>",
  "verifyingContract": "<token contract address>"
}
```
