Skip to main content

Overview

Before swapping ERC-20 tokens, you need to approve the DEX contract to spend your tokens. This is a required step for all ERC-20 token swaps on EVM-compatible chains.
Native tokens (ETH, BNB, MATIC, etc.) do not require approval. Only ERC-20 tokens need approval before swapping.

When Approvals Are Needed

Token approvals are required when:
  • Swapping FROM an ERC-20 token (USDC, USDT, DAI, etc.)
  • First time using a token with the OKX DEX aggregator
  • Increasing approval amount beyond the current allowance
Approvals are NOT required when:
  • Swapping FROM native tokens (ETH, BNB, MATIC)
  • Token already has sufficient allowance

Basic Token Approval

1

Initialize Client with Wallet

Token approvals require a configured EVM wallet.
import { OKXDexClient } from '@okxweb3/dex-sdk';
import { ethers } from 'ethers';
import { createEVMWallet } from '@okxweb3/dex-sdk';

const provider = new ethers.JsonRpcProvider(process.env.EVM_RPC_URL);
const evmWallet = createEVMWallet(process.env.EVM_PRIVATE_KEY, provider);

const client = new OKXDexClient({
  apiKey: process.env.OKX_API_KEY!,
  secretKey: process.env.OKX_SECRET_KEY!,
  apiPassphrase: process.env.OKX_API_PASSPHRASE!,
  projectId: process.env.OKX_PROJECT_ID!,
  evm: {
    wallet: evmWallet
  }
});
2

Execute Token Approval

const result = await client.dex.executeApproval({
  chainIndex: '8453',                                           // Base mainnet
  tokenContractAddress: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', // USDC on Base
  approveAmount: '1000000000'                                   // 1000 USDC (6 decimals)
});

console.log('Approval successful!');
console.log('Transaction Hash:', result.transactionHash);
console.log('Explorer URL:', result.explorerUrl);

Handling Approval Amounts

Converting Human-Readable Amounts

Tokens have different decimal places. Always convert amounts to base units:
function toBaseUnits(amount: string, decimals: number): string {
  const [integerPart, decimalPart = ''] = amount.split('.');
  const currentDecimals = decimalPart.length;
  
  let result = integerPart + decimalPart;
  
  if (currentDecimals < decimals) {
    result = result + '0'.repeat(decimals - currentDecimals);
  } else if (currentDecimals > decimals) {
    result = result.slice(0, result.length - (currentDecimals - decimals));
  }
  
  return result.replace(/^0+/, '') || '0';
}

// Example: Approve 1000 USDC (6 decimals)
const amount = toBaseUnits('1000', 6); // Returns '1000000000'

Getting Token Decimals

Use the quote endpoint to get token information:
const tokenInfo = await client.dex.getQuote({
  chainIndex: '8453',
  fromTokenAddress: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', // USDC
  toTokenAddress: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE',   // ETH
  amount: '1000000',
  slippagePercent: '0.5'
});

const decimals = parseInt(tokenInfo.data[0].fromToken.decimal);
const symbol = tokenInfo.data[0].fromToken.tokenSymbol;

console.log(`Token: ${symbol}, Decimals: ${decimals}`);

Checking Existing Allowance

The SDK automatically checks current allowance before approving:
try {
  const result = await client.dex.executeApproval({
    chainIndex: '8453',
    tokenContractAddress: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
    approveAmount: '1000000000'
  });
  
  if ('alreadyApproved' in result) {
    console.log('Token already approved for the requested amount!');
  } else {
    console.log('New approval completed:', result.transactionHash);
  }
} catch (error) {
  console.error('Approval failed:', error);
}

Approval with Retry Logic

The SDK includes automatic retry logic for failed approvals:
// From evm-approve.ts:116-147

private async executeApprovalTransaction(
  tokenAddress: string,
  spenderAddress: string, 
  amount: string
) {
  const tokenContract = new ethers.Contract(
    tokenAddress, 
    ERC20_ABI, 
    this.config.evm.wallet
  );

  let retryCount = 0;
  const maxRetries = this.networkConfig.maxRetries || 3;
  
  while (retryCount < maxRetries) {
    try {
      const tx = await tokenContract.approve(spenderAddress, amount, {
        gasLimit: BigInt(100000), // Safe default for approvals
        maxFeePerGas: (await this.provider.getFeeData()).maxFeePerGas! * BigInt(150) / BigInt(100),
        maxPriorityFeePerGas: (await this.provider.getFeeData()).maxPriorityFeePerGas! * BigInt(150) / BigInt(100)
      });

      return await tx.wait();
    } catch (error) {
      retryCount++;
      if (retryCount === maxRetries) throw error;
      await new Promise(resolve => setTimeout(resolve, 2000 * retryCount));
    }
  }
}
The SDK uses a 1.5x gas multiplier for EIP-1559 transactions to ensure approvals succeed during network congestion.

