# 代币交换

在 CLMM 池中执行代币交换。SDK 支持以下两种： **base-input** （固定输入金额）和 **base-output** （固定输出金额）交换。

***

### Base-input 交换（精确输入）

指定要卖出的代币精确数量。SDK 会计算跨越 tick arrays 的交换路径，并计算你将收到多少代币。

```typescript
import {
  ApiV3PoolInfoConcentratedItem,
  ClmmKeys,
  ComputeClmmPoolInfo,
  PoolUtils,
  ReturnTypeFetchMultiplePoolTickArrays,
  RAYMint,
} from '@raydium-io/raydium-sdk-v2'
import BN from 'bn.js'
import { initSdk, txVersion } from '../config'
import { isValidClmm } from './utils'

const swap = async () => {
  const raydium = await initSdk()
  let poolInfo: ApiV3PoolInfoConcentratedItem
  const poolId = 'DiwsGxJYoRZURvyCtMsJVyxR86yZBBbSYeeWNm7YCmT6'
  const inputMint = RAYMint.toBase58()
  let poolKeys: ClmmKeys | undefined
  let clmmPoolInfo: ComputeClmmPoolInfo
  let tickCache: ReturnTypeFetchMultiplePoolTickArrays

  const inputAmount = new BN(100)

  if (raydium.cluster === 'mainnet') {
    const data = await raydium.api.fetchPoolById({ ids: poolId })
    poolInfo = data[0] as ApiV3PoolInfoConcentratedItem
    if (!isValidClmm(poolInfo.programId)) throw new Error('target pool is not CLMM pool')

    clmmPoolInfo = await PoolUtils.fetchComputeClmmInfo({
      connection: raydium.connection,
      poolInfo,
    })
    tickCache = await PoolUtils.fetchMultiplePoolTickArrays({
      connection: raydium.connection,
      poolKeys: [clmmPoolInfo],
    })
  } else {
    const data = await raydium.clmm.getPoolInfoFromRpc(poolId)
    poolInfo = data.poolInfo
    poolKeys = data.poolKeys
    clmmPoolInfo = data.computePoolInfo
    tickCache = data.tickData
  }

  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 { minAmountOut, remainingAccounts } = await PoolUtils.computeAmountOutFormat({
    poolInfo: clmmPoolInfo,
    tickArrayCache: tickCache[poolId],
    amountIn: inputAmount,
    tokenOut: poolInfo[baseIn ? 'mintB' : 'mintA'],
    slippage: 0.01,
    epochInfo: await raydium.fetchEpochInfo(),
  })

  const { execute } = await raydium.clmm.swap({
    poolInfo,
    poolKeys,
    inputMint: poolInfo[baseIn ? 'mintA' : 'mintB'].address,
    amountIn: inputAmount,
    amountOutMin: minAmountOut.amount.raw,
    observationId: clmmPoolInfo.observationId,
    ownerInfo: {
      useSOLBalance: true,
    },
    remainingAccounts,
    txVersion,
    // 可选：在此处设置优先费用
    // computeBudgetConfig: {
    //   units: 600000,
    //   microLamports: 465915,
    // },
  })

  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 交换（精确输出）

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

```typescript
import {
  ApiV3PoolInfoConcentratedItem,
  ClmmKeys,
  ComputeClmmPoolInfo,
  PoolUtils,
  ReturnTypeFetchMultiplePoolTickArrays,
  USDCMint,
} from '@raydium-io/raydium-sdk-v2'
import BN from 'bn.js'
import Decimal from 'decimal.js'
import { NATIVE_MINT } from '@solana/spl-token'
import { initSdk, txVersion } from '../config'
import { isValidClmm } from './utils'

