> ## 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" />

```ts theme={null}
powfi.clmm // ClmmModule
```

CLMM 池采用 Uniswap V3 风格：流动性提供者选择一个价格区间（`tickLower`–`tickUpper`），只有当价格处于区间内时才赚取手续费。每个池由**代币对加费率档位**（`configIndex`）确定。价格存储为 `sqrtPriceX96 = sqrt(token1/token0) × 2^96`。

<CardGroup cols={2}>
  <Card title="指南：在 CLMM 上兑换" icon="arrow-right-arrow-left" href="/zh/guides/clmm-swap" />

  <Card title="指南：开设和管理头寸" icon="chart-area" href="/zh/guides/clmm-position" />
</CardGroup>

## 费率档位（池配置）

费率档位是在工厂合约中注册的 `PoolConfig` 合约，索引从 `0n` 开始。每个池恰好引用一个档位。

```ts theme={null}
interface ClmmPoolConfig {
  configIndex: bigint
  tickSpacing: bigint  // positions' ticks must be multiples of this
  tradingFee: bigint   // pips (MAX_PIPS = 1_000_000n): 3000n = 0.3%
  protocolFee: bigint  // protocol share of trading fees
}
```

| 方法 | 返回 |
| - | - |
| `getAllPoolConfigs()` | `Promise<ClmmPoolConfig[]>`：所有档位（按索引缓存） |
| `getPoolConfig(configIndex)` | `Promise<ClmmPoolConfig \| undefined>`：索引不存在时为 `undefined` |
| `getPoolConfigId(configIndex)` | 该档位 `PoolConfig` 的合约 ID |

## 查询池

| 方法 | 返回 |
| - | - |
| `getPoolId(tokenA, tokenB, configIndex)` | 池合约 ID（内部会对代币排序） |
| `getPoolAddress(tokenA, tokenB, configIndex)` | 池地址 |
| `getPool(tokenA, tokenB, configIndex)` | 带类型的 `PoolInstance`，用于直接调用合约 |
| `poolExists(tokenA, tokenB, configIndex)` | `Promise<boolean>` |
| `findBestRoute(tokenA, tokenB)` | `Promise<bigint>`：活跃流动性最多的池所在的 `configIndex`。一个都没有时抛出 `PoolNotFoundError`。 |

### `getPoolState(poolId)`

```ts theme={null}
getPoolState(poolId: string): Promise<ClmmPoolContractState>
```

<ResponseField name="ClmmPoolContractState" type="object">
  <Expandable title="字段" defaultOpen>
    <ResponseField name="poolId" type="string" />

    <ResponseField name="token0Info / token1Info" type="TokenInfo">排序后代币对的元数据（两者都必须在代币列表中）。</ResponseField>
    <ResponseField name="sqrtPriceX96" type="bigint">当前价格。用 `TickUtils.sqrtPriceX96ToPrice` 换算。</ResponseField>
    <ResponseField name="tick" type="bigint">当前 tick。</ResponseField>
    <ResponseField name="liquidity" type="bigint">当前区间内的活跃流动性。</ResponseField>
    <ResponseField name="configIndex / tickSpacing / tradingFee / protocolFee" type="bigint">费率档位字段。</ResponseField>
  </Expandable>
</ResponseField>

池不存在时抛出 `PoolNotFoundError`。

### 其他池查询

| 方法 | 返回 |
| - | - |
| `getPoolTokenBalances(poolId)` | `{ token0Balance, token1Balance }`：池合约实际持有的代币余额 |
| `getPoolProtocolFees(poolId)` | `{ token0, token1 }`：未收取的协议费用 |
| `getPoolRewardState(poolId)` | `{ token2Info?, rewardInfos: { amount, openTime, endTime }[] }`：流动性挖矿奖励计划。`token2` 是池的额外奖励代币。没有额外奖励代币的池，`token2Info` 为 `undefined`。 |

## 兑换

