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

# 开设和管理 CLMM 头寸

> 选择价格区间、确定存入数量、添加流动性、收取手续费并提取。

export const VersionBanner = ({network = 'mainnet', products = [], lang = 'en'}) => {
  const SDK_VERSION = '1.0.2';
  const ADDRESSES = {
    mainnet: {
      cpmm: {
        Router: 'ze15rnAwTyVCPnDhN7J8An3SvNeWBsZHAVMeBx1ehXeP',
        TokenPairFactory: '24CXRLvj1QiDH6riwr1EPERG7BcduHKZtktygbq97MfYb'
      },
      clmm: {
        PoolFactory: '24gqd4DMQXVzjm5QDAGxUtYupx3WM2PSu3vZUseuBCV9h',
        PositionManager: 'vFyKSqnJHS9MicrojBcLgztyRFqhS4hB349Ci3LJCELw'
      },
      staking: {
        XAlphToken: '225WevmFp5ZgzPsyVJTvyp2v2uyKrvp329HfrVmzffnWj',
        RewardFeeCollector: '22CE7vG6u64zjjsLZLxeJqQuPDydjPvbP9iG1d861j2G7'
      }
    },
    testnet: {
      cpmm: {
        Router: 'xaueT8kPMvpnaFJEfHv6CJLgqsSPpoNmHjF1QgTxoF9R',
        TokenPairFactory: '29hdd9b9Gp7oXuamwHKfUqBaRXP487HoeTcutS3RhZJEj'
      },
      clmm: {
        PoolFactory: 'z2QFT7qBQrfm8UwAYG3VHWoHs7B56ZDFNdSCcDu5j53m',
        PositionManager: '21p6egW8d6FEyVVuH17WqvHCARsCsD61GcBmeCr75XdGT'
      },
      staking: {
        XAlphToken: '23psDicimC5VdpM56wrgqgCzQzcyXWGCkQGSYWbtZAPRq',
        RewardFeeCollector: '29qQ1LPJ5uqawRviXLVDMeo19JUygRrNNTQy5RUBh817D'
      }
    }
  };
  const EXPLORERS = {
    mainnet: 'https://explorer.alephium.org/addresses/',
    testnet: 'https://testnet.alephium.org/addresses/'
  };
  const LABELS = {
    en: {
      sdk: 'SDK',
      network: 'Network',
      contracts: 'Contracts',
      all: 'All addresses',
      allNetworks: 'mainnet · testnet · devnet',
      devnet: 'Local deployment (addresses vary per devnet)'
    },
    zh: {
      sdk: 'SDK 版本',
      network: '网络',
      contracts: '合约',
      all: '全部地址',
      allNetworks: 'mainnet · testnet · devnet',
      devnet: '本地部署（每个 devnet 的地址不同）'
    },
    fr: {
      sdk: 'SDK',
      network: 'Réseau',
      contracts: 'Contrats',
      all: 'Toutes les adresses',
      allNetworks: 'mainnet · testnet · devnet',
      devnet: 'Déploiement local (adresses propres à chaque devnet)'
    }
  };
  const t = LABELS[lang] ?? LABELS.en;
  const contractsPage = lang === 'en' ? '/reference/contracts' : `/${lang}/reference/contracts`;
  const short = a => `${a.slice(0, 6)}…${a.slice(-4)}`;
  const entries = [];
  const addrs = ADDRESSES[network];
  if (addrs) {
    for (const p of products) {
      for (const [name, address] of Object.entries(addrs[p] ?? ({}))) entries.push({
        name,
        address
      });
    }
  }
  let contractsCell;
  if (network === 'devnet' && products.length > 0) {
    contractsCell = <span>{t.devnet}</span>;
  } else if (entries.length > 0) {
    contractsCell = entries.map(({name, address}, i) => <span key={name}>
        {i > 0 && <span className="mx-1 opacity-50">·</span>}
        {name}{' '}
        <a href={EXPLORERS[network] + address} target="_blank" rel="noreferrer" title={address}>
          <code>{short(address)}</code>
        </a>
      </span>);
  }
  const row = (label, value) => <div className="flex flex-wrap gap-x-2">
      <span className="font-semibold min-w-[5.5rem]">{label}</span>
      <span className="flex-1 min-w-0 break-words">{value}</span>
    </div>;
  return <div className="not-prose mb-6 rounded-xl border border-zinc-950/10 dark:border-white/10 bg-zinc-50 dark:bg-white/5 px-4 py-3 text-sm leading-6 text-zinc-700 dark:text-zinc-300">
      {row(t.sdk, <code>@alephium/powfi-sdk@{SDK_VERSION}</code>)}
      {row(t.network, network === 'all' ? t.allNetworks : network)}
      {contractsCell && row(t.contracts, <span>
            {contractsCell}
            <span className="mx-1 opacity-50">·</span>
            <a href={contractsPage}>{t.all} →</a>
          </span>)}
    </div>;
};

<VersionBanner network="mainnet" products={['clmm']} lang="zh" />

