# 代币交换

在 CPMM 池中执行代币交换。SDK 支持两种类型： **base-input** （固定输入数量）和 **base-output** （固定输出数量）交换。

***

### Base-input swap（精确输入）

指定要卖出的代币精确数量。SDK 会计算你将收到多少代币。

```typescript
import {
  ApiV3PoolInfoStandardItemCpmm,
  CpmmKeys,
  CpmmParsedRpcData,
  CurveCalculator,
  FeeOn,
  TxVersion,
} from '@raydium-io/raydium-sdk-v2'
import { NATIVE_MINT } from '@solana/spl-token'
import BN from 'bn.js'
import { initSdk } from '../config'

const swap = async () => {
  const raydium = await initSdk()

  const poolId = '7JuwJuNU88gurFnyWeiyGKbFmExMWcmRZntn9imEzdny'
  const inputAmount = new BN(100)
  const inputMint = NATIVE_MINT.toBase58()

  let poolInfo: ApiV3PoolInfoStandardItemCpmm
  let poolKeys: CpmmKeys | undefined
  let rpcData: CpmmParsedRpcData

  if (raydium.cluster === 'mainnet') {
    const data = await raydium.api.fetchPoolById({ ids: poolId })
    poolInfo = data[0] as ApiV3PoolInfoStandardItemCpmm
    rpcData = await raydium.cpmm.getRpcPoolInfo(poolInfo.id, true)
  } else {
    const data = await raydium.cpmm.getPoolInfoFromRpc(poolId)
    poolInfo = data.poolInfo
    poolKeys = data.poolKeys
    rpcData = data.rpcData
  }

  if (inputMint !== poolInfo.mintA.address && inputMint !== poolInfo.mintB.address)
    throw new Error('input mint does not match pool')

  const baseIn = inputMint === poolInfo.mintA.address

  // 在链下计算交换
  const swapResult = CurveCalculator.swapBaseInput(
    inputAmount,
    baseIn ? rpcData.baseReserve : rpcData.quoteReserve,
    baseIn ? rpcData.quoteReserve : rpcData.baseReserve,
    rpcData.configInfo!.tradeFeeRate,
    rpcData.configInfo!.creatorFeeRate,
    rpcData.configInfo!.protocolFeeRate,
    rpcData.configInfo!.fundFeeRate,
    rpcData.feeOn === FeeOn.BothToken || rpcData.feeOn === FeeOn.OnlyTokenB
  )

  /**
   * swapResult.inputAmount   -> 手续费后的实际输入量
   * swapResult.outputAmount  -> 收到的代币数量
   * swapResult.tradeFee      -> 针对 input mint 收取的手续费
   */

  const { execute } = await raydium.cpmm.swap({
    poolInfo,
    poolKeys,
    inputAmount,
    swapResult,
    slippage: 0.001, // 0.1% — 范围：1（100%）到 0.0001（0.01%）
    baseIn,
    txVersion: TxVersion.V0,
    // 可选：在此设置优先费
    // computeBudgetConfig: {
    //   units: 600000,
    //   microLamports: 4659150,
    // },
  })

  const { txId } = await execute({ sendAndConfirm: true })
  console.log(`swapped: ${poolInfo.mintA.symbol} to ${poolInfo.mintB.symbol}:`, {
    txId: `https://explorer.solana.com/tx/${txId}`,
  })
}

swap()
```

***

### Base-output swap（精确输出）

指定要收到的代币精确数量。SDK 会计算你需要卖出多少代币。

```typescript
import {
  ApiV3PoolInfoStandardItemCpmm,
  CpmmKeys,
  CpmmParsedRpcData,
  CurveCalculator,
  FeeOn,
  TxVersion,
} from '@raydium-io/raydium-sdk-v2'
import { PublicKey } from '@solana/web3.js'
import BN from 'bn.js'
import { initSdk } from '../config'

