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

# Open & manage a CLMM position

> Choose a price range, size the deposit, add liquidity, collect fees, and withdraw.

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

<Steps>
  <Step title="Load the pool and fee tier">
    ```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="Choose a range">
    Convert human prices (token1 per token0) to ticks aligned to the tier's spacing:

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

    For a full-range position, use `TickUtils.getMinPriceFromTick` and `getMaxPriceFromTick`, which return spacing-aligned extreme ticks.
  </Step>

  <Step title="Size the deposit">
    Given the amount of one token, compute the matching amount of the other at the current price:

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

    If the current price is outside the range, the position is single-sided: only token0 (price at or below `tickLower`) or only token1 (price above the range) is needed.
  </Step>

  <Step title="Add liquidity">
    ```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
    })
    ```

    Keep the returned `positionId`: the next steps use it to read, collect from, and withdraw the position. To top up the same range later, pass `existingPosition: true`, which attaches the position token.
  </Step>

  <Step title="Monitor fees and value">
    ```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` holds fees that are already credited to the position (token0, token1, reward). The pool's `positionInfo` view (`powfi.clmm.positionInfo`) also returns current token amounts and fees. See the [CLMM reference](/modules/clmm#positioninfo-params).

    To value the position, pass `-liquidity` to `ClmmLiquidityUtils.getAmountsForLiquidity` with the current `sqrtPriceX96` and the range bounds.
  </Step>

  <Step title="Collect fees">
    ```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="Withdraw">
    ```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` also collects in the same transaction: the withdrawn tokens and any accrued fees and rewards go to your address, so no separate `collectTokens` call is needed.
  </Step>
</Steps>

## Creating a new pool

Anyone can create a CLMM pool. There is no allowlist of creators or tokens on-chain. Before you create one, note:

* **Only existing fee tiers can be used.** Pools are created under a fee tier (`configIndex`) that the Powfi team has already set up. You can't choose your own fee or tick spacing. List the available tiers with `getAllPoolConfigs()`.
* **One pool per token pair and fee tier.** The pool address is derived from the pair and the tier, so creation fails if that pool already exists.
* **The creator sets the initial price.** The `tick` you pass becomes the pool's starting price, and your first position is minted at that price. If it's far from the market price, arbitrageurs will trade against your position right away.
* **Both tokens should be in the token list.** The contract accepts any token, but the SDK's `getPoolState` and related methods throw for tokens that aren't in the [token list](/modules/token).

If `poolExists(token0, token1, configIndex)` is false, create the pool and seed the first position in one call:

```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>
  To compute a position ID yourself (for example, to check whether a wallet already holds a range before adding), don't pass a groupless address as-is: it gives the wrong ID. Normalize it first with `normalizeAddress(address, powfi.clmm.getClmmConfig().groupIndex)`, or use `PoolUtils.getPositionId(poolAddress, owner, ...)`, which normalizes for you.
</Note>


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