> ## 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.

# CPMM

> 恒定乘积（x·y=k）池：池状态、报价、兑换、流动性和建池。

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={['cpmm']} lang="zh" />

```ts theme={null}
powfi.cpmm              // CpmmModule instance (network-bound)
import { CpmmModule } from '@alephium/powfi-sdk' // static quote helpers
```

CPMM 池持有两种代币，按恒定乘积不变量 `reserve0 × reserve1 = k` 定价。每笔兑换向流动性提供者支付 **0.3%** 的手续费。流动性以同质化的 **LP 代币**表示，**LP 代币的 ID 就是池 ID**。

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

  <Card title="指南：提供 CPMM 流动性" icon="droplet" href="/zh/guides/cpmm-liquidity" />
</CardGroup>

## 查询池

### `getPoolId(tokenA, tokenB)`

```ts theme={null}
getPoolId(tokenA: string, tokenB: string): string
```

离线推导代币对的合约 ID（会先对代币排序，所以顺序无关）。它同时也是 **LP 代币 ID**。

### `getPoolAddress(tokenA, tokenB)`

返回代币对的合约地址（`addressFromContractId(getPoolId(...))`）。

### `poolExists(tokenA, tokenB)`

```ts theme={null}
poolExists(tokenA: string, tokenB: string): Promise<boolean>
```

### `getPoolState(tokenA, tokenB)`

```ts theme={null}
getPoolState(tokenA: string, tokenB: string): Promise<CpmmPoolContractState>
```

获取储备量和 LP 总量，并通过代币列表解析两个代币。代币对不存在时抛出 `PoolNotFoundError`。两个代币都必须在代币列表中。

<ResponseField name="CpmmPoolContractState" type="object">
  <Expandable title="字段" defaultOpen>
    <ResponseField name="poolId" type="string">代币对合约 ID（= LP 代币 ID）。</ResponseField>
    <ResponseField name="reserve0" type="bigint">`token0` 的储备量（排序后顺序）。</ResponseField>
    <ResponseField name="reserve1" type="bigint">`token1` 的储备量。</ResponseField>
    <ResponseField name="token0Info" type="TokenInfo">字典序较小的代币 ID 的元数据。</ResponseField>
    <ResponseField name="token1Info" type="TokenInfo">字典序较大的代币 ID 的元数据。</ResponseField>
    <ResponseField name="totalSupply" type="bigint">流通中的 LP 代币总量。</ResponseField>
    <ResponseField name="dexRoot" type="string">代币对中记录的 DEX 根合约 ID。</ResponseField>
  </Expandable>
</ResponseField>

### `getPoolProtocolFees(poolAddress)`

```ts theme={null}
getPoolProtocolFees(poolAddress: string): Promise<bigint>
```

代币对中已累计但尚未收取的协议费用。参数是池**地址**，而不是代币 ID。

## 报价（静态，离线）

所有报价函数都是**静态**纯函数。传入你已经获取的 `CpmmPoolContractState`。

### `CpmmModule.computeSwapAmount(params)`

```ts theme={null}
static computeSwapAmount(params: CpmmSwapQuoteParams): CpmmSwapQuote
```

<ParamField path="state" type="CpmmPoolContractState" required />

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

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

<ParamField path="amountIn" type="bigint">设置此项表示**精确输入**报价。</ParamField>
<ParamField path="amountOut" type="bigint">设置此项表示**精确输出**报价（仅在 `amountIn` 未定义时使用）。</ParamField>
<ParamField path="slippageBps" type="bigint" required>滑点容忍度，单位为基点。</ParamField>

<ResponseField name="CpmmSwapQuote" type="object">
  <Expandable title="字段" defaultOpen>
    <ResponseField name="swapType" type="'ExactIn' | 'ExactOut'" />

    <ResponseField name="tokenInInfo / tokenOutInfo" type="TokenInfo" />

    <ResponseField name="tokenInAmount" type="bigint">输入数量（给定值，或精确输出时的计算值）。</ResponseField>
    <ResponseField name="tokenOutAmount" type="bigint">输出数量（计算值，或精确输出时的给定值）。</ResponseField>
    <ResponseField name="minimalTokenOutAmount" type="bigint | undefined">仅精确输入：扣除滑点后的输出。</ResponseField>
    <ResponseField name="maximalTokenInAmount" type="bigint | undefined">仅精确输出：加上滑点后的输入。</ResponseField>
    <ResponseField name="priceImpact" type="number">百分比，例如 `0.42` = 0.42%。</ResponseField>
    <ResponseField name="state" type="CpmmPoolContractState">计算报价所用的状态。</ResponseField>
  </Expandable>
