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

# 如何使用 Kuru Flow 在您的应用中添加代币兑换

您的应用可能需要允许用户在两种资产之间进行兑换。您希望用户能够
获得整个生态系统中最优的价格。

聚合器会检查多个流动性来源并找到最佳价格。因此，集成一个聚合器 API
可以为您的应用启用便捷高效的兑换功能。

在本指南中，我们将了解如何集成一个聚合器：[Kuru Flow](https://docs.kuru.io/kuru-flow/flow-overview)。
您将学习如何将 Kuru Flow 集成到前端应用中——从身份验证到获取报价再到执行兑换。

## 要求

在开始之前，请确保您具备以下条件：

* 一个具有钱包连接的前端框架（React、Next.js 等）
* [wagmi](https://wagmi.sh) 和 [viem](https://viem.sh) 用于钱包交互
* 已连接到 Monad 主网（链 ID `143`）的用户钱包

## 1. 配置身份验证

我们首先需要获取访问 Kuru Flow API 的凭证。
Kuru Flow 使用 JWT token 进行 API 身份验证。

```typescript lines title="lib/kuru-flow.ts" theme={null}
const KURU_FLOW_API = "https://ws.kuru.io";

async function generateToken(userAddress: string) {
  const response = await fetch(`${KURU_FLOW_API}/api/generate-token`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ user_address: userAddress }),
  });

  return response.json();
  // Returns: { token: string, expires_at: number, rate_limit: { rps: number, burst: number } }
}
```

## 2. 获取兑换报价

`/api/quote` 端点会计算最佳兑换路径，并返回交易数据。

支持以下参数：

| 参数                  | 必需  | 说明                    |
| ------------------- | --- | --------------------- |
| `userAddress`       | 是   | 用户的钱包地址               |
| `tokenIn`           | 是   | 代币地址（原生 MON 使用 `0x0`） |
| `tokenOut`          | 是   | 代币地址（原生 MON 使用 `0x0`） |
| `amount`            | 是   | 代币最小单位的数量             |
| `autoSlippage`      | 否\* | 设为 `true` 以自动计算滑点     |
| `slippageTolerance` | 否\* | 手动滑点（以基点为单位，1-10000）  |
| `referrerAddress`   | 否   | 用于接收推荐费的您的地址          |
| `referrerFeeBps`    | 否   | 推荐费（以基点为单位，0-10000）   |

\*必须提供 `autoSlippage: true` 或 `slippageTolerance` 之一。

我们可以编写一个小函数与 `quote` API 交互：

```typescript lines title="lib/kuru-flow.ts" theme={null}
const KURU_FLOW_API = "https://ws.kuru.io";

// Your wallet address to receive referral fees
const REFERRER_ADDRESS = "0xYOUR_WALLET_ADDRESS_HERE";
const REFERRER_FEE_BPS = 50; // 0.5%

async function getQuote(
  userAddress: string,
  tokenIn: string,
  tokenOut: string,
  amount: string,
  token: string
) {
  const response = await fetch(`${KURU_FLOW_API}/api/quote`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${token}`,
    },
    body: JSON.stringify({
      userAddress,
      tokenIn,
      tokenOut,
      amount,
      autoSlippage: true,
      referrerAddress: REFERRER_ADDRESS,
      referrerFeeBps: REFERRER_FEE_BPS,
    }),
  });

  return response.json();
}
```

### 响应结构

```typescript lines title="lib/kuru-flow.ts" theme={null}
export interface QuoteResponse {
  type: string;
  status: "success" | "error";
  output: string;           // Expected output amount (in wei)
  minOut: string;           // Minimum output with slippage
  transaction: {
    to: string;             // Router contract address
    calldata: string;       // Transaction calldata (WITHOUT 0x prefix!)
    value: string;          // Native MON amount (non-zero when swapping native MON)
  };
  gasPrices: {
    slow: string;
    standard: string;
    fast: string;
    rapid: string;
    extreme: string;
  };
  message?: string;         // Error message if status is "error"
}
```

<Note>
  `calldata` 字段**不**包含 `0x` 前缀。在发送交易之前，您必须自行添加。
</Note>

## 3. 检查代币授权额度（仅 ERC20）

在使用 ERC20 代币执行兑换之前，我们应验证 router 是否有权限
花费用户的代币。

<Tip>
  **原生 MON 不需要授权。** 兑换原生 MON 时跳过此步骤。
</Tip>

```typescript lines title="components/SwapCard.tsx" theme={null}
import { useState } from "react";
import { useAccount, useReadContract } from "wagmi";
import { parseUnits } from "viem";
import type { QuoteResponse } from "@/lib/kuru-flow";

// Token addresses
const NATIVE_MON = "0x0000000000000000000000000000000000000000" as const;
const USDC = "0x754704Bc059F8C67012fEd69BC8A327a5aafb603" as const;

const ERC20_ABI = [
  {
    name: "allowance",
    type: "function",
    stateMutability: "view",
    inputs: [
      { name: "owner", type: "address" },
      { name: "spender", type: "address" },
    ],
    outputs: [{ name: "", type: "uint256" }],
  },
] as const;

// Inside your component:
const { address } = useAccount();
const [amount, setAmount] = useState("");
const [quote, setQuote] = useState<QuoteResponse | null>(null);

const tokenIn = USDC; // Example: swapping USDC
const isNativeToken = tokenIn === NATIVE_MON;
const amountWei = amount ? parseUnits(amount, 6) : BigInt(0);