const swapBaseOut = async () => {
  const raydium = await initSdk()

  const poolId = '7JuwJuNU88gurFnyWeiyGKbFmExMWcmRZntn9imEzdny'
  const outputAmount = new BN(1_000_000_000)
  const outputMint = new PublicKey('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v')

  let poolInfo: ApiV3PoolInfoStandardItemCpmm
  let poolKeys: CpmmKeys | undefined
  let rpcData: CpmmParsedRpcData

  if (raydium.cluster === 'mainnet') {
    const data = await raydium.api.fetchPoolById({ ids: poolId })
    poolInfo = data[0] as ApiV3PoolInfoStandardItemCpmm
    rpcData = await raydium.cpmm.getRpcPoolInfo(poolInfo.id, true)
  } else {
    const data = await raydium.cpmm.getPoolInfoFromRpc(poolId)
    poolInfo = data.poolInfo
    poolKeys = data.poolKeys
    rpcData = data.rpcData
  }

  if (
    outputMint.toBase58() !== poolInfo.mintA.address &&
    outputMint.toBase58() !== poolInfo.mintB.address
  )
    throw new Error('output mint does not match pool')

  const baseIn = outputMint.toBase58() === poolInfo.mintB.address

  // 将输出限制在可用储备内
  const reserveKey = baseIn ? 'quoteReserve' : 'baseReserve'
  const clampedOutput = outputAmount.gt(rpcData[reserveKey])
    ? rpcData[reserveKey].sub(new BN(1))
    : outputAmount

  const swapResult = CurveCalculator.swapBaseOutput(
    clampedOutput,
    baseIn ? rpcData.baseReserve : rpcData.quoteReserve,
    baseIn ? rpcData.quoteReserve : rpcData.baseReserve,
    rpcData.configInfo!.tradeFeeRate,
    rpcData.configInfo!.creatorFeeRate,
    rpcData.configInfo!.protocolFeeRate,
    rpcData.configInfo!.fundFeeRate,
    rpcData.feeOn === FeeOn.BothToken || rpcData.feeOn === FeeOn.OnlyTokenB
  )

  /**
   * swapResult.sourceAmountSwapped       -> 所需输入数量
   * swapResult.destinationAmountSwapped   -> 实际收到的输出数量
   * swapResult.tradeFee                   -> 针对 input mint 收取的手续费
   */

  const { execute } = await raydium.cpmm.swap({
    poolInfo,
    poolKeys,
    inputAmount: new BN(0), // 当 fixedOut 为 true 时忽略
    fixedOut: true,
    swapResult,
    slippage: 0.001, // 0.1%
    baseIn,
    txVersion: TxVersion.V0,
    // 可选：在此设置优先费
    // computeBudgetConfig: {
    //   units: 600000,
    //   microLamports: 465915,
    // },
  })

  const { txId } = await execute({ sendAndConfirm: true })
  console.log('swapped:', { txId: `https://explorer.solana.com/tx/${txId}` })
}

swapBaseOut()
```

***

### Swap 参数

| 参数            | 类型        | 描述                                                                                   |
| ------------- | --------- | ------------------------------------------------------------------------------------ |
| `poolInfo`    | object    | 来自 API 或 RPC 的池信息。                                                                   |
| `poolKeys`    | object    | 池的 keys。devnet 需要。                                                                   |
| `inputAmount` | BN        | 输入代币数量（base-input）。当 `fixedOut: true`.                                               |
| `fixedOut`    | boolean   | 为 base-output 交换设置为 `true` 。默认值 `false`.                                             |
| `swapResult`  | object    | 预先计算的交换结果，来自 `CurveCalculator.swapBaseInput()` 或 `CurveCalculator.swapBaseOutput()`. |
| `slippage`    | number    | 以小数表示的滑点容忍度。 `0.001` = 0.1%。范围： `1` （100%）到 `0.0001` (0.01%).                        |
| `baseIn`      | boolean   | `true` 如果是 mintA → mintB 交换， `false` 则用于 mintB → mintA。                              |
| `txVersion`   | TxVersion | 交易版本。                                                                                |

### CurveCalculator

该 `CurveCalculator` 在提交交易前离链计算交换数量。

| 方法                 | 描述                 |
| ------------------ | ------------------ |
| `swapBaseInput()`  | 给定精确输入数量，计算输出和手续费。 |
| `swapBaseOutput()` | 给定期望输出数量，计算所需输入。   |

这两种方法都使用池中相同的手续费参数： `configInfo`:

| 参数                | 描述                             |
| ----------------- | ------------------------------ |
| `tradeFeeRate`    | 从交换输入中收取的手续费，单位为 1e-6。         |
| `creatorFeeRate`  | 发送给池创建者的额外手续费。                 |
| `protocolFeeRate` | 发送给 Raydium protocol 的交易手续费份额。 |
| `fundFeeRate`     | 发送给 Raydium treasury 的交易手续费份额。 |

{% hint style="info" %}
在计算交换前，始终获取最新的 RPC 数据（`getRpcPoolInfo`）以获得最新储备并避免过期报价。
{% endhint %}
