Trade API

Build Swap Applications on EVM#

There are two approaches to building swap applications with OKX DEX on EVM networks:

  1. The API-first approach - directly interacting with OKX DEX API endpoints
  2. The SDK approach - using the @okx-dex/okx-dex-sdk package for a simplified developer experience

This guide covers both methods to help you choose the approach that best fits your needs.

Method 1: API-First Approach#

This approach demonstrates a token swap using the OKX DEX API endpoints directly. You will swap USDC to ETH on the Ethereum network.

1. Set Up Your Environment#

// --------------------- npm package ---------------------
import { Web3 } from 'web3';
import axios from 'axios';
import * as dotenv from 'dotenv';
import CryptoJS from 'crypto-js';
// The URL for the Ethereum node you want to connect to
const web3 = new Web3('https://......com');
// --------------------- environment variable ---------------------

// Load hidden environment variables
dotenv.config();

// Your wallet information - REPLACE WITH YOUR OWN VALUES
const WALLET_ADDRESS: string = process.env.EVM_WALLET_ADDRESS || '0xYourWalletAddress';
const PRIVATE_KEY: string = process.env.EVM_PRIVATE_KEY || 'YourPrivateKey'; 

// Token addresses for swap on Base Chain
const ETH_ADDRESS: string = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE'; // Native ETH
const USDC_ADDRESS: string = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913'; // USDC on Base

// Chain ID for Base Chain
const chainId: string = '8453';

// API URL
const baseUrl: string = 'https://web3.okx.com/api/v5/';

// Amount to swap in smallest unit (0.0005 ETH)
const SWAP_AMOUNT: string = '500000000000000'; // 0.0005 ETH
const SLIPPAGE: string = '0.005'; // 0.5% slippage tolerance

// --------------------- util function ---------------------
export function getHeaders(timestamp: string, method: string, requestPath: string, queryString = "") {
// Check https://web3.okx.com/zh-hans/web3/build/docs/waas/rest-authentication for api-key
    const apiKey = process.env.OKX_API_KEY;
    const secretKey = process.env.OKX_SECRET_KEY;
    const apiPassphrase = process.env.OKX_API_PASSPHRASE;
    const projectId = process.env.OKX_PROJECT_ID;

    if (!apiKey || !secretKey || !apiPassphrase || !projectId) {
        throw new Error("Missing required environment variables");
    }

    const stringToSign = timestamp + method + requestPath + queryString;
    return {
        "Content-Type": "application/json",
        "OK-ACCESS-KEY": apiKey,
        "OK-ACCESS-SIGN": CryptoJS.enc.Base64.stringify(
            CryptoJS.HmacSHA256(stringToSign, secretKey)
        ),
        "OK-ACCESS-TIMESTAMP": timestamp,
        "OK-ACCESS-PASSPHRASE": apiPassphrase,
        "OK-ACCESS-PROJECT": projectId,
    };
};

2. Check Allowance#

You need to check if the token has been approved for the DEX to spend. This step is only needed for ERC20 tokens, not for native tokens like ETH.

/**
 * Check token allowance for DEX
 * @param tokenAddress - Token contract address
 * @param ownerAddress - Your wallet address
 * @param spenderAddress - DEX spender address
 * @returns Allowance amount
 */
async function checkAllowance(
  tokenAddress: string,
  ownerAddress: string,
  spenderAddress: string
): Promise<bigint> {
  const tokenABI = [
    {
      "constant": true,
      "inputs": [
        { "name": "_owner", "type": "address" },
        { "name": "_spender", "type": "address" }
      ],
      "name": "allowance",
      "outputs": [{ "name": "", "type": "uint256" }],
      "payable": false,
      "stateMutability": "view",
      "type": "function"
    }
  ];

  const tokenContract = new web3.eth.Contract(tokenABI, tokenAddress);
  try {
    const allowance = await tokenContract.methods.allowance(ownerAddress, spenderAddress).call();
    return BigInt(String(allowance));
  } catch (error) {
    console.error('Failed to query allowance:', error);
    throw error;
  }
}

3. Check the Approval Parameters and Initiate the Approval#

If the allowance is lower than the amount you want to swap, you need to approve the token.

3.1 Define your transaction approval parameters

const getApproveTransactionParams = {
  chainId: chainId,
  tokenContractAddress: tokenAddress,
  approveAmount: amount
};

3.2 Define helper functions

async function getApproveTransaction(
  tokenAddress: string,
  amount: string
): Promise<any> {
  try {
    const path = 'dex/aggregator/approve-transaction';
    const url = `${baseUrl}${path}`;
    const params = {
      chainId: chainId,
      tokenContractAddress: tokenAddress,
      approveAmount: amount
    };

    // Prepare authentication
    const timestamp = new Date().toISOString();
    const requestPath = `/api/v5/${path}`;
    const queryString = "?" + new URLSearchParams(params).toString();
    const headers = getHeaders(timestamp, 'GET', requestPath, queryString);

    const response = await axios.get(url, { params, headers });

    if (response.data.code === '0') {
      return response.data.data[0];
    } else {
      throw new Error(`API Error: ${response.data.msg || 'Unknown error'}`);
    }
  } catch (error) {
    console.error('Failed to get approval transaction data:', (error as Error).message);
    throw error;
  }
}

3.3 Create Compute gasLimit utility function

Using the Onchain gateway API to get the gas limit.

This function uses the Onchain gateway API. This API is available to our enterprise customers only. If you are interested, please contact us dexapi@okx.com.

/**
 * Get transaction gas limit from Onchain gateway API
 * @param fromAddress - Sender address
 * @param toAddress - Target contract address
 * @param txAmount - Transaction amount (0 for approvals)
 * @param inputData - Transaction calldata
 * @returns Estimated gas limit
 */
async function getGasLimit(
  fromAddress: string,
  toAddress: string,
  txAmount: string = '0',
  inputData: string = ''
): Promise<string> {
  try {
    const path = 'dex/pre-transaction/gas-limit';
    const url = `https://web3.okx.com/api/v5/${path}`;

    const body = {
      chainIndex: chainId,
      fromAddress: fromAddress,
      toAddress: toAddress,
      txAmount: txAmount,
      extJson: {
        inputData: inputData
      }
    };

    // Prepare authentication with body included in signature
    const bodyString = JSON.stringify(body);
    const timestamp = new Date().toISOString();
    const requestPath = `/api/v5/${path}`;
    const headers = getHeaders(timestamp, 'POST', requestPath, "", bodyString);

    const response = await axios.post(url, body, { headers });

    if (response.data.code === '0') {
      return response.data.data[0].gasLimit;
    } else {
      throw new Error(`API Error: ${response.data.msg || 'Unknown error'}`);
    }
  } catch (error) {
    console.error('Failed to get gas limit:', (error as Error).message);
    throw error;
  }
}

Using RPC to get the gas limit.

const gasLimit = await web3.eth.estimateGas({
  from: WALLET_ADDRESS,
  to: tokenAddress,
  value: '0',
  data: approveData.data
});

// Add 20% buffer
const gasLimit = (BigInt(gasLimit) * BigInt(12) / BigInt(10)).toString();

3.4 Get transaction information and send approveTransaction

/**
 * Sign and send approve transaction
 * @param tokenAddress - Token to approve
 * @param amount - Amount to approve
 * @returns Transaction hash of the approval transaction
 */
