# 存入 / 提取流动性

将流动性添加到现有 CLMM 仓位，以增加你的集中度，或移除流动性以提取代币。

***

### 正在获取池和仓位信息

这两种操作都需要池信息以及你想要修改的仓位。

```typescript
import { ApiV3PoolInfoConcentratedItem, ClmmKeys } from '@raydium-io/raydium-sdk-v2'
import { initSdk, txVersion } from '../config'
import { isValidClmm } from './utils'

const raydium = await initSdk()
const poolId = 'YOUR_POOL_ID'
let poolInfo: ApiV3PoolInfoConcentratedItem
let poolKeys: ClmmKeys | undefined

if (raydium.cluster === 'mainnet') {
  const data = await raydium.api.fetchPoolById({ ids: poolId })
  poolInfo = data[0] as ApiV3PoolInfoConcentratedItem
  if (!isValidClmm(poolInfo.programId)) throw new Error('目标池不是 CLMM 池')
} else {
  const data = await raydium.clmm.getPoolInfoFromRpc(poolId)
  poolInfo = data.poolInfo
  poolKeys = data.poolKeys
}

const allPosition = await raydium.clmm.getOwnerPositionInfo({ programId: poolInfo.programId })
const position = allPosition.find((p) => p.poolId.toBase58() === poolInfo.id)
if (!position) throw new Error(`在池中未找到仓位: ${poolInfo.id}`)
```

***

### 存入流动性（增加）

使用 `raydium.clmm.increasePositionFromLiquidity()` 为现有仓位添加更多流动性。SDK 会根据仓位的 tick 范围和当前池价格计算所需的代币数量。

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

const increaseLiquidity = async () => {
  const raydium = await initSdk()
  const poolId = 'Enfoa5Xdtirwa46xxa5LUVcQWe7EUb2pGzTjfvU7EBS1'
  let poolInfo: ApiV3PoolInfoConcentratedItem
  let poolKeys: ClmmKeys | undefined

  if (raydium.cluster === 'mainnet') {
    const data = await raydium.api.fetchPoolById({ ids: poolId })
    poolInfo = data[0] as ApiV3PoolInfoConcentratedItem
    if (!isValidClmm(poolInfo.programId)) throw new Error('目标池不是 CLMM 池')
  } else {
    const data = await raydium.clmm.getPoolInfoFromRpc(poolId)
    poolInfo = data.poolInfo
    poolKeys = data.poolKeys
  }

  const allPosition = await raydium.clmm.getOwnerPositionInfo({ programId: poolInfo.programId })
  if (!allPosition.length) throw new Error('用户没有任何仓位')

  const position = allPosition.find((p) => p.poolId.toBase58() === poolInfo.id)
  if (!position) throw new Error(`用户在池中没有仓位: ${poolInfo.id}`)

  const inputAmount = 0.0001 // UI 数量
  const slippage = 0.05

  const epochInfo = await raydium.fetchEpochInfo()
  const res = await PoolUtils.getLiquidityAmountOutFromAmountIn({
    poolInfo,
    slippage: 0,
    inputA: true,
    tickUpper: Math.max(position.tickLower, position.tickUpper),
    tickLower: Math.min(position.tickLower, position.tickUpper),
    amount: new BN(new Decimal(inputAmount || '0').mul(10 ** poolInfo.mintA.decimals).toFixed(0)),
    add: true,
    amountHasFee: true,
    epochInfo,
  })

  const { execute } = await raydium.clmm.increasePositionFromLiquidity({
    poolInfo,
    poolKeys,
    ownerPosition: position,
    ownerInfo: {
      useSOLBalance: true,
    },
    liquidity: new BN(new Decimal(res.liquidity.toString()).mul(1 - slippage).toFixed(0)),
    amountMaxA: new BN(new Decimal(inputAmount || '0').mul(10 ** poolInfo.mintA.decimals).toFixed(0)),
    amountMaxB: new BN(
      new Decimal(res.amountSlippageB.amount.toString()).mul(1 + slippage).toFixed(0)
    ),
    checkCreateATAOwner: true,
    txVersion,
    // 可选：在此设置优先费用
    // computeBudgetConfig: {
    //   units: 600000,
    //   microLamports: 46591500,
    // },
  })

  const { txId } = await execute({ sendAndConfirm: true })
  console.log('clmm 仓位流动性已增加:', {
    txId: `https://explorer.solana.com/tx/${txId}`,
  })
}