### `simulateSwap(params)`

```ts theme={null}
simulateSwap(p: ClmmSimulateSwapParams): Promise<ClmmSimulateSwapQuote>
```

在节点上运行池的 `simulateSwap` 视图方法（不发交易），返回兑换经过的流动性曲线。把结果传给 [`PoolUtils.offlineSwap`](/zh/utilities/liquidity-utils) 即可在本地计算输出数量。

<ParamField path="configIndex" type="bigint" required />

<ParamField path="token0 / token1" type="string" required>池中的两个代币（顺序任意，用于定位池）。</ParamField>
<ParamField path="zeroForOne" type="boolean" required>`true` 表示卖出排序后的 `token0` 换取 `token1`（价格下降）。</ParamField>
<ParamField path="amount" type="bigint" required>**正数 = 精确输入**，**负数 = 精确输出**。</ParamField>
<ParamField path="data" type="string">来自 `buildSwapPath` 的多跳路径（可选）。</ParamField>
<ParamField path="interestedContracts" type="string[]">多跳路径涉及的其他池的地址。</ParamField>

<ResponseField name="ClmmSimulateSwapQuote" type="object">
  <Expandable title="字段" defaultOpen>
    <ResponseField name="baseSqrtPriceX96" type="bigint">在与兑换方向相反的一侧找到的某个 tick 边界上的参考 sqrt price，用于锚定离线流动性曲线。</ResponseField>
    <ResponseField name="sqrtPriceX96" type="bigint">模拟兑换开始时池子的 sqrt price。</ResponseField>
    <ResponseField name="liquidity" type="bigint">模拟兑换开始时价格区间内的活跃流动性。</ResponseField>
    <ResponseField name="fee" type="bigint">交易手续费，单位 pips。</ResponseField>
    <ResponseField name="rows" type="{ sqrtPriceX96, liquidity }[]">在 LP 区间边界、tick 搜索分组边界以及模拟最终到达的点上记录的价格和流动性。</ResponseField>
  </Expandable>
</ResponseField>

### `swap(params)`

```ts theme={null}
swap(p: ClmmSwapRequest): Promise<SignExecuteScriptTxResult>
```

通过 `SwapWithoutAccount` 脚本（设置了集成方手续费时使用 `SwapWithoutAccountWithFee`）执行单池兑换。签名器既是付款方也是接收方。

<ParamField path="token0" type="string" required>**输入**代币（不一定是池排序后的 token0）。</ParamField>
<ParamField path="token1" type="string" required>**输出**代币。</ParamField>
<ParamField path="amount" type="bigint" required>指定数量：**正数 = 精确输入**，**负数 = 精确输出**（期望输出取负）。</ParamField>
<ParamField path="amountIn" type="bigint" required>要附加的输入代币数量。精确输入时与 `amount` 相同；精确输出时为你愿意支付的最大输入（先模拟，再加上滑点）。</ParamField>
<ParamField path="slippage" type="bigint" required>基点，会被转换为 `sqrtPriceLimitX96`。</ParamField>
<ParamField path="routePlan" type="bigint[]" required>要经过的费率档位。目前只使用 `routePlan[0]`。</ParamField>
<ParamField path="fee / feeRecipient" type="bigint / string">可选的[集成方手续费](/zh/guides/integrator-fees)，以输入代币计。</ParamField>

<Warning>
  `swap()` 总是发送空的多跳路径，所以只在单个池中兑换。如需多跳，请用 `buildSwapPath` 构建路径，并直接调用 `ClmmScripts` 中的 `SwapWithoutAccount` 脚本。
</Warning>

### `swapTo(params)`

```ts theme={null}
swapTo(p: ClmmSwapToRequest): Promise<SignExecuteScriptTxResult>
// { tokenIn, tokenOut, configIndex, targetSqrtPriceX96, amountInMax }
```

