In this guide, we’ll provide an example token swap through OKX DEX, using ETH from the Ethereum network to purchase USDC. The process is as follows:
// --------------------- npm package ---------------------
const { Web3} = require('web3');
const cryptoJS = require('crypto-js');
// The URL for the Ethereum node you want to connect to
const web3 = new Web3('https://......com');
const apiBaseUrl = 'https://www.okx.com/api/v5/dex/aggregator';
// --------------------- environment variable ---------------------
const chainId = '1';
// usdc contract address
const fromTokenAddress = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48';
// Native token contract address
const toTokenAddress = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE';
// gasPrice or GasLimit ratio
const ratio = BigInt(3) / BigInt(2);
// your wallet address
const user = '0x6f9fxxxxxxxxxxxxxxxxxxxx61059dcfd9'
const fromAmount = '1000000'
// user wallet private key
const privateKey = 'xxxxx';
// open api Secret key
const secretkey = 'xxxxx'
// Get the current time
const date = new Date();
// --------------------- util function ---------------------
function getAggregatorRequestUrl(methodName, queryParams) {
return apiBaseUrl + methodName + '?' + (new URLSearchParams(queryParams)).toString();
}
// Check https://www.okx.com/zh-hans/web3/build/docs/waas/rest-authentication for api-key
const headersParams = {
'Content-Type': 'application/json',
// The api Key obtained from the previous application
'OK-ACCESS-KEY': 'xxxxx',
'OK-ACCESS-SIGN': cryptoJS.enc.Base64.stringify(
// The field order of headersParams should be consistent with the order of quoteParams.
// example : quote ==> cryptoJS.HmacSHA256(timestamp + 'GET' + '/api/v5/dex/aggregator/quote?amount=1000000&chainId=1&toTokenAddress=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE&fromTokenAddress=0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', secretKey)
cryptoJS.HmacSHA256(date.toISOString() + 'GET' + '/api/v5/dex/aggregator/xxx/xxx/xxx', secretKey)
),
// Convert the current time to the desired format
'OK-ACCESS-TIMESTAMP': date.toISOString(),
// The password created when applying for the key
'OK-ACCESS-PASSPHRASE': 'xxxxxxx',
};
const tokenAddress = fromTokenAddress;
// user address
const ownerAddress = user;
// ETH dex token approval address
const spenderAddress = '0x40aa958dd87fc8305b97f2ba922cddca374bcd7f';
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"
}
];
// Create token contract instance
const tokenContract = new web3.eth.Contract(tokenABI, tokenAddress);
// Query token approve allowance function
async function getAllowance(ownerAddress, spenderAddress) {
try {
const allowance = await tokenContract.methods.allowance(ownerAddress, spenderAddress).call();
return parseFloat(allowance);
} catch (error) {
console.error('Failed to query allowance:', error);
}
}
const getApproveTransactionParams = {
chainId: chainId,
tokenContractAddress: fromTokenAddress,
approveAmount: fromAmount,
};
const approveTransaction = async () => {
const apiRequestUrl = getAggregatorRequestUrl(
'/approve-transaction',
getApproveTransactionParams
);
console.log('apiRequestUrl:', apiRequestUrl)
return fetch(apiRequestUrl, {
method: 'get',
headers: headersParams,
})
.then((res) => res.json())
.then((res) => {
return res;
});
};
async function sendApproveTx() {
const allowanceAmount = await getAllowance(ownerAddress, spenderAddress);
if (allowanceAmount < parseFloat(fromAmount)) {
let gasPrice = await web3.eth.getGasPrice();
let nonce = await web3.eth.getTransactionCount(user)
const {data} = await approveTransaction();
const txObject = {
nonce: nonce,
to: getApproveTransactionParams.tokenContractAddress, // approve token address
gasLimit: data[0].gasLimit * 2, // avoid GasLimit too low
gasPrice: gasPrice * BigInt(3) / BigInt(2), // avoid GasPrice too low
data: swapDataTxInfo.data, // approve callData
value: 0 // approve value fix 0
};
const {rawTransaction} = await web3.eth.accounts.signTransaction(
txObject,
privateKey
);
await web3.eth.sendSignedTransaction(rawTransaction);
}
}
const quoteParams = {
amount: fromAmount,
chainId: chainId,
toTokenAddress: toTokenAddress,
fromTokenAddress: fromTokenAddress,
};
const getQuote = async () => {
const apiRequestUrl = getAggregatorRequestUrl('/quote', quoteParams);
return fetch(apiRequestUrl, {
method: 'get',
headers: headersParams,
})
.then((res) => res.json())
.then((res) => {
return res;
});
};
const swapParams = {
chainId: chainid,
fromTokenAddress: fromTokenAddress,
toTokenAddress: toTokenAddress,
amount: fromAmount,
slippage: '0.03',
userWalletAddress: user
};
const getSwapData = async () => {
const apiRequestUrl = getAggregatorRequestUrl('/swap', swapParams);
return fetch(apiRequestUrl, {
method: 'get',
headers: headersParams,
})
.then((res) => res.json())
.then((res) => {
return res;
});
};
async function sendSwapTx() {
const {data: swapData} = await getSwapData();
console.log('swapData:', swapData)
const swapDataTxInfo = swapData[0].tx;
const nonce = await web3.eth.getTransactionCount(user, 'latest');
let signTransactionParams = {
data: swapDataTxInfo.data,
gasPrice: BigInt(swapDataTxInfo.gasPrice) * BigInt(ratio), // avoid GasPrice too low,
to: swapDataTxInfo.to,
value: swapDataTxInfo.value,
gas: BigInt(swapDataTxInfo.gas) * BigInt(ratio), // avoid GasLimit too low
nonce,
};
const {rawTransaction} = await web3.eth.accounts.signTransaction(
signTransactionParams,
privateKey
);
const chainTxInfo = await web3.eth.sendSignedTransaction(rawTransaction);
console.log('chainTxInfo:', chainTxInfo);
}