</ResponseField>

精确输出时，如果 `amountOut >= reserveOut`，抛出 `InsufficientLiquidityError`。

### `CpmmModule.getAmountOut(state, tokenInId, amountIn)` / `getAmountIn(state, tokenOutId, amountOut)`

带 0.3% 手续费的原始 x·y=k 公式：

```ts theme={null}
amountOut = (amountIn * 997 * reserveOut) / (reserveIn * 1000 + amountIn * 997)
amountIn  = (reserveIn * amountOut * 1000) / ((reserveOut - amountOut) * 997) + 1
```

### `CpmmModule.calcPriceImpact(reserve0, reserve1, tokenInId, token0Id, amountIn, amountOut)`

返回以百分比表示的价格影响。

### `CpmmModule.computeLiquidityAmounts(params)`

```ts theme={null}
static computeLiquidityAmounts(params: CpmmAddLiquidityQuoteParams): CpmmAddLiquidityQuote
```

* **已有池**（设置了 `poolState`）：给出一侧的数量（`inputType: 'TokenA'` 使用 `amountA`，`'TokenB'` 使用 `amountB`），函数会根据储备量推导另一侧的数量，以及你将获得的 LP 代币数量。
* **新池**（`poolState` 未定义）：`amountA` 和 `amountB` 都必须提供。铸造的 LP = `sqrt(amountA × amountB) − MINIMUM_LIQUIDITY`（1000 个单位被永久锁定）。如果 `sqrt(amountA × amountB) <= 1000`，抛出 `InsufficientLiquidityError`。

返回 `{ tokenAId, tokenBId, amountA, amountB, shareAmount, sharePercentage, state? }`，其中 `sharePercentage` 是存入后你在池中的份额百分比。

### `CpmmModule.computeRemoveLiquidityAmounts(state, totalLiquidity, liquidityToRemove)`

销毁 `liquidityToRemove` 个 LP 代币后返还的代币数量，以及你剩余的份额。`totalLiquidity` 是**你**的 LP 余额。如果 `liquidityToRemove > totalLiquidity` 则抛出错误。

### `CpmmModule.computeClaimableAmounts(state, liquidityBalance)`

LP 余额对应的全部底层价值：`{ token0, amount0, token1, amount1, ... }`。

### `CpmmModule.minimalAmount(amount, slippage)` / `maximalAmount(amount, slippage)`

滑点辅助函数。见[数量与滑点](/zh/concepts/amounts-and-slippage)。

## 兑换

### `swap(params, balances?)`

```ts theme={null}
swap(params: CpmmSwapRequest, balances?: Map<string, bigint>): Promise<ExecuteScriptResult>
```

获取最新池状态、报价、检查价格影响，然后通过路由合约执行 `SwapMinOut`（精确输入）或 `SwapMaxIn`（精确输出）。需要签名器。

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

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

<ParamField path="amountIn" type="bigint">精确输入数量。</ParamField>
<ParamField path="amountOut" type="bigint">精确输出数量（在 `amountIn` 未定义时使用）。</ParamField>
<ParamField path="slippageBps" type="bigint" required>基点。</ParamField>
<ParamField path="sender" type="string" required>付款并接收的地址，通常是签名器的地址。</ParamField>
<ParamField path="ttlMinutes" type="number" default="60">截止时间。</ParamField>
<ParamField path="fee" type="bigint">可选的[集成方手续费](/zh/guides/integrator-fees)，以输入代币计。</ParamField>
<ParamField path="feeRecipient" type="string">必须与 `fee` 一起提供。</ParamField>

<ParamField path="balances" type="Map<tokenId, bigint>">
  可选的预检查：如果提供了此参数且 `balances.get(tokenIn) < 输入 + 手续费`，会在签名前抛出 `InsufficientBalanceError`。对于 ALPH，请用 `ALPH_TOKEN_ID` 作为键。