increaseLiquidity()
```

#### 存入参数

| 参数                    | 类型        | 描述                                          |
| --------------------- | --------- | ------------------------------------------- |
| `poolInfo`            | object    | 来自 API 或 RPC 的池信息。                          |
| `poolKeys`            | object    | 池 keys。devnet 需要。                           |
| `ownerPosition`       | object    | 来自 `getOwnerPositionInfo()`.                |
| `liquidity`           | BN        | 要添加的流动性数量（已按滑点调整）。                          |
| `amountMaxA`          | BN        | 要存入的最大 token A 数量，单位为最小单位。                  |
| `amountMaxB`          | BN        | 要存入的最大 token B 数量，单位为最小单位（已按滑点调整）。          |
| `ownerInfo`           | object    | `{ useSOLBalance: true }` 用于包装时使用原生 SOL 余额。 |
| `checkCreateATAOwner` | boolean   | 如果 `true`，则在创建前验证关联 token 账户的所有权。           |
| `txVersion`           | TxVersion | 交易版本。                                       |

***

### 提取流动性（减少）

使用 `raydium.clmm.decreaseLiquidity()` 从仓位中移除流动性并取回代币。

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

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

  let poolInfo: ApiV3PoolInfoConcentratedItem
  const poolId = '2QdhepnKRTLjjSqPL1PtKNwqrUkoLee5Gqs8bvZhRdMv'
  let poolKeys: ClmmKeys | undefined

  if (raydium.cluster === 'mainnet') {
    const data = await raydium.api.fetchPoolById({ ids: poolId })
    poolInfo = data[0] as ApiV3PoolInfoConcentratedItem
    if (!isValidClmm(poolInfo.programId)) throw new Error('目标池不是 CLMM 池')
  } else {
    const data = await raydium.clmm.getPoolInfoFromRpc(poolId)
    poolInfo = data.poolInfo
    poolKeys = data.poolKeys
  }

  const allPosition = await raydium.clmm.getOwnerPositionInfo({ programId: poolInfo.programId })
  if (!allPosition.length) throw new Error('用户没有任何仓位')

  const position = allPosition.find((p) => p.poolId.toBase58() === poolInfo.id)
  if (!position) throw new Error(`用户在池中没有仓位: ${poolInfo.id}`)

  const { execute } = await raydium.clmm.decreaseLiquidity({
    poolInfo,
    poolKeys,
    ownerPosition: position,
    ownerInfo: {
      useSOLBalance: true,
      closePosition: true, // 提取全部流动性后关闭仓位
    },
    liquidity: position.liquidity,
    amountMinA: new BN(0),
    amountMinB: new BN(0),
    txVersion,
    // 可选：在此设置优先费用
    // computeBudgetConfig: {
    //   units: 600000,
    //   microLamports: 46591500,
    // },
  })

  const { txId } = await execute({ sendAndConfirm: true })
  console.log('从 clmm 仓位提取流动性:', {
    txId: `https://explorer.solana.com/tx/${txId}`,
  })
}

decreaseLiquidity()
```

#### 提取参数

| 参数              | 类型        | 描述                                                                                          |
| --------------- | --------- | ------------------------------------------------------------------------------------------- |
| `poolInfo`      | object    | 来自 API 或 RPC 的池信息。                                                                          |
| `poolKeys`      | object    | 池 keys。devnet 需要。                                                                           |
| `ownerPosition` | object    | 来自 `getOwnerPositionInfo()`.                                                                |
| `liquidity`     | BN        | 要移除的流动性数量。使用 `position.liquidity` 即可全部提取。                                                   |
| `amountMinA`    | BN        | 最少可收到的 token A 数量（滑点保护）。                                                                    |
| `amountMinB`    | BN        | 最少可收到的 token B 数量（滑点保护）。                                                                    |
| `ownerInfo`     | object    | `{ useSOLBalance: true, closePosition: boolean }`. 设置 `closePosition: true` 以在提取全部流动性后关闭仓位。 |
| `txVersion`     | TxVersion | 交易版本。                                                                                       |

{% hint style="info" %}
当 `closePosition` 为 `true`时，仓位 NFT 会被销毁，并回收账户租金。仅在提取全部流动性时设置此项。
{% endhint %}
