> ## 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.

# Token Approvals

> How to check and set token allowances before executing transfers

Before executing a transfer with an ERC20 token, you must approve the LI.FI contract to spend tokens on behalf of the user. This page explains when approval is needed and how to handle it correctly.

## When Is Approval Needed?

| Token Type                      | Approval Required | How to Identify                                         |
| ------------------------------- | ----------------- | ------------------------------------------------------- |
| Native token (ETH, MATIC, etc.) | **No**            | Address is `0x0000000000000000000000000000000000000000` |
| ERC20 token (USDC, USDT, etc.)  | **Yes**           | Any other address                                       |

### Decision Logic

```
IF fromToken.address === "0x0000000000000000000000000000000000000000"
  → Skip approval (native token)

ELSE
  → Check allowance and approve if needed
```

***

## Step 1: Identify the Spender

The spender address comes from the quote response:

```json theme={"system"}
{
  "estimate": {
    "approvalAddress": "0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE"
  }
}
```

<Warning>
  Always use `estimate.approvalAddress` from the quote response. Never hardcode spender addresses - they may vary by route or bridge.
</Warning>

***

## Step 2: Check Current Allowance

Call the `allowance` function on the token contract:

### Function Signature

```solidity theme={"system"}
function allowance(address owner, address spender) external view returns (uint256)
```

### JavaScript Example

```javascript theme={"system"}
import { createPublicClient, http, erc20Abi } from 'viem';
import { mainnet } from 'viem/chains';

const client = createPublicClient({
  chain: mainnet,
  transport: http()
});

async function checkAllowance(tokenAddress, ownerAddress, spenderAddress) {
  const allowance = await client.readContract({
    address: tokenAddress,
    abi: erc20Abi,
    functionName: 'allowance',
    args: [ownerAddress, spenderAddress]
  });
  
  return allowance;
}

// Usage
const currentAllowance = await checkAllowance(
  quote.action.fromToken.address,  // Token contract
  quote.action.fromAddress,         // User's wallet
  quote.estimate.approvalAddress    // LI.FI spender
);
```

### Python Example

```python theme={"system"}
from web3 import Web3

# ERC20 ABI (minimal)
ERC20_ABI = [
    {
        "constant": True,
        "inputs": [
            {"name": "owner", "type": "address"},
            {"name": "spender", "type": "address"}
        ],
        "name": "allowance",
        "outputs": [{"name": "", "type": "uint256"}],
        "type": "function"
    }
]

def check_allowance(web3, token_address, owner, spender):
    contract = web3.eth.contract(address=token_address, abi=ERC20_ABI)
    return contract.functions.allowance(owner, spender).call()
```

***

## Step 3: Compare and Decide

```javascript theme={"system"}
const fromAmount = BigInt(quote.action.fromAmount);
const currentAllowance = await checkAllowance(...);

if (currentAllowance >= fromAmount) {
  // Sufficient allowance - proceed to execution
  console.log('Allowance sufficient, skipping approval');
} else {
  // Need to approve
  console.log('Approval needed');
  await sendApproval(...);
}
```

***

## Step 4: Send Approval Transaction

### Function Signature

```solidity theme={"system"}
function approve(address spender, uint256 amount) external returns (bool)
```

### Approval Amount Strategies

| Strategy  | Amount              | Pros                       | Cons                                   |
| --------- | ------------------- | -------------------------- | -------------------------------------- |
| Exact     | `fromAmount`        | Most secure                | Requires approval for every transfer   |
| Unlimited | `type(uint256).max` | One-time approval          | Higher risk if contract is compromised |
| Buffer    | `fromAmount * 2`    | Balance of security and UX | May still need re-approval             |

<Tip>
  For AI agents, **exact approval** is recommended for security. For user-facing apps, consider letting users choose.
</Tip>

### JavaScript Example

```javascript theme={"system"}
import { createWalletClient, http, erc20Abi, maxUint256 } from 'viem';

async function approveToken(walletClient, tokenAddress, spenderAddress, amount) {
  const hash = await walletClient.writeContract({
    address: tokenAddress,
    abi: erc20Abi,
    functionName: 'approve',
    args: [spenderAddress, amount]
  });
  
  // Wait for transaction confirmation
  const receipt = await publicClient.waitForTransactionReceipt({ hash });
  
  if (receipt.status !== 'success') {
    throw new Error('Approval transaction failed');
  }
  
  return receipt;
}

// Exact approval
await approveToken(
  walletClient,
  quote.action.fromToken.address,
  quote.estimate.approvalAddress,
  BigInt(quote.action.fromAmount)
);

// Or unlimited approval
await approveToken(
  walletClient,
  quote.action.fromToken.address,
  quote.estimate.approvalAddress,
  maxUint256
);
```

