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

> Concentrated-liquidity pools: fee tiers, pool state, swap simulation, swaps, positions, and farming rewards.

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="en" />

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

CLMM pools are Uniswap V3-style: liquidity providers choose a price range (`tickLower`–`tickUpper`) and earn fees only while the price is inside it. Each pool is identified by a **token pair plus a fee tier** (`configIndex`). Prices are stored as `sqrtPriceX96 = sqrt(token1/token0) × 2^96`.

<CardGroup cols={2}>
  <Card title="Guide: swap on CLMM" icon="arrow-right-arrow-left" href="/guides/clmm-swap" />

  <Card title="Guide: open & manage a position" icon="chart-area" href="/guides/clmm-position" />
</CardGroup>

## Fee tiers (pool configs)

A fee tier is a `PoolConfig` contract registered in the factory, indexed from `0n`. Every pool references exactly one.

```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
}
```

| Method | Returns |
| - | - |
| `getAllPoolConfigs()` | `Promise<ClmmPoolConfig[]>`: every tier (cached per index) |
| `getPoolConfig(configIndex)` | `Promise<ClmmPoolConfig \| undefined>`: `undefined` if the index doesn't exist |
| `getPoolConfigId(configIndex)` | Contract ID of the tier's `PoolConfig` |

## Pool lookups

| Method | Returns |
| - | - |
| `getPoolId(tokenA, tokenB, configIndex)` | Pool contract ID (tokens sorted internally) |
| `getPoolAddress(tokenA, tokenB, configIndex)` | Pool address |
| `getPool(tokenA, tokenB, configIndex)` | Typed `PoolInstance` for direct contract calls |
| `poolExists(tokenA, tokenB, configIndex)` | `Promise<boolean>` |
| `findBestRoute(tokenA, tokenB)` | `Promise<bigint>`: the `configIndex` whose pool has the most active liquidity. Throws `PoolNotFoundError` if none exist. |

### `getPoolState(poolId)`

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

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

    <ResponseField name="token0Info / token1Info" type="TokenInfo">Sorted pair metadata (both must be in the token list).</ResponseField>
    <ResponseField name="sqrtPriceX96" type="bigint">Current price. Convert with `TickUtils.sqrtPriceX96ToPrice`.</ResponseField>
    <ResponseField name="tick" type="bigint">Current tick.</ResponseField>
    <ResponseField name="liquidity" type="bigint">Active in-range liquidity.</ResponseField>
    <ResponseField name="configIndex / tickSpacing / tradingFee / protocolFee" type="bigint">Fee-tier fields.</ResponseField>
  </Expandable>
</ResponseField>

Throws `PoolNotFoundError` if the pool doesn't exist.

### Other pool reads

| Method | Returns |
| - | - |
| `getPoolTokenBalances(poolId)` | `{ token0Balance, token1Balance }`: actual token balances held by the pool contract |
| `getPoolProtocolFees(poolId)` | `{ token0, token1 }`: uncollected protocol fees |
| `getPoolRewardState(poolId)` | `{ token2Info?, rewardInfos: { amount, openTime, endTime }[] }`: farming reward programs. `token2` is the pool's extra reward token. `token2Info` is `undefined` for pools without one. |

## Swaps

### `simulateSwap(params)`

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

Runs the pool's `simulateSwap` view method on the node (no transaction) and returns the liquidity curve the swap walks through. Feed the result to [`PoolUtils.offlineSwap`](/utilities/liquidity-utils#offlineswap) to compute the output amount locally.

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

<ParamField path="token0 / token1" type="string" required>The pool's tokens (any order, used to locate the pool).</ParamField>
<ParamField path="zeroForOne" type="boolean" required>`true` sells sorted `token0` for `token1` (price goes down).</ParamField>
<ParamField path="amount" type="bigint" required>**Positive = exact-in**, **negative = exact-out**.</ParamField>
<ParamField path="data" type="string">Multi-hop path from `buildSwapPath` (optional).</ParamField>
<ParamField path="interestedContracts" type="string[]">Addresses of extra pools touched by a multi-hop path.</ParamField>

