> ## Documentation Index
> Fetch the complete documentation index at: https://docs.krokoswap.io/llms.txt
> Use this file to discover all available pages before exploring further.

# 执行交换

> 逐步指南：如何执行代币交换

# 执行交换

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

## 概览

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

## 前置条件

```typescript theme={null}
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 一次。我们建议授权最大额度。

```typescript theme={null}
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 使用你代币的权限。

```typescript theme={null}
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：获取报价

获取最优交换路由和预期产出。

<CodeGroup>
  ```typescript Exact Input theme={null}
  // "我想卖出 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} 跳）`);
  ```

  ```typescript Exact Output theme={null}
  // "我想买入 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}`);
  ```
</CodeGroup>

## 步骤 4：获取交换 Calldata

从 Swap API 请求编码后的交易数据。

```typescript theme={null}
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 发送交易。

```typescript theme={null}
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` — 将其包含在交易中

```typescript theme={null}
// 卖出 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） | 用户在钱包中拒绝  | 无需操作           |

更多错误处理详情请参阅 [错误处理](/guides/error-handling)。