async function approveToken(tokenAddress: string, amount: string): Promise<string | null> {
  const spenderAddress = '0x3b3ae790Df4F312e745D270119c6052904FB6790'; // Ethereum Mainnet DEX spender
  // See Router addresses at:  https://web3.okx.com/build/docs/waas/dex-smart-contract
  const currentAllowance = await checkAllowance(tokenAddress, WALLET_ADDRESS, spenderAddress);

  if (currentAllowance >= BigInt(amount)) {
    console.log('Sufficient allowance already exists');
    return null;
  }

  console.log('Insufficient allowance, approving tokens...');

  // Get approve transaction data from OKX DEX API
  const approveData = await getApproveTransaction(tokenAddress, amount);

  // Get accurate gas limit using RPC
  const gasLimit = await web3.eth.estimateGas({
    from: WALLET_ADDRESS,
    to: tokenAddress,
    value: '0',
    data: approveData.data
  });

  // Get accurate gas limit using Onchain gateway API
//   const gasLimit = await getGasLimit(WALLET_ADDRESS, tokenAddress, '0', approveData.data);

  // Get current gas price
  const gasPrice = await web3.eth.getGasPrice();
  const adjustedGasPrice = BigInt(gasPrice) * BigInt(15) / BigInt(10); // 1.5x for faster confirmation

  // Get current nonce
  const nonce = await web3.eth.getTransactionCount(WALLET_ADDRESS, 'latest');

  // Create transaction object
  const txObject = {
    from: WALLET_ADDRESS,
    to: tokenAddress,
    data: approveData.data,
    value: '0',
    gas: gasLimit,
    gasPrice: adjustedGasPrice.toString(),
    nonce: nonce
  };

  // Sign and broadcast transaction
  const signedTx = await web3.eth.accounts.signTransaction(txObject, PRIVATE_KEY);
  const receipt = await web3.eth.sendSignedTransaction(signedTx.rawTransaction);
  
  console.log(`Approval transaction successful: ${receipt.transactionHash}`);
  return receipt.transactionHash;
}

4. Get Quote Data#

4.1 Define quote parameters

const quoteParams = {
  amount: fromAmount,
  chainId: chainId,
  toTokenAddress: toTokenAddress,
  fromTokenAddress: fromTokenAddress,
};

4.2 Define helper functions

/**
 * Get swap quote from DEX API
 * @param fromTokenAddress - Source token address
 * @param toTokenAddress - Destination token address
 * @param amount - Amount to swap
 * @param slippage - Maximum slippage (e.g., "0.005" for 0.5%)
 * @returns Swap quote
 */
async function getSwapQuote(
  fromTokenAddress: string,
  toTokenAddress: string,
  amount: string,
  slippage: string = '0.005'
): Promise<any> {
  try {
    const path = 'dex/aggregator/quote';
    const url = `${baseUrl}${path}`;

    const params = {
      chainId: chainId,
      fromTokenAddress,
      toTokenAddress,
      amount,
      slippage
    };

    // Prepare authentication
    const timestamp = new Date().toISOString();
    const requestPath = `/api/v5/${path}`;
    const queryString = "?" + new URLSearchParams(params).toString();
    const headers = getHeaders(timestamp, 'GET', requestPath, queryString);

    const response = await axios.get(url, { params, headers });

    if (response.data.code === '0') {
      return response.data.data[0];
    } else {
      throw new Error(`API Error: ${response.data.msg || 'Unknown error'}`);
    }
  } catch (error) {
    console.error('Failed to get swap quote:', (error as Error).message);
    throw error;
  }
}

5. Prepare Transaction#

5.1 Define swap parameters

const swapParams = {
      chainId: chainId,
      fromTokenAddress,
      toTokenAddress,
      amount,
      userWalletAddress: userAddress,
      slippage
};

5.2 Request swap transaction data

/**
 * Get swap transaction data from DEX API
 * @param fromTokenAddress - Source token address
 * @param toTokenAddress - Destination token address
 * @param amount - Amount to swap
 * @param userAddress - User wallet address
 * @param slippage - Maximum slippage (e.g., "0.005" for 0.5%)
 * @returns Swap transaction data
 */
async function getSwapTransaction(
  fromTokenAddress: string,
  toTokenAddress: string,
  amount: string,
  userAddress: string,
  slippage: string = '0.005'
): Promise<any> {
  try {
    const path = 'dex/aggregator/swap';
    const url = `${baseUrl}${path}`;

    const params = {
      chainId: chainId,
      fromTokenAddress,
      toTokenAddress,
      amount,
      userWalletAddress: userAddress,
      slippage
    };

    // Prepare authentication
    const timestamp = new Date().toISOString();
    const requestPath = `/api/v5/${path}`;
    const queryString = "?" + new URLSearchParams(params).toString();
    const headers = getHeaders(timestamp, 'GET', requestPath, queryString);

    const response = await axios.get(url, { params, headers });

    if (response.data.code === '0') {
      return response.data.data[0];
    } else {
      throw new Error(`API Error: ${response.data.msg || 'Unknown error'}`);
    }
  } catch (error) {
    console.error('Failed to get swap transaction data:', (error as Error).message);
    throw error;
  }
}

6. Simulate Transaction#

Before executing the actual swap, it's crucial to simulate the transaction to ensure it will succeed and to identify any potential issues:

This function uses the Onchain gateway API. This API is available to our enterprise customers only. If you are interested, please contact us dexapi@okx.com.

async function simulateTransaction(swapData: any) {
    try {
        if (!swapData.tx) {
            throw new Error('Invalid swap data format - missing transaction data');
        }

        const tx = swapData.tx;
        const params: any = {
            fromAddress: tx.from,
            toAddress: tx.to,
            txAmount: tx.value || '0',
            chainIndex: chainId,
            extJson: {
                inputData: tx.data
            },
            includeDebug: true
        };

        const timestamp = new Date().toISOString();
        const requestPath = "/api/v5/dex/pre-transaction/simulate";
        const requestBody = JSON.stringify(params);
        const headers = getHeaders(timestamp, "POST", requestPath, "", requestBody);

        console.log('Simulating transaction...');
        const response = await axios.post(
            `https://web3.okx.com${requestPath}`, 
            params, 
            { headers }
        );

        if (response.data.code !== "0") {
            throw new Error(`Simulation failed: ${response.data.msg || "Unknown simulation error"}`);
        }

        const simulationResult = response.data.data[0];
        
        // Check simulation success
        if (simulationResult.success === false) {
            console.error('Transaction simulation failed:', simulationResult.error);
            throw new Error(`Transaction would fail: ${simulationResult.error}`);
        }

        console.log('Transaction simulation successful');
        console.log(`Estimated gas used: ${simulationResult.gasUsed || 'N/A'}`);
        
        if (simulationResult.logs) {
            console.log('Simulation logs:', simulationResult.logs);
        }

        return simulationResult;
    } catch (error) {
        console.error("Error simulating transaction:", error);
        throw error;
    }
}

7. Broadcast Transaction#

7.1 Creating a Compute Gas Limit Utility Function There are two approaches to obtain the gas limit for your transactions: using standard RPC calls or leveraging the Onchain Gateway API.

Method 1: Using the Onchain Gateway API for Gas Estimation

The first approach leverages OKX's Onchain Gateway API, which provides more accurate gas estimations and additional enterprise features.

This API is exclusively available to enterprise customers. For access inquiries, please contact our team at dexapi@okx.com.