<Steps>
  <Step title="加载池和费率档位">
    ```ts theme={null}
    import { Powfi, TickUtils, ClmmLiquidityUtils, sortTokens, UNLIMITED_AMOUNT } from '@alephium/powfi-sdk'

    const powfi = Powfi.load({ networkId: 'mainnet', signer })
    const { address } = await signer.getSelectedAccount()

    const [token0, token1] = sortTokens(tokenA, tokenB)
    const configIndex = 0n
    const config = (await powfi.clmm.getPoolConfig(configIndex))!
    const poolId = powfi.clmm.getPoolId(token0, token1, configIndex)
    const state = await powfi.clmm.getPoolState(poolId)
    const d0 = state.token0Info.decimals, d1 = state.token1Info.decimals
    ```
  </Step>

  <Step title="选择区间">
    把人类可读价格（每个 token0 值多少 token1）转换为按档位间距对齐的 tick：

    ```ts theme={null}
    const tickLower = TickUtils.getAlignedTick(0.04, d0, d1, config.tickSpacing)
    const tickUpper = TickUtils.getAlignedTick(0.06, d0, d1, config.tickSpacing)
    ```

    如需全区间头寸，请使用 `TickUtils.getMinPriceFromTick` 和 `getMaxPriceFromTick`，它们返回按间距对齐的极值 tick。
  </Step>

  <Step title="确定存入数量">
    给定一种代币的数量，按当前价格计算另一种代币的匹配数量：

    ```ts theme={null}
    const [amount0, amount1, liquidity] = ClmmLiquidityUtils.getAmountsAndLiquidityAtSqrtPrice(
      state.sqrtPriceX96,
      TickUtils.getSqrtRatioAtTick(tickLower),
      TickUtils.getSqrtRatioAtTick(tickUpper),
      1_000n * 10n ** BigInt(d0), // user typed 1000 token0
      UNLIMITED_AMOUNT            // token1 side follows
    )
    ```

    如果当前价格在区间之外，头寸是单边的：只需要 token0（价格等于或低于 `tickLower`）或只需要 token1（价格高于区间）。
  </Step>

  <Step title="添加流动性">
    ```ts theme={null}
    const { positionId, result } = await powfi.clmm.addLiquidity({
      token0, token1, configIndex,
      tickLower, tickUpper,
      amount0, amount1,
      slippage: 100n,             // 1% price tolerance
      existingPosition: false     // true when topping up a position you already hold
    })
    ```

    保存返回的 `positionId`：后续读取、领取和撤出头寸都要用到它。之后要向同一区间追加流动性时，传入 `existingPosition: true`，SDK 会附加头寸代币。
  </Step>

  <Step title="监控手续费和价值">
    ```ts theme={null}
    import { ClmmContracts } from '@alephium/powfi-sdk'
    import { addressFromContractId } from '@alephium/web3'

    const pos = await ClmmContracts.Position.at(addressFromContractId(positionId)).fetchState()
    const { liquidity, tokensOwed } = pos.fields
    ```

    `tokensOwed` 保存已经记入头寸的手续费（token0、token1、奖励代币）。池的 `positionInfo` 视图方法（`powfi.clmm.positionInfo`）也会返回当前的代币数量和手续费，见 [CLMM 参考](/zh/modules/clmm)。

    要计算头寸价值，请把 `-liquidity`、当前 `sqrtPriceX96` 和区间边界传给 `ClmmLiquidityUtils.getAmountsForLiquidity`。
  </Step>

  <Step title="收取手续费">
    ```ts theme={null}
    await powfi.clmm.collectTokens({
      token0, token1, configIndex,
      owner: address, recipient: address,
      tickLower, tickUpper,
      amount0Max: UNLIMITED_AMOUNT,
      amount1Max: UNLIMITED_AMOUNT
    })
    ```
  </Step>

  <Step title="提取">
    ```ts theme={null}
    const remove = (liquidity * 50n) / 100n // 50%

    await powfi.clmm.removeLiquidity({
      token0, token1, configIndex,
      owner: address,
      tickLower, tickUpper,
      liquidity: remove,
      base: 'token0',
      baseAmount: 0n,       // min token0
      otherAmountMax: 0n    // min token1 (acts as a minimum)
    })
    ```

    `removeLiquidity` 会在同一笔交易中完成收取：提取出的代币以及累计的手续费和奖励都会发送到你的地址，无需再单独调用 `collectTokens`。
  </Step>
</Steps>

## 创建新池

任何人都可以创建 CLMM 池，合约层面对创建者和代币都没有白名单。创建之前请注意：

* \*\*只能使用已有的费率档位。\*\*池必须建在 Powfi 团队已经设置好的费率档位（`configIndex`）下，不能自定义手续费率或 tick 间距。用 `getAllPoolConfigs()` 查看可用的档位。
* \*\*每个代币对在每个档位上只能有一个池。\*\*池地址由代币对和档位推导得出，如果该池已存在，创建会失败。
* \*\*初始价格由创建者设定。\*\*你传入的 `tick` 就是池的初始价格，第一个头寸也按这个价格铸造。如果它与市场价格相差很大，套利者会立即与你的头寸交易。
* \*\*两个代币都应在代币列表中。\*\*合约接受任何代币，但如果代币不在[代币列表](/zh/modules/token)中，SDK 的 `getPoolState` 等方法会报错。

如果 `poolExists(token0, token1, configIndex)` 为 false，可以一次调用完成建池并注入第一个头寸：

```ts theme={null}
const initialTick = TickUtils.getAlignedTick(0.05, d0, d1, config.tickSpacing)

const { poolId } = await powfi.clmm.createPool(
  configIndex, token0, token1,
  initialTick,
  amount0, amount1,
  tickLower, tickUpper
)
```

<Note>
  如果要自己计算头寸 ID（例如在添加前检查钱包是否已持有某个区间），不要把无分组地址原样传给 `getPositionId`，否则算出的 ID 是错的。请先用 `normalizeAddress(address, powfi.clmm.getClmmConfig().groupIndex)` 规范化，或者使用会自动规范化的 `PoolUtils.getPositionId(poolAddress, owner, ...)`。
</Note>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.