持续兑换直到池价格达到 `targetSqrtPriceX96`，最多花费 `amountInMax` 的 `tokenIn`。如果目标价格在当前价格的错误一侧，链上会失败（错误码 106，`InvalidSqrtPriceLimit`）。

### `buildSwapPath(tokenId, configIndex)`

为 `simulateSwap({ data })` 编码额外的一跳：`tokenId + configIndex`（4 个十六进制字符）。

## 流动性头寸

头寸由 **池 + 所有者 + tickLower + tickUpper** 确定。头寸合约 ID 同时也是**头寸代币 ID**：所有者持有 1 个单位，修改头寸时需要附加它。

### `getPositionId(poolId, owner, tickLower, tickUpper)`

离线推导头寸 ID。对于无分组地址，请传入规范化后的 `owner`（见[分组与地址](/zh/concepts/networks)）。`PoolUtils.getPositionId(poolAddress, owner, ...)` 会自动规范化。

### `createPool(...)`

```ts theme={null}
createPool(
  configIndex: bigint,
  token0: string, token1: string,
  tick: bigint,                    // initial price as a tick
  amount0: bigint, amount1: bigint,
  tickLower: bigint, tickUpper: bigint,
  dustAmount?: bigint
): Promise<{ poolId: string; result: ExecuteScriptResult }>
```

以 `tick` 对应的价格创建池，并在 `[tickLower, tickUpper]` 内铸造第一个头寸。代币及其数量、tick 都会在内部排序。会为新合约附加 `6 × MINIMAL_CONTRACT_DEPOSIT` 的 ALPH。

### `addLiquidity(params)`

```ts theme={null}
addLiquidity(p: ClmmAddLiquidityRequest): Promise<{ positionId: string; result: SignExecuteScriptTxResult }>
```

<ParamField path="token0 / token1" type="string" required>池中的代币。请**排序后**传入（`sortTokens`），并让 `amount0` / `amount1` 与该顺序对应。</ParamField>

<ParamField path="configIndex" type="bigint" required />

<ParamField path="tickLower / tickUpper" type="bigint" required>必须是该档位 `tickSpacing` 的整数倍。用 `TickUtils.getAlignedTick` 对齐。</ParamField>
<ParamField path="amount0 / amount1" type="bigint" required>期望数量。合约会在不超过这些数量的前提下存入最大的平衡数量。</ParamField>
<ParamField path="slippage" type="bigint" required>作用于价格的基点。最小数量由价格边界推导。</ParamField>
<ParamField path="owner" type="string">头寸所有者，默认为签名器。</ParamField>
<ParamField path="existingPosition" type="boolean">向你已持有的头寸追加流动性时设为 `true`（会附加头寸代币）。签名者必须持有该头寸的 NFT，SDK 会自动附上。</ParamField>

头寸的押金（`dustAmount`）通过 `PositionManager.getSqrtPricesX96` 从链上读取。

**两步版本：** `getAddLiquidityParams(p)` 返回 `[positionId, positionManager, params]` 而不发送，`addLiquidityFromParams(positionId, positionManager, params)` 再执行。可以用它展示带有精确最小数量的确认界面。

### `removeLiquidity(params)`

```ts theme={null}
removeLiquidity(p: ClmmRemoveLiquidityRequest): Promise<{ positionId: string; result: SignExecuteScriptTxResult }>
```

减少头寸的流动性，并在同一笔交易中收取全部累计的手续费和奖励。代币发送给签名器。

<ParamField path="token0 / token1 / configIndex" required>确定池。</ParamField>
<ParamField path="owner / tickLower / tickUpper" required>确定头寸。</ParamField>
<ParamField path="liquidity" type="bigint" required>要移除的流动性单位（先读取头寸当前的 `liquidity`，再取一部分）。</ParamField>
<ParamField path="base" type="'token0' | 'token1'" required>`baseAmount` 指的是哪一侧。</ParamField>
<ParamField path="baseAmount" type="bigint" required>至少要收到的 `base` 代币数量。</ParamField>
<ParamField path="otherAmountMax" type="bigint" required>至少要收到的另一种代币数量。虽然名字里有 Max，但它在链上是作为**最小值**传递的。</ParamField>