/**
 * Get transaction gas limit from Onchain gateway API
 * @param fromAddress - Sender address
 * @param toAddress - Target contract address
 * @param txAmount - Transaction amount (0 for approvals)
 * @param inputData - Transaction calldata
 * @returns Estimated gas limit
 */
async function getGasLimit(
  fromAddress: string,
  toAddress: string,
  txAmount: string = '0',
  inputData: string = ''
): Promise<string> {
  try {
    const path = 'dex/pre-transaction/gas-limit';
    const url = `https://web3.okx.com/api/v5/${path}`;

    const body = {
      chainIndex: chainId,
      fromAddress: fromAddress,
      toAddress: toAddress,
      txAmount: txAmount,
      extJson: {
        inputData: inputData
      }
    };

    // Prepare authentication with body included in signature
    const bodyString = JSON.stringify(body);
    const timestamp = new Date().toISOString();
    const requestPath = `/api/v5/${path}`;
    const headers = getHeaders(timestamp, 'POST', requestPath, "", bodyString);

    const response = await axios.post(url, body, { headers });

    if (response.data.code === '0') {
      return response.data.data[0].gasLimit;
    } else {
      throw new Error(`API Error: ${response.data.msg || 'Unknown error'}`);
    }
  } catch (error) {
    console.error('Failed to get gas limit:', (error as Error).message);
    throw error;
  }
}

Method 2: Using RPC to Estimate Gas Limit

The second approach utilizes standard Web3 RPC calls to estimate the required gas for your transaction.

const gasLimit = await web3.eth.estimateGas({
  from: WALLET_ADDRESS,
  to: tokenAddress,
  value: '0',
  data: swapData.data
});

// Add 20% buffer
const gasLimit = (BigInt(gasLimit) * BigInt(12) / BigInt(10)).toString();

Broadcasting Transactions with the Onchain Gateway API

For enterprise customers with access to the Onchain Gateway API, you can broadcast transactions directly through OKX's infrastructure. This method provides enhanced reliability and monitoring capabilities for high-volume trading operations. Note: This functionality requires enterprise API access. To learn more about enterprise features and pricing, please reach out to dexapi@okx.com.

import { Web3 } from 'web3';
import axios from 'axios';
import * as dotenv from 'dotenv';
import CryptoJS from 'crypto-js';

// Load environment variables
dotenv.config();

// Connect to Base network
const web3 = new Web3(process.env.EVM_RPC_URL || 'https://mainnet.base.org');

// Your wallet information - REPLACE WITH YOUR OWN VALUES
const WALLET_ADDRESS = process.env.EVM_WALLET_ADDRESS || '';
const PRIVATE_KEY = process.env.EVM_PRIVATE_KEY || '';

// Token addresses for swap on Base Chain
const ETH_ADDRESS = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE'; // Native ETH

// Chain ID for Base Chain
const chainId = '8453';

// API URL
const baseUrl = 'https://web3.okx.com/api/v5/';

// Define interfaces
interface GasLimitApiResponse {
  code: string;
  msg?: string;
  data: Array<{
    gasLimit: string;
  }>;
}

// Interface for broadcast API response
interface BroadcastApiResponse {
  code: string;
  msg?: string;
  data: Array<{
    orderId: string;
  }>;
}

/**
 * Generate API authentication headers
 */
function getHeaders(timestamp: string, method: string, requestPath: string, queryString = "", body = "") {
    const apiKey = process.env.OKX_API_KEY;
    const secretKey = process.env.OKX_SECRET_KEY;
    const apiPassphrase = process.env.OKX_API_PASSPHRASE;
    const projectId = process.env.OKX_PROJECT_ID;

    if (!apiKey || !secretKey || !apiPassphrase || !projectId) {
        throw new Error("Missing required environment variables for API authentication");
    }

    const stringToSign = timestamp + method + requestPath + (queryString || body);

    return {
        "Content-Type": "application/json",
        "OK-ACCESS-KEY": apiKey,
        "OK-ACCESS-SIGN": CryptoJS.enc.Base64.stringify(
            CryptoJS.HmacSHA256(stringToSign, secretKey)
        ),
        "OK-ACCESS-TIMESTAMP": timestamp,
        "OK-ACCESS-PASSPHRASE": apiPassphrase,
        "OK-ACCESS-PROJECT": projectId,
    };
}

/**
 * Get transaction gas limit from Onchain gateway API
 * @param fromAddress - Sender address
 * @param toAddress - Target contract address
 * @param txAmount - Transaction amount (0 for approvals)
 * @param inputData - Transaction calldata
 * @returns Estimated gas limit
 */
async function getGasLimit(
  fromAddress: string,
  toAddress: string,
  txAmount: string = '0',
  inputData: string = ''
): Promise<string> {
  const path = 'dex/pre-transaction/gas-limit';
  const url = `${baseUrl}${path}`;
  const body = { chainIndex: chainId, fromAddress, toAddress, txAmount, extJson: { inputData } };
  
  const bodyString = JSON.stringify(body);
  const timestamp = new Date().toISOString();
  const headers = getHeaders(timestamp, 'POST', `/api/v5/${path}`, "", bodyString);

  const response = await axios.post<GasLimitApiResponse>(url, body, { headers });
  if (response.data.code === '0') {
    return response.data.data[0].gasLimit;
  }
  throw new Error(`API Error: ${response.data.msg || 'Unknown error'}`);
}

/**
 * Get swap data from OKX API
 */
async function getSwapData(
    fromTokenAddress: string,
    toTokenAddress: string,
    amount: string,
    slippage = '0.5'
) {
    const path = 'dex/aggregator/swap';
    const url = `${baseUrl}${path}`;
    const params = { chainIndex: chainId, fromTokenAddress, toTokenAddress, amount, slippage, userWalletAddress: WALLET_ADDRESS };
    
    const queryString = "?" + new URLSearchParams(params).toString();
    const timestamp = new Date().toISOString();
    const headers = getHeaders(timestamp, 'GET', `/api/v5/${path}`, queryString);

    const response = await axios.get(`${url}${queryString}`, { headers });
    const responseData = response.data as any;
    if (responseData.code === '0') {
        return responseData.data[0];
    }
    throw new Error(`Swap API Error: ${responseData.msg || 'Unknown error'}`);
}

/**
 * Build and sign transaction using gas limit
 */
async function buildAndSignTransaction(swapData: any, gasLimit: string): Promise<any> {
    const gasPrice = await web3.eth.getGasPrice();
    const nonce = await web3.eth.getTransactionCount(WALLET_ADDRESS, 'pending');
    
    const transaction = {
        from: swapData.tx.from,
        to: swapData.tx.to,
        data: swapData.tx.data,
        value: swapData.tx.value || '0x0',
        gas: gasLimit,
        gasPrice: gasPrice.toString(),
        nonce: Number(nonce),
        chainId: parseInt(chainId)
    };

    return await web3.eth.accounts.signTransaction(transaction, PRIVATE_KEY);
}

/**
 * Broadcast transaction using Onchain Gateway API
 */
async function broadcastTransaction(signedTx: any, chainId: string, walletAddress: string): Promise<string> {
    const path = 'dex/pre-transaction/broadcast-transaction';
    const url = `${baseUrl}${path}`;
    const rawTxHex = typeof signedTx.rawTransaction === 'string' ? signedTx.rawTransaction : web3.utils.bytesToHex(signedTx.rawTransaction);
    const body = { signedTx: rawTxHex, chainIndex: chainId, address: walletAddress };
    
    const bodyString = JSON.stringify(body);
    const timestamp = new Date().toISOString();
    const headers = getHeaders(timestamp, 'POST', `/api/v5/${path}`, "", bodyString);

    const response = await axios.post<BroadcastApiResponse>(url, body, { headers });
    if (response.data.code === '0') {
        return response.data.data[0].orderId;
    }
    throw new Error(`Broadcast API Error: ${response.data.msg || 'Unknown error'}`);
}

