# 锁定流动性 / 收取费用

使用 Burn & Earn 程序永久锁定 LP 代币，并通过 Fee Key NFT 收取您应得的交易手续费份额。

***

### 工作原理

当您锁定 LP 代币时：

1. 您的 LP 代币会被转移到 Burn & Earn locker program——它们无法被提取。
2. 您会收到一个 **Fee Key NFT** ，它代表您的锁定仓位。
3. Fee Key NFT 赋予您领取流动性池中已累积交易手续费的权利。

#### 底层的手续费提取机制

在 constant-product 流动性池中，手续费不会被单独存储——它们会累积到流动性池 vaults 中，使每个 LP 代币随着时间推移对应更多的底层代币。提取这部分累计价值的唯一方式是赎回（burn）LP 代币。

当您从锁定仓位中提取收益时：

1. 例如，您的锁定仓位持有 **1000 LP** 代币。
2. 发生代币交换后，手续费流入流动性池 vaults，每个 LP 代币的价值都会提升。
3. 您调用 `harvestLockLp` 并传入一个 `lpFeeAmount` （例如 **5 LP**).
4. locker program 会通过在 CPMM program 上执行 withdraw 来 burn 这 5 LP。
5. 您将按比例收到 token A + token B，并发送到您的钱包。
6. 您的锁定仓位现在持有 **995 LP** 代币。

LP 余额 **确实会减少** 每次 harvest 后都会减少。不过，剩余的 995 LP 代币的价值大致相当于手续费累积之前原先 1000 LP 的价值，因为现在每个 LP 代币都更值钱了。您提取的是增值部分，而不是本金。

\`lpFeeAmount\` `lpFeeAmount` 参数控制要 burn 多少 LP。&#x20;

***

### 锁定 LP 代币

使用 `raydium.cpmm.lockLp()` 永久锁定 LP 代币并接收一个 Fee Key NFT。

```typescript
import {
  ApiV3PoolInfoStandardItemCpmm,
  CpmmKeys,
  DEVNET_PROGRAM_ID,
  TxVersion,
} from '@raydium-io/raydium-sdk-v2'
import { initSdk } from '../config'