<ResponseField name="ClmmSimulateSwapQuote" type="object">
  <Expandable title="fields" defaultOpen>
    <ResponseField name="baseSqrtPriceX96" type="bigint">Reference sqrt price at a tick boundary found opposite the swap direction, used to anchor the offline liquidity curve.</ResponseField>
    <ResponseField name="sqrtPriceX96" type="bigint">Pool sqrt price at the start of the simulated swap.</ResponseField>
    <ResponseField name="liquidity" type="bigint">Active in-range liquidity at the start of the simulated swap.</ResponseField>
    <ResponseField name="fee" type="bigint">Trading fee in pips.</ResponseField>
    <ResponseField name="rows" type="{ sqrtPriceX96, liquidity }[]">Price and liquidity recorded at LP range boundaries, tick-search group edges, and the final point reached by the simulation.</ResponseField>
  </Expandable>
</ResponseField>

### `swap(params)`

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

Executes a single-pool swap through the `SwapWithoutAccount` script (or `SwapWithoutAccountWithFee` when an integrator fee is set). The signer is both payer and recipient.

<ParamField path="token0" type="string" required>The **input** token (not necessarily the pool's sorted token0).</ParamField>
<ParamField path="token1" type="string" required>The **output** token.</ParamField>
<ParamField path="amount" type="bigint" required>Amount specified: **positive = exact-in**, **negative = exact-out** (the negated desired output).</ParamField>
<ParamField path="amountIn" type="bigint" required>How much input token to attach. Exact-in: same as `amount`. Exact-out: your maximum input (simulate, then add slippage).</ParamField>
<ParamField path="slippage" type="bigint" required>Basis points, converted to a `sqrtPriceLimitX96`.</ParamField>
<ParamField path="routePlan" type="bigint[]" required>Fee tiers to route through. Only `routePlan[0]` is used today.</ParamField>
<ParamField path="fee / feeRecipient" type="bigint / string">Optional [integrator fee](/guides/integrator-fees), in the input token.</ParamField>

<Warning>
  `swap()` always sends an empty multi-hop path, so it swaps in one pool only. For multi-hop, build the path with `buildSwapPath` and call the `SwapWithoutAccount` script from `ClmmScripts` directly.
</Warning>

### `swapTo(params)`

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

Swaps until the pool reaches `targetSqrtPriceX96`, spending at most `amountInMax` of `tokenIn`. Fails on-chain (error code 106, `InvalidSqrtPriceLimit`) if the target is on the wrong side of the current price.

### `buildSwapPath(tokenId, configIndex)`

Encodes one extra hop for `simulateSwap({ data })`: `tokenId + configIndex` as 4 hex characters.

## Liquidity positions

A position is identified by **pool + owner + tickLower + tickUpper**. The position contract ID doubles as a **position token ID**. The owner holds one unit of it, and it is attached when modifying the position.

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

Derives the position ID offline. Pass `owner` in normalized form for groupless addresses (see [Groups and addresses](/concepts/networks#groups-and-addresses)). `PoolUtils.getPositionId(poolAddress, owner, ...)` normalizes for you.

### `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 }>
```

Creates a pool at price `tick` and mints the first position in `[tickLower, tickUpper]`. Tokens, their amounts, and the ticks are sorted internally. Attaches `6 × MINIMAL_CONTRACT_DEPOSIT` ALPH for the new contracts.

### `addLiquidity(params)`

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

<ParamField path="token0 / token1" type="string" required>Pool tokens. Pass them **sorted** (`sortTokens`) and match `amount0` / `amount1` to that order.</ParamField>

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

<ParamField path="tickLower / tickUpper" type="bigint" required>Multiples of the tier's `tickSpacing`. Use `TickUtils.getAlignedTick`.</ParamField>
<ParamField path="amount0 / amount1" type="bigint" required>Desired amounts. The contract deposits the maximal balanced amounts up to these.</ParamField>
<ParamField path="slippage" type="bigint" required>Basis points on price. Minimum amounts are derived from the price bounds.</ParamField>
<ParamField path="owner" type="string">Position owner. Defaults to the signer.</ParamField>
<ParamField path="existingPosition" type="boolean">Set `true` when adding to a position you already own (attaches the position token). The signer must hold its position NFT, which the SDK attaches automatically.</ParamField>

The position's deposit (`dustAmount`) is read on-chain via `PositionManager.getSqrtPricesX96`.

**Two-step variant:** `getAddLiquidityParams(p)` returns `[positionId, positionManager, params]` without sending, and `addLiquidityFromParams(positionId, positionManager, params)` executes them. Use this to show a confirmation screen with the exact minimum amounts.

### `removeLiquidity(params)`

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

Decreases the position's liquidity and, in the same transaction, collects all accrued fees and rewards. Tokens go to the signer.

<ParamField path="token0 / token1 / configIndex" required>Identify the pool.</ParamField>
<ParamField path="owner / tickLower / tickUpper" required>Identify the position.</ParamField>
<ParamField path="liquidity" type="bigint" required>Liquidity units to remove (read the position's current `liquidity`, then take a fraction).</ParamField>
<ParamField path="base" type="'token0' | 'token1'" required>Which side `baseAmount` refers to.</ParamField>
<ParamField path="baseAmount" type="bigint" required>Minimum amount of the `base` token to receive.</ParamField>
<ParamField path="otherAmountMax" type="bigint" required>Minimum amount of the other token to receive. Despite the name, it's passed on-chain as a **minimum**.</ParamField>

Pass `0n` for both minimums to disable the check.

### `collectTokens(params)`

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

Collects accrued fees and rewards to `recipient`. It does not remove liquidity; use [`removeLiquidity`](#removeliquidity-params) for that.

<ParamField path="token0 / token1 / configIndex / owner / tickLower / tickUpper" required>Identify the position.</ParamField>

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

<ParamField path="amount0Max / amount1Max" type="bigint" required>Caps on what to collect. Use `U128_MAX`-sized values (for example `UNLIMITED_AMOUNT`) to collect everything.</ParamField>

### `positionInfo(params)`

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

Calls the pool's `positionInfo` view and returns its result unchanged. `fees` holds three entries: token0, token1, and the pool's reward token. The SDK passes the accumulator arguments (`acc`, `iacc0`, `iacc1`, `t0`, `acct0`) straight to the contract. See the `Pool` contract source for what they mean.

To read a position's raw liquidity, fetch the position contract:

```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]
```

## Farming rewards

A pool can run up to three reward programs (`MAX_REWARDS = 3`), paying in token0, token1, or the pool's extra reward token (`token2`). Programs are set up and funded by the Powfi team.

* `getPoolRewardState(poolId)` returns the reward token and each program's `amount`, `openTime`, and `endTime` (millisecond timestamps).
* Rewards accrued by a position appear in `positionInfo(...).fees`: token0 rewards in `fees[0]`, token1 rewards in `fees[1]`, and rewards in any other token in `fees[2]`. They are paid out through `collectTokens`.

## Referral (DEX) accounts

Powfi tracks referrals through per-user `DexAccount` contracts under a `DexAccountRoot`.

| Method | Description |
| - | - |
| `createDexAccount(referrer)` | Creates the signer's account with `referrer` (costs `MINIMAL_CONTRACT_DEPOSIT`). |
| `getDexAccountId(owner)` | Derives an owner's account contract ID. |
| `getDexAccountState(owner)` | `{ address, id, state }` of the account. |
| `getDexAccountRoot()` | Typed `DexAccountRootInstance`. |

## Configuration

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

Read the active config with `getClmmConfig()` and override it with `setConfig()`. `getConfig()` re-reads the bundled deployment.

## Constants

| Name | Value |
| - | - |
| `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.