async function main() {
    try {
        console.log('EVM Gas Limit and Broadcast');
        console.log('================================');

        // Validate environment variables
        if (!WALLET_ADDRESS || !PRIVATE_KEY) {
            throw new Error('Missing wallet address or private key in environment variables');
        }

        console.log(`Wallet Address: ${WALLET_ADDRESS}`);
        console.log(`Chain ID: ${chainId}`);
        console.log(`RPC URL: ${process.env.EVM_RPC_URL || 'https://mainnet.base.org'}`);

        // Example parameters
        const fromToken = ETH_ADDRESS;
        const toToken = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913'; // USDC on Base
        const amount = '100000000000000'; // 0.0001 ETH in wei
        const slippage = '0.05'; // 0.5%

        // Step 1: Get swap data
        const swapData = await getSwapData(fromToken, toToken, amount, slippage);
        console.log('Swap data obtained');

        // Step 2: Get gas limit
        const gasLimit = await getGasLimit(
            swapData.tx.from,
            swapData.tx.to,
            swapData.tx.value || '0',
            swapData.tx.data
        );
        console.log('Gas limit obtained', gasLimit);

        // Step 3: Build and sign transaction
        const signedTx = await buildAndSignTransaction(swapData, gasLimit);
        console.log('Transaction built and signed');

        // Step 4: Broadcast transaction
        try {
            const orderId = await broadcastTransaction(signedTx, chainId, swapData.tx.from);
            console.log(`Transaction broadcast successful. Order ID: ${orderId}`);
        } catch (broadcastError: any) {
            if (broadcastError.message.includes('enterprise access required')) {
                console.log('Broadcast failed - enterprise access required');
                console.log('Gas limit obtained successfully:', gasLimit);
            } else {
                throw broadcastError;
            }
        }
        
    } catch (error) {
        console.error('Main execution failed:', (error as Error).message);
        process.exit(1);
    }
}

// Run the script
if (require.main === module) {
    main();
}

export {
    getSwapData,
    getGasLimit,
    broadcastTransaction
}; 

Alternative: Broadcasting Transactions Using Standard RPC For developers who prefer using standard blockchain RPC methods or don't have access to enterprise features, you can broadcast transactions directly to the network using Web3 RPC calls.

/**
 * Execute token swap
 * @param fromTokenAddress - Source token address
 * @param toTokenAddress - Destination token address
 * @param amount - Amount to swap
 * @param slippage - Maximum slippage
 * @returns Transaction hash
 */
async function executeSwap(
  fromTokenAddress: string,
  toTokenAddress: string,
  amount: string,
  slippage: string = '0.005'
): Promise<string> {
  // 1. Check allowance and approve if necessary (skip for native token)
  if (fromTokenAddress !== ETH_ADDRESS) {
    await approveToken(fromTokenAddress, amount);
  }

  // 2. Get swap transaction data
  const swapData = await getSwapTransaction(fromTokenAddress, toTokenAddress, amount, WALLET_ADDRESS, slippage);
  
  const txData = swapData.tx;
  console.log("Swap TX data received");

  // 3. Get accurate gas limit
  const gasLimit = await getGasLimit(
    WALLET_ADDRESS,
    txData.to,
    txData.value || '0',
    txData.data
  );
  console.log("Gas limit received");

  // 4. Get current nonce
  const nonce = await web3.eth.getTransactionCount(WALLET_ADDRESS, 'latest');
  console.log("Nonce received");

  // 5. Get current gas price and adjust for faster confirmation
  const gasPrice = await web3.eth.getGasPrice();
  const adjustedGasPrice = BigInt(gasPrice) * BigInt(15) / BigInt(10); // 1.5x for faster confirmation
  console.log("Gas price received");

  // 6. Create transaction object
  const txObject = {
    from: WALLET_ADDRESS,
    to: txData.to,
    data: txData.data,
    value: txData.value || '0',
    gas: gasLimit,
    gasPrice: adjustedGasPrice.toString(),
    nonce: nonce
  };
  console.log("TX build complete");

  // 7. Sign and broadcast transaction using RPC
  const signedTx = await web3.eth.accounts.signTransaction(txObject, PRIVATE_KEY);
  console.log("TX signed");
  
  const receipt = await web3.eth.sendSignedTransaction(signedTx.rawTransaction);
  console.log(`Transaction successful: ${receipt.transactionHash}`);
  
  return receipt.transactionHash;
}

8. Track Transaction#

Using Onchain gateway API

This function uses the Onchain gateway API. This API is available to our enterprise customers only. If you are interested, please contact us dexapi@okx.com.

// Define error info interface
interface TxErrorInfo {
  error: string;
  message: string;
  action: string;
}

/**
 * Tracking transaction confirmation status using the Onchain gateway API
 * @param orderId - Order ID from broadcast response
 * @param intervalMs - Polling interval in milliseconds
 * @param timeoutMs - Maximum time to wait
 * @returns Final transaction confirmation status
 */
async function trackTransaction(
  orderId: string,
  intervalMs: number = 5000,
  timeoutMs: number = 300000
): Promise<any> {
  console.log(`Tracking transaction with Order ID: ${orderId}`);

  const startTime = Date.now();
  let lastStatus = '';

  while (Date.now() - startTime < timeoutMs) {
    // Get transaction status
    try {
      const path = 'dex/post-transaction/orders';
      const url = `https://web3.okx.com/api/v5/${path}`;

      const params = {
        orderId: orderId,
        chainIndex: chainId,
        address: WALLET_ADDRESS,
        limit: '1'
      };

      // Prepare authentication
      const timestamp = new Date().toISOString();
      const requestPath = `/api/v5/${path}`;
      const queryString = "?" + new URLSearchParams(params).toString();
      const headers = getHeaders(timestamp, 'GET', requestPath, queryString);

      const response = await axios.get(url, { params, headers });

      if (response.data.code === '0' && response.data.data && response.data.data.length > 0) {
        if (response.data.data[0].orders && response.data.data[0].orders.length > 0) {
          const txData = response.data.data[0].orders[0];
          
          // Use txStatus to match the API response
          const status = txData.txStatus;

          // Only log when status changes
          if (status !== lastStatus) {
            lastStatus = status;

            if (status === '1') {
              console.log(`Transaction pending: ${txData.txHash || 'Hash not available yet'}`);
            } else if (status === '2') {
              console.log(`Transaction successful: https://web3.okx.com/explorer/base/tx/${txData.txHash}`);
              return txData;
            } else if (status === '3') {
              const failReason = txData.failReason || 'Unknown reason';
              const errorMessage = `Transaction failed: ${failReason}`;

              console.error(errorMessage);

              const errorInfo = handleTransactionError(txData);
              console.log(`Error type: ${errorInfo.error}`);
              console.log(`Suggested action: ${errorInfo.action}`);

              throw new Error(errorMessage);
            }
          }
        } else {
          console.log(`No orders found for Order ID: ${orderId}`);
        }
      }
    } catch (error) {
      console.warn('Error checking transaction status:', (error as Error).message);
    }

    // Wait before next check
    await new Promise(resolve => setTimeout(resolve, intervalMs));
  }

  throw new Error('Transaction tracking timed out');
}

