执行交换
本指南将带你完成在 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} 跳)`);
// "我想买入 1 个代币 — 需要多少?"
const params = new URLSearchParams({
tokenIn,
tokenOut,
amountOut: '1000000000000000000',
tradeType: '1'
});
const res = await fetch(`${API_BASE}/api/v1/quote?${params}`);
const quote = await res.json();
console.log(`需要输入:${quote.amountIn}`);
步骤 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) | 用户在钱包中拒绝 | 无需操作 |