Run your first DApp#
API-First Approach#
This approach demonstrates a token swap using the Trade 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 chainIndex: string = '8453';
// API URL
const baseUrl: string = 'https://web3.okx.com/api/v6/';
// Amount to swap in smallest unit (0.0005 ETH)
const SWAP_AMOUNT: string = '500000000000000'; // 0.0005 ETH
const SLIPPAGEPERCENT: string = '0.5'; // 0.5% slippagePercent 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 = {
chainIndex: chainIndex,
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 = {
chainIndex: chainIndex,
tokenContractAddress: tokenAddress,
approveAmount: amount
};
// Prepare authentication
const timestamp = new Date().toISOString();
const requestPath = `/api/v6/${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.
/**
* 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/v6/${path}`;
const body = {
chainIndex: chainIndex,
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/v6/${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,
chainIndex: chainIndex,
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 slippagePercent - Maximum slippagePercent (e.g., "0.5" for 0.5%)
* @returns Swap quote
*/
async function getSwapQuote(
fromTokenAddress: string,
toTokenAddress: string,
amount: string,
slippagePercent: string = '0.5'
): Promise<any> {
try {
const path = 'dex/aggregator/quote';
const url = `${baseUrl}${path}`;
const params = {
chainIndex: chainIndex,
fromTokenAddress,
toTokenAddress,
amount,
slippagePercent
};
// Prepare authentication
const timestamp = new Date().toISOString();
const requestPath = `/api/v6/${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 = {
chainIndex: chainIndex,
fromTokenAddress,
toTokenAddress,
amount,
userWalletAddress: userAddress,
slippagePercent
};
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 slippagePercent - Maximum slippagePercent (e.g., "0.5" for 0.5%)
* @returns Swap transaction data
*/
async function getSwapTransaction(
fromTokenAddress: string,
toTokenAddress: string,
amount: string,
userAddress: string,
slippagePercent: string = '0.5'
): Promise<any> {
try {
const path = 'dex/aggregator/swap';
const url = `${baseUrl}${path}`;
const params = {
chainIndex: chainIndex,
fromTokenAddress,
toTokenAddress,
amount,
userWalletAddress: userAddress,
slippagePercent
};
// Prepare authentication
const timestamp = new Date().toISOString();
const requestPath = `/api/v6/${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:
The Simulate API is available to our whitelisted customers only. Please reach out to dexapi@okx.com to request access.
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: chainIndex,
extJson: {
inputData: tx.data
},
includeDebug: true
};
const timestamp = new Date().toISOString();
const requestPath = "/api/v6/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#
Use the Transaction API for gas estimation. This approach leverages Transaction API, which provides more accurate gas estimations than standard methods.
/**
* 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/v6/${path}`;
const body = {
chainIndex: chainIndex,
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/v6/${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;
}
}