/**
 * Comprehensive error handling with failReason
 * @param txData - Transaction data from post-transaction/orders
 * @returns Structured error information
 */
function handleTransactionError(txData: any): TxErrorInfo {
  const failReason = txData.failReason || 'Unknown reason';

  // Log the detailed error
  console.error(`Transaction failed with reason: ${failReason}`);

  // Default error handling
  return {
    error: 'TRANSACTION_FAILED',
    message: failReason,
    action: 'Try again or contact support'
  };
}

Track transaction using SWAP API:

/**
 * Track transaction using SWAP API
 * @param chainId - Chain ID (e.g., 1 for Ethereum Mainnet)
 * @param txHash - Transaction hash
 * @returns Transaction details
 */
async function trackTransactionWithSwapAPI(chainId: string, txHash: string): Promise<any> {
  try {
    const path = 'dex/aggregator/history';
    const url = `${baseUrl}${path}`;

    const params = {
      chainId: chainId,
      txHash: txHash,
      isFromMyProject: 'true'
    };

    // Prepare authentication
    const timestamp = new Date().toISOString();
    const requestPath = `/api/v5/${path}`;
    const queryString = "?" + new URLSearchParams(params).toString();
    const headers = getHeaders(timestamp, 'GET', requestPath, queryString);

    const response = await axios.get(url, { params, headers });

    if (response.data.code === '0') {
      const txData = response.data.data[0];
      const status = txData.status;

      if (status === 'pending') {
        console.log(`Transaction is still pending: ${txHash}`);
        return { status: 'pending', details: txData };
      } else if (status === 'success') {
        console.log(`Transaction successful!`);
        console.log(`From: ${txData.fromTokenDetails.symbol} - Amount: ${txData.fromTokenDetails.amount}`);
        console.log(`To: ${txData.toTokenDetails.symbol} - Amount: ${txData.toTokenDetails.amount}`);
        console.log(`Transaction Fee: ${txData.txFee}`);
        console.log(`Explorer URL: https://basescan.org/tx/${txHash}`);
        return { status: 'success', details: txData };
      } else if (status === 'failure') {
        console.error(`Transaction failed: ${txData.errorMsg || 'Unknown reason'}`);
        return { status: 'failure', details: txData };
      }
      
      return txData;
    } else {
      throw new Error(`API Error: ${response.data.msg || 'Unknown error'}`);
    }
  } catch (error) {
    console.error('Failed to track transaction status:', (error as Error).message);
    throw error;
  }
}

SWAP API transaction tracking provides comprehensive swap execution details using the /dex/aggregator/history endpoint. It offers token-specific information (symbols, amounts), fees paid, and detailed blockchain data. Use this when you need complete swap insight with token-level details.

9. Complete Implementation#

Here's a complete implementation example:

import { Web3 } from 'web3';
import * as axios from 'axios';
import * as dotenv from 'dotenv';
import * as CryptoJS from 'crypto-js';

// Load environment variables
dotenv.config();

// Connect to Base network
const web3 = new Web3(process.env.EVM_RPC_URL || 'https://mainnet.base.org');

// Your wallet information - REPLACE WITH YOUR OWN VALUES
const WALLET_ADDRESS: string = process.env.EVM_WALLET_ADDRESS || '';
const PRIVATE_KEY: string = process.env.EVM_PRIVATE_KEY || '';

// Token addresses for swap on Base Chain
const ETH_ADDRESS: string = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE'; // Native ETH

// Chain ID for Base Chain
const chainId: string = '8453';

// API URL
const baseUrl: string = 'https://web3.okx.com/api/v5/';

// Define interfaces
interface TokenInfo {
    tokenSymbol: string;
    decimal: string;
    tokenUnitPrice: string;
}

// Interface for gas limit API response
interface GasLimitApiResponse {
  code: string;
  msg?: string;
  data: Array<{
    gasLimit: string;
  }>;
}

// Interface for simulation API response
interface SimulationApiResponse {
  code: string;
  msg?: string;
  data: Array<{
    intention: string;
    gasUsed?: string;
    failReason?: string;
    assetChange?: Array<{
      assetType: string;
      name: string;
      symbol: string;
      decimals: number;
      address: string;
      imageUrl: string;
      rawValue: string;
    }>;
    risks?: Array<any>;
  }>;
}

// Interface for broadcast API response
interface BroadcastApiResponse {
  code: string;
  msg?: string;
  data: Array<{
    orderId: string;
  }>;
}

// Define error info interface
interface TxErrorInfo {
  error: string;
  message: string;
  action: string;
}

/**
 * Generate API authentication headers
 */
function getHeaders(timestamp: string, method: string, requestPath: string, queryString = "", body = "") {
    const apiKey = process.env.OKX_API_KEY;
    const secretKey = process.env.OKX_SECRET_KEY;
    const apiPassphrase = process.env.OKX_API_PASSPHRASE;
    const projectId = process.env.OKX_PROJECT_ID;

    if (!apiKey || !secretKey || !apiPassphrase || !projectId) {
        throw new Error("Missing required environment variables for API authentication");
    }

    const stringToSign = timestamp + method + requestPath + (queryString || body);

    return {
        "Content-Type": "application/json",
        "OK-ACCESS-KEY": apiKey,
        "OK-ACCESS-SIGN": CryptoJS.enc.Base64.stringify(
            CryptoJS.HmacSHA256(stringToSign, secretKey)
        ),
        "OK-ACCESS-TIMESTAMP": timestamp,
        "OK-ACCESS-PASSPHRASE": apiPassphrase,
        "OK-ACCESS-PROJECT": projectId,
    };
}

/**
 * Get transaction gas limit from Onchain gateway API
 * @param fromAddress - Sender address
 * @param toAddress - Target contract address
 * @param txAmount - Transaction amount (0 for approvals)
 * @param inputData - Transaction calldata
 * @returns Estimated gas limit
 */
async function getGasLimit(
  fromAddress: string,
  toAddress: string,
  txAmount: string = '0',
  inputData: string = ''
): Promise<string> {
  try {
    console.log('Getting gas limit from Onchain Gateway API...');
    
    const path = 'dex/pre-transaction/gas-limit';
    const url = `${baseUrl}${path}`;

    const body = {
      chainIndex: chainId,
      fromAddress: fromAddress,
      toAddress: toAddress,
      txAmount: txAmount,
      extJson: {
        inputData: inputData
      }
    };

    // Prepare authentication with body included in signature
    const bodyString = JSON.stringify(body);
    const timestamp = new Date().toISOString();
    const requestPath = `/api/v5/${path}`;
    const headers = getHeaders(timestamp, 'POST', requestPath, "", bodyString);

    const response = await axios.post<GasLimitApiResponse>(url, body, { headers });

    console.log('Gas Limit API Response:');
    console.log(JSON.stringify(response.data, null, 2));

    if (response.data.code === '0') {
      const gasLimit = response.data.data[0].gasLimit;
      console.log(`Gas Limit obtained: ${gasLimit}`);
      return gasLimit;
    } else {
      throw new Error(`API Error: ${response.data.msg || 'Unknown error'}`);
    }
  } catch (error) {
    console.error('Failed to get gas limit:', (error as Error).message);
    throw error;
  }
}

/**
 * Get swap data from OKX API
 */