const lockLiquidity = async () => {
  const raydium = await initSdk()
  const poolId = 'YOUR_POOL_ID'

  let poolInfo: ApiV3PoolInfoStandardItemCpmm
  let poolKeys: CpmmKeys | undefined

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

  // 获取钱包代币账户以取得 LP 余额
  await raydium.account.fetchWalletTokenAccounts()
  const lpBalance = raydium.account.tokenAccounts.find(
    (a) => a.mint.toBase58() === poolInfo.lpMint.address
  )
  if (!lpBalance) throw new Error(`no LP balance for pool: ${poolId}`)

  const { execute, extInfo } = await raydium.cpmm.lockLp({
    // devnet: programId: DEVNET_PROGRAM_ID.LOCK_CPMM_PROGRAM
    // devnet: authProgram: DEVNET_PROGRAM_ID.LOCK_CPMM_AUTH
    // devnet: poolKeys
    poolInfo,
    lpAmount: lpBalance.amount,
    withMetadata: true,
    txVersion: TxVersion.V0,
  })

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

lockLiquidity()
```

#### 锁定参数

| 参数             | 类型        | 说明                                                                            |
| -------------- | --------- | ----------------------------------------------------------------------------- |
| `poolInfo`     | 对象        | 来自 API 或 RPC 的流动性池信息。                                                         |
| `poolKeys`     | 对象        | 流动性池 keys。devnet 必填。                                                          |
| `lpAmount`     | BN        | 要锁定的 LP 代币数量，以最小单位表示。                                                         |
| `withMetadata` | 布尔值       | 如果 `true`，则为 Fee Key NFT 创建链上 metadata。                                       |
| `programId`    | PublicKey | Locker program。Mainnet 默认会自动解析。Devnet： `DEVNET_PROGRAM_ID.LOCK_CPMM_PROGRAM`. |
| `authProgram`  | PublicKey | Locker auth program。Devnet： `DEVNET_PROGRAM_ID.LOCK_CPMM_AUTH`.               |
| `txVersion`    | TxVersion | 交易版本。                                                                         |

{% hint style="warning" %}
锁定是永久性的。一旦锁定，LP 代币将无法提取。只有交易手续费可以通过 Fee Key NFT 收取。
{% endhint %}

***

### 从锁定流动性中收取手续费

使用 `raydium.cpmm.harvestLockLp()` 使用 Fee Key NFT 的 mint 地址，从锁定的 LP 仓位中领取已累积的交易手续费。

```typescript
import { PublicKey } from '@solana/web3.js'
import BN from 'bn.js'

const harvestLockLiquidity = async () => {
  const raydium = await initSdk()
  const poolId = 'YOUR_POOL_ID'

  let poolInfo: ApiV3PoolInfoStandardItemCpmm
  let poolKeys: CpmmKeys | undefined

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

  const { execute } = await raydium.cpmm.harvestLockLp({
    // devnet: programId: DEVNET_PROGRAM_ID.LOCK_CPMM_PROGRAM
    // devnet: authProgram: DEVNET_PROGRAM_ID.LOCK_CPMM_AUTH
    // devnet: poolKeys
    poolInfo,
    nftMint: new PublicKey('YOUR_FEE_KEY_NFT_MINT'),
    lpFeeAmount: new BN(99999999),
    txVersion: TxVersion.V0,
    // closeWsol: false, // 默认 true —— 设为 false 以保留 wSOL 账户
  })

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

harvestLockLiquidity()
```

#### Harvest 参数

| 参数            | 类型        | 说明                                                        |
| ------------- | --------- | --------------------------------------------------------- |
| `poolInfo`    | 对象        | 来自 API 或 RPC 的流动性池信息。                                     |
| `poolKeys`    | 对象        | 流动性池 keys。devnet 必填。                                      |
| `nftMint`     | PublicKey | 锁定时收到的 Fee Key NFT 的 mint 地址。                             |
| `lpFeeAmount` | BN        | 从锁定仓位中要 burn 的 LP 代币数量。这是您提取累计手续费价值的方式——参见上方“手续费提取的工作原理”。 |
| `closeWsol`   | 布尔值       | 默认 `true`。关闭 wSOL 账户并返回原生 SOL。设为 `false` 以保留 wSOL。        |
| `txVersion`   | TxVersion | 交易版本。                                                     |

***

### 收取 creator fees

creator fees 是每次代币交换时支付给流动性池创建者的一项独立手续费。它们 **仅适用于通过 permissioned path 创建的流动性池** (`createPoolWithPermission`），这需要来自 Raydium admin 的 Permission PDA。使用标准 `createPool()` 创建的流动性池在 program 级别禁用了 creator fees——详情请参见 Creating a pool。

如果您的流动性池是通过 permission 创建的，并且 fee config 中包含非零的 `creator_fee_rate`，则您可以收取已累积的 creator fees。

#### 单个流动性池

```typescript
const collectCreatorFee = async () => {
  const raydium = await initSdk()
  // ... 获取 poolInfo 和 poolKeys

  const { execute } = await raydium.cpmm.collectCreatorFees({
    poolInfo,
    poolKeys,
    txVersion: TxVersion.V0,
  })

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

#### 同时处理多个流动性池

```typescript
import {
  CREATE_CPMM_POOL_PROGRAM,
  DEVNET_PROGRAM_ID,
} from '@raydium-io/raydium-sdk-v2'

const collectAllCreatorFees = async () => {
  const raydium = await initSdk()
  const isDevnet = raydium.cluster === 'devnet'

  const host = isDevnet
    ? 'https://temp-api-v1-devnet.raydium.io'
    : 'https://temp-api-v1.raydium.io'

  // 获取已连接钱包中所有存在待领取 creator fees 的流动性池
  const res = await fetch(
    `${host}/cp-creator-fee?wallet=${raydium.ownerPubKey.toBase58()}`
  )
  const { data } = await res.json()

  const poolsWithFees = data.filter(
    (d: any) => d.fee.amountA !== '0' || d.fee.amountB !== '0'
  )

  if (!poolsWithFees.length) {
    console.log('no pending creator fees')
    return
  }

  const { execute } = await raydium.cpmm.collectMultiCreatorFees({
    poolInfoList: poolsWithFees.map((d: any) => d.poolInfo),
    programId: isDevnet
      ? DEVNET_PROGRAM_ID.CREATE_CPMM_POOL_PROGRAM
      : CREATE_CPMM_POOL_PROGRAM,
    txVersion: TxVersion.V0,
  })

  const sentInfo = await execute({ sequentially: true })
  console.log('creator fees collected', sentInfo)
}
```
