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

# ERC20 Token Approvals

> Handle ERC20 token approvals for cross-chain swaps

When swapping ERC20 tokens (like USDC, USDT, DAI), you need to approve the protocol contract to spend your tokens before the actual swap. This guide explains the two-transaction approval flow and common edge cases.

## Why Approvals Are Needed

ERC20 tokens require explicit approval before a smart contract can transfer them on your behalf. This is a security feature that prevents contracts from moving your tokens without permission.

<Info>
  **Native tokens like ETH don't require approval** - Only ERC20 tokens need the approval step.
</Info>

## Two-Transaction Flow

When depositing ERC20 tokens, LeoKit returns **two unsigned transactions**:

1. **Approval Transaction** - Authorizes the protocol contract to spend your tokens
2. **Deposit Transaction** - Transfers tokens to initiate the swap

Both must be sent sequentially, with the approval transaction confirmed before the deposit.

## Example: ETH USDC → BTC

Let's swap 2000 USDC on Ethereum to BTC.

### Step 1: Get Quote

```javascript theme={null}
const quote = await fetch(
  "https://api.leodex.io/leokit/quote?" +
    new URLSearchParams({
      from_asset: "ETH.USDC-0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
      to_asset: "BTC.BTC",
      amount: "2000000000", // 2000 USDC (6 decimals)
      destination: "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh",
      origin: "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
    }),
  { headers: { "Api-Key": "your_api_key_here" } }
).then((r) => r.json());
```

### Step 2: Generate Deposit (Returns 2 Transactions)

```javascript theme={null}
const deposit = await fetch("https://api.leodex.io/leokit/deposit", {
  method: "POST",
  headers: {
    "Api-Key": "your_api_key_here",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    quote_id: quote.quote_id,
    selected_protocol: "thorchain",
  }),
}).then((r) => r.json());

console.log("Number of transactions:", deposit.unsigned_transactions.length);
// Output: 2
```

**Response Structure:**

```json theme={null}
{
  "type": "EVM",
  "protocol": "thorchain",
  "quote_id": "01936b4a-7c8e-7890-abcd-ef1234567890",
  "unsigned_transactions": [
    {
      // Transaction #1: Approval
      "to": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
      "data": "0x095ea7b3...",
      "value": "0x0",
      "chainId": 1
    },
    {
      // Transaction #2: Deposit
      "to": "0xD37BbE5744D730a1d98d8DC97c42F0Ca46aD7146",
      "data": "0x1fece7b4...",
      "value": "0x0",
      "chainId": 1
    }
  ]
}
```

### Step 3: Sign & Send Both Transactions

<Warning>
  **Critical:** Wait for the approval transaction to be confirmed before sending the deposit transaction. Sending them simultaneously will cause the deposit to fail.
</Warning>

```javascript theme={null}
import { ethers } from "ethers";

const provider = new ethers.providers.Web3Provider(window.ethereum);
const signer = provider.getSigner();

// Send approval transaction first
const approvalTx = await signer.sendTransaction(
  deposit.unsigned_transactions[0]
);
console.log("Approval TX sent:", approvalTx.hash);

// Wait for confirmation (1-2 blocks recommended)
await approvalTx.wait();
console.log("Approval confirmed!");

// Then send deposit transaction
const depositTx = await signer.sendTransaction(
  deposit.unsigned_transactions[1]
);
console.log("Deposit TX sent:", depositTx.hash);

const receipt = await depositTx.wait();
console.log("Deposit confirmed!");

// Save the deposit transaction hash
await fetch("https://api.leodex.io/leokit/save-transaction", {
  method: "POST",
  headers: {
    "Api-Key": "your_api_key_here",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    quote_id: quote.quote_id,
    tx_hash: depositTx.hash,
  }),
});
```

## Understanding Approval Amounts

LeoKit approves the **exact swap amount** by default. This is more secure than infinite approvals but requires a new approval for each swap.

### Exact Amount Approval (Default)

```solidity theme={null}
// Approves exactly 2000 USDC
approve(routerAddress, 2000000000)
```

**Pros:**

* More secure (limits exposure)
* User-friendly (clear intent)

**Cons:**

* Requires approval for every swap
* Higher gas costs over time

### Infinite Approval (Not Recommended)