async function getSwapData(
    fromTokenAddress: string,
    toTokenAddress: string,
    amount: string,
    slippage = '0.5'
) {
    try {
        console.log('Getting swap data from OKX API...');
        
        const path = 'dex/aggregator/swap';
        const url = `${baseUrl}${path}`;

        const params = {
            chainIndex: chainId,
            fromTokenAddress: fromTokenAddress,
            toTokenAddress: toTokenAddress,
            amount: amount,
            slippage: slippage,
            userWalletAddress: WALLET_ADDRESS
        };

        console.log('Swap API Request Parameters:');
        console.log(JSON.stringify(params, null, 2));

        // Prepare authentication with query string
        const queryString = "?" + new URLSearchParams(params).toString();
        const timestamp = new Date().toISOString();
        const requestPath = `/api/v5/${path}`;
        const headers = getHeaders(timestamp, 'GET', requestPath, queryString);

        const response = await axios.get(`${url}${queryString}`, { headers });

        console.log('Swap API Response:');
        console.log(JSON.stringify(response.data, null, 2));

        const responseData = response.data as any;
        if (responseData.code === '0') {
            return responseData.data[0];
        } else {
            throw new Error(`Swap API Error: ${responseData.msg || 'Unknown error'}`);
        }
    } catch (error) {
        console.error('Failed to get swap data:', (error as Error).message);
        throw error;
    }
}

/**
 * Simulate transaction using Onchain Gateway API
 */
async function simulateTransaction(swapData: any) {
    try {
        console.log('Simulating transaction with Onchain Gateway API...');
        
        const path = 'dex/pre-transaction/simulate';
        const url = `${baseUrl}${path}`;

        const body = {
            chainIndex: chainId,
            fromAddress: swapData.tx.from,
            toAddress: swapData.tx.to,
            txAmount: swapData.tx.value || '0',
            extJson: {
                inputData: swapData.tx.data
            }
        };

        // Prepare authentication with body included in signature
        const bodyString = JSON.stringify(body);
        const timestamp = new Date().toISOString();
        const requestPath = `/api/v5/${path}`;
        const headers = getHeaders(timestamp, 'POST', requestPath, "", bodyString);

        const response = await axios.post<SimulationApiResponse>(url, body, { headers });

        console.log('Simulation API Response:');
        console.log(JSON.stringify(response.data, null, 2));

        if (response.data.code === '0') {
            const simulationResult = response.data.data[0];
            // Check if simulation was successful (no failReason or empty failReason)
            if (!simulationResult.failReason || simulationResult.failReason === '') {
                console.log(`Transaction simulation successful. Gas used: ${simulationResult.gasUsed}`);
                return simulationResult;
            } else {
                throw new Error(`Simulation failed: ${simulationResult.failReason}`);
            }
        } else {
            throw new Error(`Simulation API Error: ${response.data.msg || 'Unknown error'}`);
        }
    } catch (error) {
        console.error('Transaction simulation failed:', (error as Error).message);
        throw error;
    }
}

/**
 * Broadcast transaction using Onchain Gateway API with RPC fallback
 */
async function broadcastTransaction(signedTx: any, chainId: string, walletAddress: string): Promise<string> {
    try {
        console.log('Broadcasting transaction via Onchain Gateway API...');
        
        const path = 'dex/pre-transaction/broadcast-transaction';
        const url = `${baseUrl}${path}`;

        // Convert rawTransaction to hex string
        const rawTxHex = typeof signedTx.rawTransaction === 'string' 
            ? signedTx.rawTransaction 
            : web3.utils.bytesToHex(signedTx.rawTransaction);

        const body = {
            signedTx: rawTxHex,
            chainIndex: chainId,
            address: walletAddress
        };

        console.log('Broadcast API Request Body:');
        console.log(JSON.stringify(body, null, 2));

        // Prepare authentication with body included in signature
        const bodyString = JSON.stringify(body);
        const timestamp = new Date().toISOString();
        const requestPath = `/api/v5/${path}`;
        const headers = getHeaders(timestamp, 'POST', requestPath, "", bodyString);

        const response = await axios.post<BroadcastApiResponse>(url, body, { headers });

        console.log('Broadcast API Response:');
        console.log(JSON.stringify(response.data, null, 2));

        if (response.data.code === '0') {
            const orderId = response.data.data[0].orderId;
            console.log(`Transaction broadcast successful. Order ID: ${orderId}`);
            return orderId;
        } else {
            throw new Error(`Broadcast API Error: ${response.data.msg || 'Unknown error'}`);
        }
    } catch (error) {
        console.error('API broadcast failed, trying RPC fallback:', (error as Error).message);
        
        // Fallback to RPC broadcast
        try {
            console.log('Broadcasting via RPC fallback...');
            const receipt = await web3.eth.sendSignedTransaction(signedTx.rawTransaction);
            console.log(`RPC broadcast successful. Transaction hash: ${receipt.transactionHash}`);
            return receipt.transactionHash.toString();
        } catch (rpcError) {
            console.error('RPC broadcast also failed:', (rpcError as Error).message);
            throw new Error(`Both API and RPC broadcast failed. API Error: ${(error as Error).message}, RPC Error: ${(rpcError as Error).message}`);
        }
    }
}

/**
 * Execute swap with full transaction flow
 */
async function executeSwap(
    fromTokenAddress: string,
    toTokenAddress: string,
    amount: string,
    slippage: string = '0.05'
): Promise<string> {
    try {
        console.log('Starting swap execution...');
        // Step 1: Get swap data
        const swapData = await getSwapData(fromTokenAddress, toTokenAddress, amount, slippage);
        console.log('Swap data obtained');

        // Step 2: Simulate transaction
        const simulationResult = await simulateTransaction(swapData);
        console.log('Transaction simulation completed');
        console.log('Simulation result', simulationResult.intention);

        // Step 3: Get gas limit
        const gasLimit = await getGasLimit(
            swapData.tx.from,
            swapData.tx.to,
            swapData.tx.value || '0',
            swapData.tx.data
        );

        // Step 4: Get current gas price
        const gasPrice = await web3.eth.getGasPrice();
        console.log(`Current gas price: ${web3.utils.fromWei(gasPrice, 'gwei')} gwei`);

        // Step 5: Get nonce
        const nonce = await web3.eth.getTransactionCount(WALLET_ADDRESS, 'pending');
        console.log(`Nonce: ${nonce}`);

        // Step 6: Build transaction
        const transaction = {
            from: swapData.tx.from,
            to: swapData.tx.to,
            data: swapData.tx.data,
            value: swapData.tx.value || '0x0',
            gas: gasLimit,
            gasPrice: gasPrice.toString(),
            nonce: Number(nonce),
            chainId: parseInt(chainId)
        };

        console.log('Transaction object:');
        console.log(JSON.stringify(transaction, null, 2));

        // Step 7: Sign transaction
        console.log('Signing transaction...');
        const signedTx = await web3.eth.accounts.signTransaction(transaction, PRIVATE_KEY);
        console.log('Transaction signed');

        // Step 8: Broadcast transaction
        const txHash = await broadcastTransaction(signedTx, chainId, WALLET_ADDRESS);
        console.log(`Transaction broadcast successful. Hash: ${txHash}`);

        // Step 9: Track transaction
        console.log('Tracking transaction status...');
        const trackingResult = await trackTransaction(txHash);
        console.log('Transaction tracking completed');
        console.log('Tracking result', trackingResult);

        return txHash;
    } catch (error) {
        console.error('Swap execution failed:', (error as Error).message);
        throw error;
    }
}

