跳转到主要内容

执行交换

本指南将带你完成在 Kroko DEX 上执行代币交换的完整 5 步流程。

概览

步骤 1:授权代币 → Permit2              (每个代币仅需一次)
步骤 2:Permit2 → 授权 Universal Router (每个代币仅需一次)
步骤 3:从 API 获取报价
步骤 4:从 API 获取交换 calldata
步骤 5:发送交易

前置条件

import { ethers } from 'ethers';

const PERMIT2 = '0x2E1987F680FD7Bc8B33d3Bf94f12B988A0B50034';
const UNIVERSAL_ROUTER = '0xefeCc1c2dE3BfE4C6D43030F2AcDD5C3cE279024';
const API_BASE = 'https://dex.kasplex.org/swap-api';

const ERC20_ABI = [
  'function allowance(address,address) view returns (uint256)',
  'function approve(address,uint256) returns (bool)'
];

const PERMIT2_ABI = [
  'function approve(address token, address spender, uint160 amount, uint48 expiration)',
  'function allowance(address owner, address token, address spender) view returns (uint160, uint48, uint48)'
];

步骤 1:授权代币给 Permit2

每个代币只需要授权给 Permit2 一次。我们建议授权最大额度。
const tokenContract = new ethers.Contract(tokenIn, ERC20_ABI, signer);
const userAddress = await signer.getAddress();

// 检查现有授权额度
const allowance = await tokenContract.allowance(userAddress, PERMIT2);

if (allowance < amountIn) {
  console.log('正在授权代币给 Permit2...');
  const tx = await tokenContract.approve(PERMIT2, ethers.MaxUint256);
  await tx.wait();
}

步骤 2:通过 Permit2 授权 Universal Router

通过 Permit2 授予 Universal Router 使用你代币的权限。
const permit2 = new ethers.Contract(PERMIT2, PERMIT2_ABI, signer);

// 检查现有的 Permit2 授权
const [amount, expiration] = await permit2.allowance(
  userAddress,
  tokenIn,
  UNIVERSAL_ROUTER
);
const now = Math.floor(Date.now() / 1000);

if (amount < amountIn || expiration < now) {
  console.log('正在通过 Permit2 授权 Universal Router...');
  const tx = await permit2.approve(
    tokenIn,
    UNIVERSAL_ROUTER,
    ethers.MaxUint160,            // 最大 uint160
    now + 365 * 24 * 60 * 60      // 1 年
  );
  await tx.wait();
}

步骤 3:获取报价

获取最优交换路由和预期产出。
// "我想卖出 1 个代币 — 能得到多少?"
const params = new URLSearchParams({
  tokenIn,
  tokenOut,
  amountIn: '1000000000000000000',
  tradeType: '0'
});

const res = await fetch(`${API_BASE}/api/v1/quote?${params}`);
const quote = await res.json();

console.log(`预期产出:${quote.amountOut}`);
console.log(`价格影响:${quote.priceImpact}%`);
console.log(`路由:${quote.route.protocol}${quote.route.hops} 跳)`);

步骤 4:获取交换 Calldata

从 Swap API 请求编码后的交易数据。
const swapRes = await fetch(`${API_BASE}/api/v1/swap`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    tokenIn,
    tokenOut,
    amountIn: '1000000000000000000',
    tradeType: 0,
    slippage: 0.5,        // 0.5% 滑点容差
    recipient: userAddress,
    deadline: 1200         // 20 分钟
  })
});

const swapData = await swapRes.json();

// 验证滑点保护
console.log(`最小产出:${swapData.quote.minAmountOut}`);

步骤 5:执行交换

使用 API 返回的 calldata 发送交易。
const tx = await signer.sendTransaction({
  to: swapData.to,        // Universal Router
  data: swapData.data,    // 编码后的 calldata
  value: swapData.value   // ERC-20 为 0,原生代币则为 KAS 数量
});

const receipt = await tx.wait();
console.log(`交换已执行:${receipt.hash}`);

原生 KAS 交换

卖出原生 KAS 时,流程相同,但有以下区别:
  • 在 API 调用中使用 WKAS 地址作为 tokenIn
  • 跳过步骤 1 和 2(原生 KAS 无需授权)
  • API 返回非零的 value — 将其包含在交易中
// 卖出 KAS 兑换代币
const swapData = await getSwapData({
  tokenIn: WKAS_ADDRESS,   // 使用 WKAS 地址,而不是零地址
  tokenOut: targetToken,
  amountIn: '1000000000000000000',
  // ...
});

// value 将为非零值
const tx = await signer.sendTransaction({
  to: swapData.to,
  data: swapData.data,
  value: swapData.value  // "1000000000000000000"(1 KAS)
});

错误处理

错误原因解决方案
TRANSFER_FROM_FAILED缺少授权或余额不足检查步骤 1 和 2
EXPIRED截止时间已过使用更长的截止时间或重新提交
V3_INVALID_AMOUNT_OUT滑点超限增加滑点容差
No route found不存在流动性路径检查代币地址和池是否存在
ACTION_REJECTED(code 4001)用户在钱包中拒绝无需操作
更多错误处理详情请参阅 错误处理