const swapBaseOut = async () => {
  const raydium = await initSdk()
  let poolInfo: ApiV3PoolInfoConcentratedItem
  const poolId = '2QdhepnKRTLjjSqPL1PtKNwqrUkoLee5Gqs8bvZhRdMv'
  let poolKeys: ClmmKeys | undefined
  let clmmPoolInfo: ComputeClmmPoolInfo
  let tickCache: ReturnTypeFetchMultiplePoolTickArrays

  const outputMint = NATIVE_MINT
  const amountOut = new BN(1000000)

  if (raydium.cluster === 'mainnet') {
    const data = await raydium.api.fetchPoolById({ ids: poolId })
    poolInfo = data[0] as ApiV3PoolInfoConcentratedItem
    if (!isValidClmm(poolInfo.programId)) throw new Error('target pool is not CLMM pool')

    clmmPoolInfo = await PoolUtils.fetchComputeClmmInfo({
      connection: raydium.connection,
      poolInfo,
    })
    tickCache = await PoolUtils.fetchMultiplePoolTickArrays({
      connection: raydium.connection,
      poolKeys: [clmmPoolInfo],
    })
  } else {
    const data = await raydium.clmm.getPoolInfoFromRpc(poolId)
    poolInfo = data.poolInfo
    poolKeys = data.poolKeys
    clmmPoolInfo = data.computePoolInfo
    tickCache = data.tickData
  }

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

  const { remainingAccounts, ...res } = await PoolUtils.computeAmountIn({
    poolInfo: clmmPoolInfo,
    tickArrayCache: tickCache[poolId],
    amountOut,
    baseMint: outputMint,
    slippage: 0.01,
    epochInfo: await raydium.fetchEpochInfo(),
  })

  const { execute } = await raydium.clmm.swapBaseOut({
    poolInfo,
    poolKeys,
    outputMint,
    amountInMax: res.maxAmountIn.amount,
    amountOut: res.realAmountOut.amount,
    observationId: clmmPoolInfo.observationId,
    ownerInfo: {
      useSOLBalance: true,
    },
    remainingAccounts,
    txVersion,
    // 可选：在此处设置优先费用
    // computeBudgetConfig: {
    //   units: 600000,
    //   microLamports: 465915,
    // },
  })

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

swapBaseOut()
```

***

### Swap 参数（base-input）

| 参数                  | 类型                  | 说明                                                   |
| ------------------- | ------------------- | ---------------------------------------------------- |
| `poolInfo`          | object              | 来自 API 或 RPC 的池信息。                                   |
| `poolKeys`          | object              | 池密钥。devnet 需要。                                       |
| `inputMint`         | string \| PublicKey | 输入代币的 Mint 地址。                                       |
| `amountIn`          | BN                  | 输入代币数量，单位为最小单位。                                      |
| `amountOutMin`      | BN                  | 可接受的最小输出代币数量（滑点保护）。                                  |
| `observationId`     | PublicKey           | 来自 `clmmPoolInfo.observationId`.                     |
| `remainingAccounts` | array               | 交换路径所需的 tick array 账户，来自 `computeAmountOutFormat()`. |
| `txVersion`         | TxVersion           | 交易版本。                                                |

### Swap 参数（base-output）

| 参数                  | 类型                  | 说明                                            |
| ------------------- | ------------------- | --------------------------------------------- |
| `poolInfo`          | object              | 来自 API 或 RPC 的池信息。                            |
| `poolKeys`          | object              | 池密钥。devnet 需要。                                |
| `outputMint`        | string \| PublicKey | 期望输出代币的 Mint 地址。                              |
| `amountInMax`       | BN                  | 最多可花费的输入代币数量（滑点保护）。                           |
| `amountOut`         | BN                  | 期望的输出数量，单位为最小单位。                              |
| `observationId`     | PublicKey           | 来自 `clmmPoolInfo.observationId`.              |
| `remainingAccounts` | array               | 交换路径所需的 tick array 账户，来自 `computeAmountIn()`. |
| `txVersion`         | TxVersion           | 交易版本。                                         |

### tick arrays 和 swap 计算

CLMM 交换会遍历 **tick arrays** 以跨越不同价格区间。在提交交换之前，你必须：

1. 通过以下方式获取池的计算信息： `PoolUtils.fetchComputeClmmInfo()`.
2. 通过以下方式获取相关的 tick arrays： `PoolUtils.fetchMultiplePoolTickArrays()`.
3. 通过以下方式计算交换结果： `PoolUtils.computeAmountOutFormat()` （base-input）或 `PoolUtils.computeAmountIn()` （base-output）。

计算步骤会返回 `remainingAccounts` （tick array 账户），这些账户必须传递给 swap 指令。

{% hint style="info" %}
在计算 swap 之前，始终获取最新的 tick array 数据，以获得最新的价格和流动性分布。
{% endhint %}