/**
 * Execute swap with simulation and detailed logging
 */
async function executeSwapWithSimulation(
    fromTokenAddress: string,
    toTokenAddress: string,
    amount: string,
    slippage: string = '0.05'
): Promise<any> {
    try {
        console.log('Starting swap execution with simulation...');
        
        const txHash = await executeSwap(fromTokenAddress, toTokenAddress, amount, slippage);
        
        console.log('Swap execution completed successfully!');
        console.log(`Transaction Hash: ${txHash}`);
        
        return { success: true, txHash };
    } catch (error) {
        console.error('Swap execution failed:', (error as Error).message);
        return { success: false, error: (error as Error).message };
    }
}

/**
 * Tracking transaction confirmation status using the Onchain gateway API
 * @param orderId - Order ID from broadcast response
 * @param intervalMs - Polling interval in milliseconds
 * @param timeoutMs - Maximum time to wait
 * @returns Final transaction confirmation status
 */
async function trackTransaction(
  orderId: string,
  intervalMs: number = 5000,
  timeoutMs: number = 300000
): Promise<any> {
  console.log(`Tracking transaction with Order ID: ${orderId}`);

  const startTime = Date.now();
  let lastStatus = '';

  while (Date.now() - startTime < timeoutMs) {
    try {
      const path = 'dex/post-transaction/orders';
      const url = `https://web3.okx.com/api/v5/${path}`;

      const params = {
        orderId: orderId,
        chainIndex: chainId,
        address: WALLET_ADDRESS,
        limit: '1'
      };

      const timestamp = new Date().toISOString();
      const requestPath = `/api/v5/${path}`;
      const queryString = "?" + new URLSearchParams(params).toString();
      const headers = getHeaders(timestamp, 'GET', requestPath, queryString);

      const response = await axios.get(url, { params, headers });

      const responseData = response.data as any;
      if (responseData.code === '0' && responseData.data && responseData.data.length > 0) {
        if (responseData.data[0].orders && responseData.data[0].orders.length > 0) {
          const txData = responseData.data[0].orders[0];
          const status = txData.txStatus;

          if (status !== lastStatus) {
            lastStatus = status;

            if (status === '1') {
              console.log(`Transaction pending: ${txData.txHash || 'Hash not available yet'}`);
            } else if (status === '2') {
              console.log(`Transaction successful: https://web3.okx.com/explorer/base/tx/${txData.txHash}`);
              return txData;
            } else if (status === '3') {
              const failReason = txData.failReason || 'Unknown reason';
              const errorMessage = `Transaction failed: ${failReason}`;

              console.error(errorMessage);

              const errorInfo = handleTransactionError(txData);
              console.log(`Error type: ${errorInfo.error}`);
              console.log(`Suggested action: ${errorInfo.action}`);

              throw new Error(errorMessage);
            }
          }
        } else {
          console.log(`No orders found for Order ID: ${orderId}`);
        }
      }
    } catch (error) {
      console.warn('Error checking transaction status:', (error as Error).message);
    }

    await new Promise(resolve => setTimeout(resolve, intervalMs));
  }

  throw new Error('Transaction tracking timed out');
}

/**
 * Comprehensive error handling with failReason
 * @param txData - Transaction data from post-transaction/orders
 * @returns Structured error information
 */
function handleTransactionError(txData: any): TxErrorInfo {
  const failReason = txData.failReason || 'Unknown reason';

  console.error(`Transaction failed with reason: ${failReason}`);

  return {
    error: 'TRANSACTION_FAILED',
    message: failReason,
    action: 'Try again or contact support'
  };
}

// ======== Main Execution ========

async function simulateOnly(
    fromTokenAddress: string,
    toTokenAddress: string,
    amount: string,
    slippage: string = '0.5'
): Promise<any> {
    try {
        console.log('Starting simulation-only mode...');
        console.log(`Simulation Details:`);
        console.log(`   From Token: ${fromTokenAddress}`);
        console.log(`   To Token: ${toTokenAddress}`);
        console.log(`   Amount: ${amount}`);
        console.log(`   Slippage: ${slippage}%`);

        // Step 1: Get swap data
        const swapData = await getSwapData(fromTokenAddress, toTokenAddress, amount, slippage);
        console.log('Swap data obtained');

        // Step 2: Simulate transaction
        const simulationResult = await simulateTransaction(swapData);
        console.log('Transaction simulation completed');

        // Step 3: Get gas limit
        const gasLimit = await getGasLimit(
            swapData.tx.from,
            swapData.tx.to,
            swapData.tx.value || '0',
            swapData.tx.data
        );

        return {
            success: true,
            swapData,
            simulationResult,
            gasLimit,
            estimatedGasUsed: simulationResult.gasUsed,
        };
    } catch (error) {
        console.error('Simulation failed:', (error as Error).message);
        return { success: false, error: (error as Error).message };
    }
}

async function main() {
    try {
        console.log('EVM Swap Tools with Onchain Gateway API');
        console.log('=====================================');

        // Validate environment variables
        if (!WALLET_ADDRESS || !PRIVATE_KEY) {
            throw new Error('Missing wallet address or private key in environment variables');
        }

        console.log(`Wallet Address: ${WALLET_ADDRESS}`);
        console.log(`Chain ID: ${chainId}`);
        console.log(`RPC URL: ${process.env.EVM_RPC_URL || 'https://mainnet.base.org'}`);

        // Parse command line arguments
        const args = process.argv.slice(2);
        const mode = args[0] || 'simulate'; // Default to simulate mode
        
        // Example parameters
        const fromToken = ETH_ADDRESS;
        const toToken = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913'; // USDC on Base
        const amount = '100000000000000'; // 0.0001 ETH in wei
        const slippage = '0.05'; // 0.5%

        console.log('\nConfiguration:');
        console.log(`   From: ${fromToken} (ETH)`);
        console.log(`   To: ${toToken} (USDC)`);
        console.log(`   Amount: ${web3.utils.fromWei(amount, 'ether')} ETH`);
        console.log(`   Slippage: ${slippage}%`);
        console.log(`   Mode: ${mode}`);

        let result;
        
        switch (mode.toLowerCase()) {
            case 'simulate':
            case 'sim':
                result = await simulateOnly(fromToken, toToken, amount, slippage);
                break;
            case 'execute':
            case 'exec':
                result = await executeSwapWithSimulation(fromToken, toToken, amount, slippage);
                break;
            default:
                console.log('\nAvailable modes:');
                console.log('   simulate/sim  - Only simulate the transaction');
                console.log('   execute/exec  - Execute the full swap');
                console.log('\nExample: npm run evm-swap simulate');
                return;
        }
        
        if (result.success) {
            console.log('\nOperation completed successfully!');
            if (mode === 'simulate' || mode === 'sim') {
                console.log(`Gas Limit: ${result.gasLimit}`);
            } else {
                console.log(`Transaction Hash: ${result.txHash}`);
            }
        } else {
            console.log('\nOperation failed!');
            console.log(`Error: ${result.error}`);
        }
    } catch (error) {
        console.error('Main execution failed:', (error as Error).message);
        process.exit(1);
    }
}

// Run the script
if (require.main === module) {
    main();
}

export {
    executeSwap,
    executeSwapWithSimulation,
    simulateOnly,
    getSwapData,
    simulateTransaction,
    getGasLimit,
    broadcastTransaction,
    trackTransaction
};