Complete Approval Example

import { OKXDexClient } from '@okxweb3/dex-sdk';
import { ethers } from 'ethers';
import { createEVMWallet } from '@okxweb3/dex-sdk';

const provider = new ethers.JsonRpcProvider(process.env.EVM_RPC_URL!);
const evmWallet = createEVMWallet(process.env.EVM_PRIVATE_KEY!, provider);

const client = new OKXDexClient({
  apiKey: process.env.OKX_API_KEY!,
  secretKey: process.env.OKX_SECRET_KEY!,
  apiPassphrase: process.env.OKX_API_PASSPHRASE!,
  projectId: process.env.OKX_PROJECT_ID!,
  evm: {
    wallet: evmWallet
  }
});

function toBaseUnits(amount: string, decimals: number): string {
  const [integerPart, decimalPart = ''] = amount.split('.');
  const currentDecimals = decimalPart.length;
  let result = integerPart + decimalPart;
  
  if (currentDecimals < decimals) {
    result = result + '0'.repeat(decimals - currentDecimals);
  }
  
  return result.replace(/^0+/, '') || '0';
}

async function approveToken() {
  const tokenAddress = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913'; // USDC on Base
  const chainIndex = '8453';
  const amount = '1000'; // 1000 USDC

  try {
    // Get token information
    const tokenInfo = await client.dex.getQuote({
      chainIndex,
      fromTokenAddress: tokenAddress,
      toTokenAddress: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE',
      amount: '1000000',
      slippagePercent: '0.5'
    });

    const tokenDecimals = parseInt(tokenInfo.data[0].fromToken.decimal);
    const tokenSymbol = tokenInfo.data[0].fromToken.tokenSymbol;
    const rawAmount = toBaseUnits(amount, tokenDecimals);

    console.log('Approval Details:');
    console.log(`Token: ${tokenSymbol}`);
    console.log(`Amount: ${amount} ${tokenSymbol}`);
    console.log(`Amount in base units: ${rawAmount}`);

    // Execute approval
    const result = await client.dex.executeApproval({
      chainIndex,
      tokenContractAddress: tokenAddress,
      approveAmount: rawAmount
    });

    if ('alreadyApproved' in result) {
      console.log('Token already approved!');
    } else {
      console.log('Approval completed successfully!');
      console.log('Transaction Hash:', result.transactionHash);
      console.log('Explorer URL:', result.explorerUrl);
    }
  } catch (error) {
    console.error('Error:', error);
  }
}

approveToken();

Best Practices

Approve Exact Amounts

For security, approve only the amount you need:
// Get swap quote first
const swapData = await client.dex.getSwapData({
  chainIndex: '8453',
  fromTokenAddress: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
  toTokenAddress: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE',
  amount: '1000000000',
  slippagePercent: '0.05',
  userWalletAddress: evmWallet.address
});

// Approve exact amount needed
await client.dex.executeApproval({
  chainIndex: '8453',
  tokenContractAddress: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
  approveAmount: '1000000000' // Same as swap amount
});

Handle Gas Costs

Approval transactions consume gas. The SDK sets a safe default:
// Default gas limit for approvals
const gasLimit = 100000; // Safe for most ERC-20 approvals

// Gas multiplier for EIP-1559
const gasMultiplier = 1.5; // 50% buffer for network congestion

Verify Approval Before Swap

Always approve tokens before attempting a swap:
async function swapWithApproval() {
  const fromToken = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913';
  const amount = '1000000000';
  
  // Step 1: Approve token
  console.log('Approving token...');
  await client.dex.executeApproval({
    chainIndex: '8453',
    tokenContractAddress: fromToken,
    approveAmount: amount
  });
  
  // Step 2: Execute swap
  console.log('Executing swap...');
  const result = await client.dex.executeSwap({
    chainIndex: '8453',
    fromTokenAddress: fromToken,
    toTokenAddress: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE',
    amount: amount,
    slippagePercent: '0.05',
    userWalletAddress: evmWallet.address
  });
  
  console.log('Swap completed:', result.transactionHash);
}

Troubleshooting

Error: “Token already approved”

This is not an error - the token has sufficient allowance:
if ('alreadyApproved' in result) {
  console.log('Skipping approval - already approved');
  // Proceed with swap
}

Error: “EVM wallet required”

Make sure to configure the EVM wallet when initializing the client:
const client = new OKXDexClient({
  // ... other config
  evm: {
    wallet: evmWallet  // Must be provided
  }
});

Error: “No dex contract address found”

This means the chain is not supported. Check supported chains:
const chainData = await client.dex.getChainData('8453');
console.log('DEX contract:', chainData.data[0].dexTokenApproveAddress);

Next Steps

Executing Swaps

Learn how to execute swaps after approval

Gas Estimation

Estimate gas costs for approvals and swaps

Build docs developers (and LLMs) love