在 EVM 链上搭建兑换应用#
搭建单链应用#
在本指南中,我们将用一个示例来展示如何通过欧易 DEX 提供的 API 在 Ethereum 上用 USDC 兑换 ETH,这个过程中的步骤包括:
- 设置你的环境
- 检查授权额度
- 检查授权交易参数并发起授权
- 请求询价接口,获取询价数据
- 请求兑换接口,发起交易
1. 设置你的环境#
// --------------------- 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',
};
2.检查授权额度#
以 ETH 网络举例#
- 示例为 JavaScript 语言
- 连接到以太坊节点:你需要确保你已经连接到了一个可用的以太坊节点。你可以使用 web3.js 或其他以太坊开发库来连接到节点。在代码中,你需要指定节点的 HTTP 或 WebSocket 端点。
- 获取代币合约实例:为使用代币的合约地址和 ABI,你需要创建一个代币合约的实例。你可以使用 web3.js 的 web3.eth.Contract 方法来实现这一点,将合约地址和 ABI 作为参数传递给合约实例。 a. 查询授权额度:通过调用合约实例的 allowance 函数来查询授权额度。该函数需要两个参数:拥有者的地址和被授权者的地址。你可以在调用时提供这两个地址来查询授权额度。
- spenderAddress 地址可以参考此处接口 Response 中的 dexTokenApproveAddress。
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);
}
}
- 下文的 allowanceAmount 代表真实的链上授权额度
3. 检查授权交易参数并发起授权#
提示
由于 allowanceAmount 小于 fromTokenAmount,你需要对该币种进行授权。
3.1 定义授权交易参数#
- 接下来,定义你要执行授权交易的参数。
const getApproveTransactionParams = {
chainId: chainId,
tokenContractAddress: fromTokenAddress,
approveAmount: '1000000',
};
3.2 定义辅助函数#
- 定义一个辅助函数,用于与 DEX API 进行交互。
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;
});
};
3.3 获取授权交易 tx 并且发送授权请求#
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: data[0].data, // approve callData
value: 0 // approve value fix 0
};
const {rawTransaction} = await web3.eth.accounts.signTransaction(
txObject,
privateKey
);
await web3.eth.sendSignedTransaction(rawTransaction);
}
}
4. 请求询价接口,拿到询价数据#
4.1 定义询价参数#
- 接下来,定义询价参数,获取询价的基础信息和路径列表信息。
const quoteParams = {
amount: fromAmount,
chainId: chainId,
toTokenAddress: toTokenAddress,
fromTokenAddress: fromTokenAddress,
};
4.2 定义辅助函数#
- 定义一个辅助函数,用于与 DEX API 进行交互。
const getQuote = async () => {
const apiRequestUrl = getAggregatorRequestUrl('/quote', quoteParams);
return fetch(apiRequestUrl, {
method: 'get',
headers: headersParams,
})
.then((res) => res.json())
.then((res) => {
return res;
});
};
5. 请求兑换接口,发起交易#
5.1 定义兑换参数#
- 接下来,定义参数,并获取兑换的 tx 信息。
const swapParams = {
chainId: 1,
fromTokenAddress: fromTokenAddress,
toTokenAddress: toTokenAddress,
amount: fromAmount,
slippage: '0.03',
userWalletAddress: user
};
5.2 定义辅助函数#
定义一个辅助函数,用于与 DEX 批准交易 API 进行交互。
const getSwapData = async () => {
const apiRequestUrl = getAggregatorRequestUrl('/swap', swapParams);
return fetch(apiRequestUrl, {
method: 'get',
headers: headersParams,
})
.then((res) => res.json())
.then((res) => {
return res;
});
};
5.3 请求兑换接口拿到 tx 信息,发起上链交易#
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);
}