You can run this script using evm-swap.ts sim or evm-swap.ts exec.

sim simulates a transaction using swap data using the transaction simulation API and retruns gasLimit info exec executes a transaction using the broadcast API

Method 2: SDK approach#

Using the OKX DEX SDK provides a much simpler developer experience while retaining all the functionality of the API-first approach. The SDK handles many implementation details for you, including retry logic, error handling, and transaction management.

1. Install the SDK#

npm install @okx-dex/okx-dex-sdk
# or
yarn add @okx-dex/okx-dex-sdk
# or
pnpm add @okx-dex/okx-dex-sdk

2. Setup Your Environment#

Create a .env file with your API credentials and wallet information:

# OKX API Credentials
OKX_API_KEY=your_api_key
OKX_SECRET_KEY=your_secret_key
OKX_API_PASSPHRASE=your_passphrase
OKX_PROJECT_ID=your_project_id
# EVM Configuration
EVM_RPC_URL=your_evm_rpc_url
EVM_WALLET_ADDRESS=your_evm_wallet_address
EVM_PRIVATE_KEY=your_evm_private_key

3. Initialize the Client#

Create a file for your DEX client (e.g., DexClient.ts):

// DexClient.ts
import { OKXDexClient } from '@okx-dex/okx-dex-sdk';
import 'dotenv/config';
// Validate environment variables
const requiredEnvVars = [
    'OKX_API_KEY',
    'OKX_SECRET_KEY',
    'OKX_API_PASSPHRASE',
    'OKX_PROJECT_ID',
    'EVM_WALLET_ADDRESS',
    'EVM_PRIVATE_KEY',
    'EVM_RPC_URL'
];
for (const envVar of requiredEnvVars) {
    if (!process.env[envVar]) {
        throw new Error(`Missing required environment variable: ${envVar}`);
    }
}
// Initialize the client
export 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: {
        connection: {
            rpcUrl: process.env.EVM_RPC_URL!,
        },
        walletAddress: process.env.EVM_WALLET_ADDRESS!,
        privateKey: process.env.EVM_PRIVATE_KEY!,
    }
});

4. Token Approval With the SDK#

Create an approval utility function:

// approval.ts
import { client } from './DexClient';
// Helper function to convert human-readable amounts to base units
export function toBaseUnits(amount: string, decimals: number): string {
    // Remove any decimal point and count the decimal places
    const [integerPart, decimalPart = ''] = amount.split('.');
    const currentDecimals = decimalPart.length;

    // Combine integer and decimal parts, removing the decimal point
    let result = integerPart + decimalPart;

    // Add zeros if you need more decimal places
    if (currentDecimals < decimals) {
        result = result + '0'.repeat(decimals - currentDecimals);
    }
    // Remove digits if you have too many decimal places
    else if (currentDecimals > decimals) {
        result = result.slice(0, result.length - (currentDecimals - decimals));
    }

    // Remove leading zeros
    result = result.replace(/^0+/, '') || '0';

    return result;
}
/**
 * Example: Approve a token for swapping
 */
async function executeApproval(tokenAddress: string, amount: string) {
    try {
        // Get token information using quote
        console.log("Getting token information...");
        const tokenInfo = await client.dex.getQuote({
            chainId: '8453',  // Base Chain
            fromTokenAddress: tokenAddress,
            toTokenAddress: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE', // Native token
            amount: '1000000', // Use a reasonable amount for quote
            slippage: '0.005'
        });
        const tokenDecimals = parseInt(tokenInfo.data[0].fromToken.decimal);
        const rawAmount = toBaseUnits(amount, tokenDecimals);
        console.log(`\nApproval Details:`);
        console.log(`--------------------`);
        console.log(`Token: ${tokenInfo.data[0].fromToken.tokenSymbol}`);
        console.log(`Amount: ${amount} ${tokenInfo.data[0].fromToken.tokenSymbol}`);
        console.log(`Amount in base units: ${rawAmount}`);
        // Execute the approval
        console.log("\nExecuting approval...");
        const result = await client.dex.executeApproval({
            chainId: '8453',  // Base Chain
            tokenContractAddress: tokenAddress,
            approveAmount: rawAmount
        });
        if ('alreadyApproved' in result) {
            console.log("\nToken already approved for the requested amount!");
            return { success: true, alreadyApproved: true };
        } else {
            console.log("\nApproval completed successfully!");
            console.log("Transaction Hash:", result.transactionHash);
            console.log("Explorer URL:", result.explorerUrl);
            return result;
        }
    } catch (error) {
        if (error instanceof Error) {
            console.error('Error executing approval:', error.message);
        }
        throw error;
    }
}
// Run if this file is executed directly
if (require.main === module) {
    // Example usage: ts-node approval.ts 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 1000
    const args = process.argv.slice(2);
    if (args.length !== 2) {
        console.log("Usage: ts-node approval.ts <tokenAddress> <amountToApprove>");
        console.log("\nExamples:");
        console.log("  # Approve 1000 USDC");
        console.log(`  ts-node approval.ts 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 1000`);
        process.exit(1);
    }
    const [tokenAddress, amount] = args;
    executeApproval(tokenAddress, amount)
        .then(() => process.exit(0))
        .catch((error) => {
            console.error('Error:', error);
            process.exit(1);
        });
}
export { executeApproval };

5. Execute a Swap With the SDK#

Create a swap execution file:

// swap.ts
import { client } from './DexClient';
/**
 * Example: Execute a swap from ETH to USDC on Base chain
 */
async function executeSwap() {
  try {
    if (!process.env.EVM_PRIVATE_KEY) {
      throw new Error('Missing EVM_PRIVATE_KEY in .env file');
    }
    // You can change this to any EVM chain
    // For example, for Base, use chainId: '8453'
    // For example, for baseSepolia, use chainId: '84532'
    // You can also use SUI, use chainId: '784'
    // When using another Chain, you need to change the fromTokenAddress and toTokenAddress to the correct addresses for that chain

    const swapResult = await client.dex.executeSwap({
      chainId: '8453', // Base chain ID
      fromTokenAddress: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE', // Native ETH
      toTokenAddress: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', // USDC on Base
      amount: String(10 * 10 ** 14), // .0001 ETH
      slippage: '0.005', // 0.5% slippage
      userWalletAddress: process.env.EVM_WALLET_ADDRESS!
    });
    console.log('Swap executed successfully:');
    console.log(JSON.stringify(swapResult, null, 2));

    return swapResult;
  } catch (error) {
    if (error instanceof Error) {
      console.error('Error executing swap:', error.message);
      // API errors include details in the message
      if (error.message.includes('API Error:')) {
        const match = error.message.match(/API Error: (.*)/);
        if (match) console.error('API Error Details:', match[1]);
      }
    }
    throw error;
  }
}
// Run if this file is executed directly
if (require.main === module) {
  executeSwap()
    .then(() => process.exit(0))
    .catch((error) => {
      console.error('Error:', error);
      process.exit(1);
    });
}
export { executeSwap };

6. Additional SDK Functionality#

The SDK provides additional methods that simplify development:

Get a quote for a token pair

const quote = await client.dex.getQuote({
    chainId: '8453',  // Base Chain
    fromTokenAddress: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', // USDC
    toTokenAddress: '0x4200000000000000000000000000000000000006', // WETH
    amount: '1000000',  // 1 USDC (in smallest units)
    slippage: '0.005'     // 0.5%
});