DEX API
在 EVM 链上搭建兑换应用

在 EVM 链上搭建兑换应用#

搭建单链应用#

在本指南中,我们将用一个示例来展示如何通过欧易 DEX 提供的 API 在 Ethereum 上用 USDC 兑换 ETH,这个过程中的步骤包括:

  1. 设置你的环境
  2. 检查授权额度
  3. 检查授权交易参数并发起授权
  4. 请求询价接口,获取询价数据
  5. 请求兑换接口,发起交易

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 语言
  1. 连接到以太坊节点:你需要确保你已经连接到了一个可用的以太坊节点。你可以使用 web3.js 或其他以太坊开发库来连接到节点。在代码中,你需要指定节点的 HTTP 或 WebSocket 端点。
  2. 获取代币合约实例:为使用代币的合约地址和 ABI,你需要创建一个代币合约的实例。你可以使用 web3.js 的 web3.eth.Contract 方法来实现这一点,将合约地址和 ABI 作为参数传递给合约实例。 a. 查询授权额度:通过调用合约实例的 allowance 函数来查询授权额度。该函数需要两个参数:拥有者的地址和被授权者的地址。你可以在调用时提供这两个地址来查询授权额度。
  3. 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: 1,
  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: swapDataTxInfo.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: '1000000',
  chainId: 1,
  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: '1000000',
  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);
}