两个最小值都传 `0n` 即可关闭检查。

### `collectTokens(params)`

```ts theme={null}
collectTokens(p: ClmmCollectTokensRequest): Promise<{ positionId: string; result: SignExecuteScriptTxResult }>
```

把累计的手续费和奖励收取到 `recipient`。它不会移除流动性；如需移除，请使用 [`removeLiquidity`](#removeliquidity-params)。

<ParamField path="token0 / token1 / configIndex / owner / tickLower / tickUpper" required>确定头寸。</ParamField>

<ParamField path="recipient" type="string" required />

<ParamField path="amount0Max / amount1Max" type="bigint" required>收取上限。要全部收取，请使用 `U128_MAX` 量级的值（例如 `UNLIMITED_AMOUNT`）。</ParamField>

### `positionInfo(params)`

```ts theme={null}
positionInfo(p: ClmmPositionInfoRequest): Promise<ClmmPositionInfo>
// => { amount0, amount1, fees: [bigint, bigint, bigint], avgValue, avgFees, avgTime }
```

调用池的 `positionInfo` 视图方法，原样返回结果。`fees` 包含三项：token0、token1 和池的奖励代币。SDK 会把累加器参数（`acc`、`iacc0`、`iacc1`、`t0`、`acct0`）直接传给合约，其含义请参考 `Pool` 合约源码。

要读取头寸的原始流动性，请获取头寸合约的状态：

```ts theme={null}
import { ClmmContracts } from '@alephium/powfi-sdk'
import { addressFromContractId } from '@alephium/web3'

const pos = await ClmmContracts.Position.at(addressFromContractId(positionId)).fetchState()
pos.fields.liquidity // bigint
pos.fields.tokensOwed // [token0, token1, reward]
```

## 流动性挖矿奖励

每个池最多可以运行三个奖励计划（`MAX_REWARDS = 3`），以 token0、token1 或池的额外奖励代币（`token2`）发放。奖励计划由 Powfi 团队设置和注资。

* `getPoolRewardState(poolId)` 返回奖励代币，以及每个计划的 `amount`、`openTime` 和 `endTime`（毫秒时间戳）。
* 头寸累计的奖励体现在 `positionInfo(...).fees` 中：token0 奖励在 `fees[0]`，token1 奖励在 `fees[1]`，其他代币的奖励在 `fees[2]`。通过 `collectTokens` 领取。

## 推荐（DEX）账户

Powfi 通过 `DexAccountRoot` 下每个用户的 `DexAccount` 合约跟踪推荐关系。

| 方法 | 说明 |
| - | - |
| `createDexAccount(referrer)` | 为签名器创建账户并绑定 `referrer`（花费 `MINIMAL_CONTRACT_DEPOSIT`）。 |
| `getDexAccountId(owner)` | 推导某个所有者的账户合约 ID。 |
| `getDexAccountState(owner)` | 账户的 `{ address, id, state }`。 |
| `getDexAccountRoot()` | 带类型的 `DexAccountRootInstance`。 |

## 配置

```ts theme={null}
interface ClmmConfig {
  groupIndex: number
  factoryId: string
  positionManagerId: string
  defaultConfigIndex: bigint // 0n
  accountRoot: string
}
```

用 `getClmmConfig()` 读取当前生效的配置，用 `setConfig()` 覆盖。`getConfig()` 会重新读取打包的部署文件。

## 常量

| 名称 | 值 |
| - | - |
| `MIN_TICK` / `MAX_TICK` | `-887272n` / `887272n` |
| `MAX_PIPS` | `1_000_000n` |
| `U256_MAX` | `2n ** 256n - 1n` |
| `UNLIMITED_AMOUNT` | `2n ** 128n - 1n` |


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