Some protocols support infinite approvals, but LeoKit doesn't use them for security reasons:

```solidity theme={null}
// NOT used by LeoKit
approve(routerAddress, type(uint256).max)
```

## USDT Edge Case: Approval Reset

<Warning>
  **USDT requires allowance reset to zero** before changing approval amounts. This is a quirk of the USDT token contract.
</Warning>

If you're swapping USDT and have an existing approval, LeoKit will return **three transactions**:

1. **Reset Approval** - Set allowance to 0
2. **New Approval** - Set allowance to swap amount
3. **Deposit** - Execute the swap

```javascript theme={null}
// USDT swap may return 3 transactions
const deposit = await fetch("https://api.leodex.io/leokit/deposit", {
  method: "POST",
  headers: {
    "Api-Key": "your_api_key_here",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    quote_id: quote.quote_id,
    selected_protocol: "thorchain",
  }),
}).then((r) => r.json());

if (deposit.unsigned_transactions.length === 3) {
  console.log("USDT detected - requires approval reset");

  // 1. Reset to zero
  const resetTx = await signer.sendTransaction(
    deposit.unsigned_transactions[0]
  );
  await resetTx.wait();

  // 2. Approve new amount
  const approveTx = await signer.sendTransaction(
    deposit.unsigned_transactions[1]
  );
  await approveTx.wait();

  // 3. Deposit
  const depositTx = await signer.sendTransaction(
    deposit.unsigned_transactions[2]
  );
  await depositTx.wait();
}
```

## Checking Current Allowance

You can check if an approval already exists using ethers.js:

```javascript theme={null}
const tokenContract = new ethers.Contract(
  "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC address
  ["function allowance(address owner, address spender) view returns (uint256)"],
  provider
);

const currentAllowance = await tokenContract.allowance(
  "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb", // Your address
  "0xD37BbE5744D730a1d98d8DC97c42F0Ca46aD7146"  // Router address
);

console.log("Current allowance:", currentAllowance.toString());
```

## Gas Optimization

Approvals cost gas. Here are strategies to minimize costs:

<AccordionGroup>
  <Accordion title="Batch Multiple Swaps">
    If you're doing multiple swaps of the same token, consider approving a larger amount once instead of per-swap approvals. **Note:** LeoKit doesn't currently support this, but you can manually approve higher amounts.
  </Accordion>

  <Accordion title="Use EIP-2612 Permits (Advanced)">
    Some tokens support gasless approvals via EIP-2612 permits. This requires signature-based approvals instead of transactions. Not yet supported by LeoKit.
  </Accordion>

  <Accordion title="Monitor Gas Prices">
    Submit approval transactions during low gas periods (typically weekends) to save costs.
  </Accordion>
</AccordionGroup>

## Error Handling

Common approval errors and solutions:

| Error                     | Cause                          | Solution                                  |
| ------------------------- | ------------------------------ | ----------------------------------------- |
| `Insufficient allowance`  | Approval not confirmed yet     | Wait for approval TX confirmation         |
| `Execution reverted`      | Insufficient token balance     | Check token balance includes amount + gas |
| `Transaction underpriced` | Gas price too low              | Increase gas price                        |
| `Nonce too low`           | Transactions sent out of order | Ensure sequential nonce management        |

## Best Practices

<Check>
  **Always wait for approval confirmation** - Don't send the deposit transaction until the approval is confirmed on-chain.
</Check>

<Check>
  **Handle USDT specially** - Check for 3-transaction responses when swapping USDT.
</Check>

<Check>
  **Show clear UI feedback** - Inform users they'll need to sign 2+ transactions before starting.
</Check>

<Check>
  **Estimate total gas costs** - Calculate approval + deposit gas costs and show users upfront.
</Check>

<Check>
  **Support transaction resumption** - If approval succeeds but deposit fails, allow users to retry just the deposit.
</Check>

## Next Steps

<CardGroup cols="2">
  <Card title="Complete Swap Flow" icon="arrow-right-arrow-left" href="/guides/complete-swap-flow">
    See the full swap workflow from quote to completion
  </Card>

  <Card title="Error Debugging" icon="bug" href="/guides/error-debugging">
    Debug failed approvals using trace IDs
  </Card>
</CardGroup>