// Check allowance against router address from quote (skip for native MON)
const { data: allowance } = useReadContract({
  address: tokenIn,
  abi: ERC20_ABI,
  functionName: "allowance",
  args: address && quote?.transaction?.to
    ? [address, quote.transaction.to as `0x${string}`]
    : undefined,
  query: {
    enabled: !isNativeToken, // Don't check allowance for native token
  },
});

// Native tokens don't need approval
const needsApproval = !isNativeToken && allowance !== undefined && amountWei > allowance;
```

## 4. 授权代币花费（仅 ERC20）

如果 ERC20 代币的授权额度不足，我们应在兑换之前请求授权。

```typescript lines title="components/SwapCard.tsx" theme={null}
import { encodeFunctionData } from "viem";
import { useSendTransaction } from "wagmi";

const APPROVE_ABI = [
  {
    name: "approve",
    type: "function",
    stateMutability: "nonpayable",
    inputs: [
      { name: "spender", type: "address" },
      { name: "amount", type: "uint256" },
    ],
    outputs: [{ name: "", type: "bool" }],
  },
] as const;

// Inside your component:
const { sendTransaction: sendApproveTx } = useSendTransaction();

const handleApprove = () => {
  if (!quote?.transaction?.to) return;

  const data = encodeFunctionData({
    abi: APPROVE_ABI,
    functionName: "approve",
    args: [quote.transaction.to as `0x${string}`, amountWei],
  });

  sendApproveTx({
    to: tokenIn,
    data,
  });
};
```

<Tip>
  使用 viem 的 `encodeFunctionData` 而非手动进行十六进制字符串操作，以获得可靠的 ABI 编码。
</Tip>

## 5. 执行兑换

要提交兑换，我们应使用报价响应中的交易数据：

```typescript lines title="components/SwapCard.tsx" theme={null}
// Inside your component:
const { sendTransaction: sendSwapTx } = useSendTransaction();

const handleSwap = () => {
  if (!quote || quote.status !== "success") return;

  // IMPORTANT: Add 0x prefix to calldata
  const calldata = quote.transaction.calldata.startsWith("0x")
    ? quote.transaction.calldata
    : `0x${quote.transaction.calldata}`;

  sendSwapTx({
    to: quote.transaction.to as `0x${string}`,
    data: calldata as `0x${string}`,
    value: BigInt(quote.transaction.value || "0"),
  });
};
```

## 代币地址和精度（主网）

| 代币      | 地址                                           | 精度 | 备注   |
| ------- | -------------------------------------------- | -- | ---- |
| MON（原生） | `0x0000000000000000000000000000000000000000` | 18 | 无需授权 |
| USDC    | `0x754704Bc059F8C67012fEd69BC8A327a5aafb603` | 6  | 需要授权 |

<Warning>
  格式化输出时请注意代币的精度。USDC 使用 6 位小数，而非 18 位。

  ```typescript theme={null}
  import { formatEther, formatUnits } from "viem";

  // WRONG - assumes 18 decimals
  formatEther(BigInt(quote.output))  // Shows 0.000000002301622011

  // CORRECT - use actual token decimals
  formatUnits(BigInt(quote.output), 6)  // Shows 2301.62
  ```
</Warning>

## 推荐费系统

Kuru 具有推荐系统。作为推荐人，我们可以通过在报价请求中包含推荐详情，从每次兑换中获得费用。

```typescript lines title="lib/kuru-flow.ts" theme={null}
// Replace with YOUR wallet address to receive referral fees
export const REFERRER_ADDRESS = "0xYOUR_WALLET_ADDRESS_HERE";

// Fee in basis points (50 = 0.5%, 100 = 1%)
export const REFERRER_FEE_BPS = 50;
```

**在 50 bps（0.5%）下的费用计算示例：**

| 兑换输出      | 费用金额     |
| --------- | -------- |
| 100 USDC  | 0.5 USDC |
| 500 USDC  | 2.5 USDC |
| 1000 USDC | 5 USDC   |

<Warning>
  在部署之前，将 `0xYOUR_WALLET_ADDRESS_HERE` 替换为您的实际钱包地址。`referrerAddress` 必须是有效的以太坊地址，否则交易将会失败。
</Warning>

## 错误处理

在过程中，我们可能会遇到错误。以下是如何理解它们的说明：

| 状态码 | 含义                 |
| --- | ------------------ |
| 400 | 请求错误 - 检查参数        |
| 401 | 未授权 - token 无效或已过期 |
| 429 | 速率受限 - 请等待后重试      |
| 500 | 服务端错误 - 稍后重试       |

## 常见错误

1. **缺少 0x 前缀** - 发送前请为 calldata 添加 `0x` 前缀
2. **推荐人地址无效** - 必须是有效的以太坊地址
3. **跳过 ERC20 授权** - ERC20 代币需要在兑换前授权（原生 MON 不需要）
4. **精度错误** - USDC 有 6 位小数，MON 有 18 位小数
5. **原生 MON 兑换时 value 为零** - 兑换原生 MON 时，交易 `value` 必须设置为兑换金额

## 后续步骤

* 尝试[在线演示](https://kuru-flow-api-example-one.vercel.app/)以观察 Kuru Flow 的实际效果
* 克隆[示例仓库](https://github.com/monad-developers/kuru-flow-api-example)以获取完整的可运行实现
* 阅读 [Kuru Flow 概述](https://docs.kuru.io/kuru-flow/flow-overview)以了解更多详情
* 查看 [Monad 网络信息](/zh/developer-essentials/network-information)以获取 RPC 端点和链配置