</ParamField>

如果 `priceImpact >= 5`，抛出 `PriceImpactTooHighError`。

### `simSwap(params)`

```ts theme={null}
simSwap(params: CpmmSwapRequest): Promise<CpmmSwapQuote>
```

获取池状态并返回 `swap` 会使用的报价，不发送任何交易。如果 `slippageBps` 为空，默认 `100n`。

### `swapTo(params)`

```ts theme={null}
swapTo(params: {
  tokenA: string
  tokenB: string
  targetPrice: number | BigNumber // human price: token1 per token0 (sorted order)
  sender: string
  slippageBps?: bigint            // default 50n
}): Promise<ExecuteScriptResult>
```

计算能把池价格推到 `targetPrice` 的精确输入交易并执行，方向自动推断。\*\*跳过价格影响检查。\*\*如果目标价格等于当前价格则抛出错误。适用于套利，以及把测试池的价格拉回锚定值。

## 流动性

### `addLiquidity(params, balances?)`

```ts theme={null}
addLiquidity(params: CpmmAddLiquidityRequest, balances?: Map<string, bigint>): Promise<ExecuteScriptResult>
```

<ParamField path="poolState" type="CpmmPoolContractState" required>来自 `getPoolState` 的最新状态。</ParamField>
<ParamField path="tokenAId / tokenBId" type="string" required>必须都是池中的代币（顺序任意）。</ParamField>
<ParamField path="amountA / amountB" type="bigint" required>期望数量，都必须大于 0。用 `computeLiquidityAmounts` 得到一对平衡的数量。</ParamField>
<ParamField path="slippageBps" type="bigint" required>作为最小值应用到两个数量上。池为空时忽略。</ParamField>

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

<ParamField path="ttlMinutes" type="number" default="60" />

路由合约按最优比例存入，并向 `sender` 铸造 LP 代币。

### `removeLiquidity(params)`

```ts theme={null}
removeLiquidity(params: CpmmRemoveLiquidityRequest): Promise<ExecuteScriptResult>
```

<ParamField path="poolState" type="CpmmPoolContractState" required />

<ParamField path="liquidity" type="bigint" required>要销毁的 LP 代币数量。</ParamField>
<ParamField path="totalLiquidityAmount" type="bigint">你的 LP 余额（用于计算份额）。默认为 `poolState.totalSupply`。</ParamField>
<ParamField path="slippageBps" type="bigint" required>作为最小值应用到两个输出数量上。</ParamField>

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

<ParamField path="ttlMinutes" type="number" default="60" />

### `computeClaimableAmounts(tokenAId, tokenBId, liquidityBalance)`

实例方法版本：获取池状态，然后返回 LP 余额对应的 `{ token0, amount0, token1, amount1 }`。

## 创建池

### `createPool(params)`

```ts theme={null}
createPool(params: CpmmCreatePoolRequest): Promise<{ poolId: string; result: ExecuteScriptResult }>
```

<ParamField path="tokenAId / tokenBId" type="string" required />

<ParamField path="sender" type="string" required>支付 1 ALPH 的合约押金。</ParamField>

<ParamField path="initialLiquidity" type="{ tokenAAmount: bigint; tokenBAmount: bigint }">
  如果设置，会在一笔交易中创建代币对并存入初始流动性（`CreatePairAndAddLiquidity`），两者的比例决定初始价格。如果省略，只创建代币对（`CreatePair`，会附加每种代币各 1 个最小单位）。
</ParamField>

## 配置

`getConfig()` 返回当前生效的 `CpmmConfig`（`groupIndex`、`factoryId`、`routerId`）。`setConfig(config)` 覆盖它。`getCpmmConfig()` 重新读取打包的部署文件。

## 常量

| 名称 | 值 | 含义 |
| - | - | - |
| `MAX_PRICE_IMPACT` | `5` | 百分比。`swap` 拒绝达到或超过此值的报价。 |
| `MINIMUM_LIQUIDITY` | `1000n` | 首次存入时锁定的 LP 单位。 |
| `BPS` | `10_000n` | 基点分母。 |


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