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

# ERC-20 approvals

> How to grant the O1Router permission to spend your tokens — including the gasless EIP-2612 permit flow.

Before the router can pull `tokenIn` from a user, the user must approve the router as a spender. This is standard ERC-20 behavior. The aggregator supports two patterns:

1. **Standard `approve` + swap** — two transactions, but works for any ERC-20.
2. **EIP-2612 permit + swap** — one transaction, but only works for tokens that implement the permit extension.

<Note>
  No approval is needed when `tokenIn` is native ETH (using the sentinel `0xEeee...EEeE` or `0x0`). In that case, the router pulls value via `msg.value`. See [Native ETH](/api/dex-aggregator/guides/native-eth).
</Note>

## Pattern 1: standard `approve`

This is the universal fallback. Two transactions per first swap, then no approvals on subsequent swaps if the previous allowance is large enough.

```ts theme={null} theme={null}
import { erc20Abi } from "viem";

const ROUTER = "0x8Dc736C6BA12e1Df1c73DB6BED8d7573F0aD90B3";

// 1. Read current allowance
const allowance = await publicClient.readContract({
  address: tokenIn,
  abi: erc20Abi,
  functionName: "allowance",
  args: [walletAddress, ROUTER],
});

// 2. Approve if insufficient
if (allowance < BigInt(amountIn)) {
  const approveHash = await walletClient.writeContract({
    address: tokenIn,
    abi: erc20Abi,
    functionName: "approve",
    args: [ROUTER, BigInt(amountIn)],
  });
  await publicClient.waitForTransactionReceipt({ hash: approveHash });
}

// 3. Now safe to call /submit and broadcast the swap
```

### Approve amount strategies

<Tabs>
  <Tab title="Approve exact amount (recommended)">
    Approve exactly `amountIn`. Re-approves on every swap. Safest from a security standpoint — the router only ever has rights to the exact amount being swapped.

    ```ts theme={null} theme={null}
    args: [ROUTER, BigInt(amountIn)],
    ```
  </Tab>

  <Tab title="Approve max (least friction)">
    Approve `2^256 - 1`. User signs an approve once and never sees an approve dialog again for that token.

    ```ts theme={null} theme={null}
    const MAX_UINT256 = (1n << 256n) - 1n;
    args: [ROUTER, MAX_UINT256],
    ```

    <Warning>
      Max allowance means the router has perpetual rights to spend that token. Any future bug or compromise affects every wallet that approved max. Most consumer swap UIs do this; high-value flows usually don't.
    </Warning>
  </Tab>

  <Tab title="Approve a buffered amount">
    Approve `amountIn × 1.1` or some user-configured tier (e.g. "approve enough for 10 swaps"). Compromise between friction and risk.
  </Tab>
</Tabs>

## Pattern 2: EIP-2612 permit (one transaction)

For tokens that implement EIP-2612 (USDC and a growing number of others on Base), the user can sign an off-chain permit that the router consumes inside the same transaction as the swap. Net effect: **one signature, one transaction, no `approve` step**.

### How it works

<Steps>
  <Step title="Build the permit typed data">
    Read the token's `name`, `EIP712_VERSION`, and the user's `nonces(user)`. Construct the EIP-712 domain and message.
  </Step>

  <Step title="User signs the permit off-chain">
    Use `signTypedData` (eth\_signTypedData\_v4). No gas, no on-chain state change.
  </Step>

  <Step title="Send the permit on /submit">
    Pass `{ value, deadline, v, r, s }` in the `permit` field on `POST /submit`.
  </Step>

  <Step title="Router consumes it atomically">
    `O1Router.swapExactIn(...)` calls `IERC20Permit(token).permit(...)` then pulls funds, all in one tx.
  </Step>
</Steps>

### Example

```ts theme={null} theme={null}
import { signTypedData } from "viem/actions";

const PERMIT_TYPES = {
  Permit: [
    { name: "owner", type: "address" },
    { name: "spender", type: "address" },
    { name: "value", type: "uint256" },
    { name: "nonce", type: "uint256" },
    { name: "deadline", type: "uint256" },
  ],
};

async function buildPermit(token: `0x${string}`, owner: `0x${string}`, value: bigint) {
  const [name, nonce, chainId] = await Promise.all([
    publicClient.readContract({ address: token, abi: erc20PermitAbi, functionName: "name" }),
    publicClient.readContract({ address: token, abi: erc20PermitAbi, functionName: "nonces", args: [owner] }),
    publicClient.getChainId(),
  ]);

  const deadline = Math.floor(Date.now() / 1000) + 600; // 10 minutes

  const signature = await walletClient.signTypedData({
    account: owner,
    domain: { name, version: "1", chainId, verifyingContract: token },
    types: PERMIT_TYPES,
    primaryType: "Permit",
    message: {
      owner,
      spender: ROUTER,
      value,
      nonce,
      deadline: BigInt(deadline),
    },
  });

  // Split into v / r / s
  const r = `0x${signature.slice(2, 66)}` as const;
  const s = `0x${signature.slice(66, 130)}` as const;
  const v = parseInt(signature.slice(130, 132), 16);

  return { value: value.toString(), deadline, v, r, s };
}
```

Then on `/submit`:

```ts theme={null} theme={null}
const permit = await buildPermit(tokenIn, walletAddress, BigInt(amountIn));

const submit = await fetch(`${API_URL}/submit`, {
  method: "POST",
  headers: { "x-api-key": API_KEY, "content-type": "application/json" },
  body: JSON.stringify({
    quoteId: quote.quoteId,
    user: walletAddress,
    permit,
  }),
}).then((r) => r.json());

const txHash = await walletClient.sendTransaction({
  to: submit.to as `0x${string}`,
  data: submit.data as `0x${string}`,
  value: BigInt(submit.value),
});
```

<Tip>
  Cache `name` and the EIP-712 domain per token to avoid an RPC call on every swap. Don't cache `nonce` though; it changes after every permit consumption.
</Tip>

## Detecting permit support

Not every token implements EIP-2612. The cheapest way to detect support is to read `nonces(user)` on the token and treat a successful read as a positive signal. Tokens without permit will revert.

```ts theme={null} theme={null}
async function supportsPermit(token: `0x${string}`, user: `0x${string}`): Promise<boolean> {
  try {
    await publicClient.readContract({
      address: token,
      abi: [{ name: "nonces", type: "function", stateMutability: "view", inputs: [{ name: "owner", type: "address" }], outputs: [{ type: "uint256" }] }],
      functionName: "nonces",
      args: [user],
    });
    return true;
  } catch {
    return false;
  }
}
```

For better UX, fall back to standard `approve` automatically when permit support is missing.

## Security checklist

<CardGroup cols={2}>
  <Card title="Always approve the router only" icon="shield">
    The approval target is **always** the canonical `O1Router` address: `0x8Dc736C6BA12e1Df1c73DB6BED8d7573F0aD90B3`. Reject any UI that asks you to approve a different address while using this API.
  </Card>

  <Card title="Verify the spender on the response" icon="circle-check">
    `submit.to` should equal the router address. If it doesn't, abort and report.
  </Card>

  <Card title="Respect deadlines on permits" icon="clock">
    Use a short deadline (5 to 15 minutes). Don't sign permits with deadlines hours in the future — they're a long-lived approval risk if the signature leaks.
  </Card>

  <Card title="Don't reuse permits" icon="ban">
    Each permit signature consumes a `nonce`. Don't try to bundle multiple swaps under a single permit.
  </Card>
</CardGroup>