### Python Example

```python theme={"system"}
def approve_token(web3, token_address, spender, amount, private_key):
    contract = web3.eth.contract(address=token_address, abi=ERC20_ABI)
    
    # Build transaction
    tx = contract.functions.approve(spender, amount).build_transaction({
        'from': account.address,
        'nonce': web3.eth.get_transaction_count(account.address),
        'gas': 100000,
        'gasPrice': web3.eth.gas_price
    })
    
    # Sign and send
    signed = web3.eth.account.sign_transaction(tx, private_key)
    tx_hash = web3.eth.send_raw_transaction(signed.rawTransaction)
    
    # Wait for confirmation
    receipt = web3.eth.wait_for_transaction_receipt(tx_hash)
    
    if receipt['status'] != 1:
        raise Exception('Approval failed')
    
    return receipt
```

***

## Complete Approval Flow

```javascript theme={"system"}
async function ensureApproval(quote, walletClient, publicClient) {
  const { fromToken, fromAmount, fromAddress } = quote.action;
  const { approvalAddress } = quote.estimate;
  
  // Step 1: Check if native token
  const NATIVE_ADDRESS = '0x0000000000000000000000000000000000000000';
  if (fromToken.address.toLowerCase() === NATIVE_ADDRESS) {
    console.log('Native token - no approval needed');
    return null;
  }
  
  // Step 2: Check current allowance
  const currentAllowance = await publicClient.readContract({
    address: fromToken.address,
    abi: erc20Abi,
    functionName: 'allowance',
    args: [fromAddress, approvalAddress]
  });
  
  // Step 3: Compare
  const requiredAmount = BigInt(fromAmount);
  if (currentAllowance >= requiredAmount) {
    console.log('Sufficient allowance exists');
    return null;
  }
  
  // Step 4: Send approval
  console.log('Sending approval transaction...');
  const hash = await walletClient.writeContract({
    address: fromToken.address,
    abi: erc20Abi,
    functionName: 'approve',
    args: [approvalAddress, requiredAmount]
  });
  
  const receipt = await publicClient.waitForTransactionReceipt({ hash });
  console.log(`Approval confirmed: ${receipt.transactionHash}`);
  
  return receipt;
}
```

***

## Common Mistakes

<AccordionGroup>
  <Accordion title="Using wrong spender address">
    Always use `quote.estimate.approvalAddress`, not a hardcoded address. Different routes may use different contracts.
  </Accordion>

  <Accordion title="Not waiting for approval confirmation">
    The main transaction will fail if sent before the approval is confirmed. Always wait for the approval receipt.
  </Accordion>

  <Accordion title="Approving on wrong chain">
    Ensure your wallet is connected to the source chain (`fromChain`) when sending the approval.
  </Accordion>

  <Accordion title="Insufficient gas for approval">
    Approval transactions typically need 50,000-100,000 gas. Use a reasonable gas limit.
  </Accordion>

  <Accordion title="USDT special case">
    USDT requires setting allowance to 0 before setting a new non-zero value. Check current allowance and reset if needed.

    ```javascript theme={"system"}
    // USDT special handling
    if (tokenSymbol === 'USDT' && currentAllowance > 0) {
      await approveToken(tokenAddress, spender, 0n); // Reset to 0
      await approveToken(tokenAddress, spender, amount); // Then set new amount
    }
    ```
  </Accordion>
</AccordionGroup>

***

## Approval Checklist

Before executing a transfer, verify:

* [ ] `fromToken.address` is NOT `0x0000...0000` (native token)
* [ ] Retrieved `approvalAddress` from quote response
* [ ] Checked current `allowance(owner, spender)`
* [ ] Compared allowance to `fromAmount`
* [ ] Sent approval tx if allowance \< fromAmount
* [ ] Waited for approval confirmation
* [ ] Connected to correct chain (`fromChain`)

***

## Related Pages

* [Execution](/agents/workflows/execution) - How to execute the main transaction
* [Five-Call Recipe](/agents/quick-start/five-call-recipe) - Complete workflow overview
