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

# CLMM code demos

> End-to-end TypeScript examples: create a CLMM pool, open a position in a chosen price range, adjust liquidity, swap, and collect fees and rewards.

<Info>
  **Version banner.** All TypeScript demos target `@raydium-io/raydium-sdk-v2@0.2.64-alpha`; they were last executed against `0.2.42-alpha` (2026-04) and their call signatures re-checked against the `0.2.64-alpha` source on 2026-09-09, against Solana mainnet-beta. The Rust CPI skeleton at the end targets `raydium-clmm` on `master`, which pins Anchor `=0.32.1` — **not** the `1.0.2` the CPMM page uses; the two cannot live in one crate. Program IDs come from [`reference/program-addresses`](/reference/program-addresses) via the SDK.
</Info>

## Setup

```bash theme={null}
npm install @raydium-io/raydium-sdk-v2 @solana/web3.js @solana/spl-token bn.js decimal.js
```

Every demo on this page mirrors a file in [`raydium-sdk-V2-demo/src/clmm`](https://github.com/raydium-io/raydium-sdk-V2-demo/tree/master/src/clmm); the GitHub link sits next to each section. Bootstrap follows the demo repo's `config.ts.template` ([source](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/config.ts.template)) — `disableFeatureCheck: true` is the recommended setting for any non-trivial integration:

```ts theme={null}
import { Connection, Keypair, clusterApiUrl } from "@solana/web3.js";
import { Raydium, TxVersion } from "@raydium-io/raydium-sdk-v2";
import fs from "node:fs";

const connection = new Connection(process.env.RPC_URL ?? clusterApiUrl("mainnet-beta"));
const owner = Keypair.fromSecretKey(
  new Uint8Array(JSON.parse(fs.readFileSync(process.env.KEYPAIR!, "utf8"))),
);
const raydium = await Raydium.load({
  owner,
  connection,
  cluster: "mainnet",
  disableFeatureCheck: true,
  blockhashCommitment: "finalized",
});
export const txVersion = TxVersion.V0;
```

## Create a CLMM pool

Source: [`src/clmm/createPool.ts`](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/clmm/createPool.ts)

```ts theme={null}
import { PublicKey } from "@solana/web3.js";
import { CLMM_PROGRAM_ID } from "@raydium-io/raydium-sdk-v2";
import BN from "bn.js";
import Decimal from "decimal.js";

const mintA = await raydium.token.getTokenInfo(
  new PublicKey("So11111111111111111111111111111111111111112"));   // wSOL
const mintB = await raydium.token.getTokenInfo(
  new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"));   // USDC

// getClmmConfigs() returns ApiClmmConfigInfo (id is a string). createPool wants
// ClmmConfigInfo, which needs a PublicKey id plus fundOwner/description — convert:
const clmmConfigs = await raydium.api.getClmmConfigs();
const picked = clmmConfigs.find((c) => c.index === 1)!;        // 0.05% tier
const ammConfig = { ...picked, id: new PublicKey(picked.id), fundOwner: "", description: "" };

const initialPrice = new Decimal(160);        // 160 USDC per SOL
const { execute, extInfo } = await raydium.clmm.createPool({
  programId: CLMM_PROGRAM_ID,
  mint1:     mintA,
  mint2:     mintB,
  ammConfig,
  initialPrice,
  txVersion: TxVersion.V0,   // no `startTime` — CreateConcentratedPool has no such field
});

const { txId } = await execute({ sendAndConfirm: true });
console.log("Pool:", extInfo.address.id, "tx:", txId);   // already a base58 string
```

The SDK:

* Sorts `mint1`/`mint2` by byte order before derivation.
* Computes `sqrt_price_x64 = floor(sqrt(initialPrice × 10^(dB−dA)) × 2^64)`.
* Creates the `observation` and `tick_array_bitmap_extension` accounts.
* Pays the pool-creation fee defined by `ammConfig`.

## Open a position in a chosen range

Source: [`src/clmm/createPosition.ts`](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/clmm/createPosition.ts)

```ts theme={null}
import { PoolUtils, TickUtil } from "@raydium-io/raydium-sdk-v2";

const POOL_ID = "<POOL_ID>";   // base58 string
// note: CLMM's getPoolInfoFromRpc returns `rpcPoolInfo`, not `rpcData` (CPMM's does)
const { poolInfo, poolKeys, rpcPoolInfo } = await raydium.clmm.getPoolInfoFromRpc(POOL_ID);

// Choose a price range. Here: ±10% of current.
const currentPrice = new Decimal(poolInfo.price);
const lowerPrice   = currentPrice.mul(0.9);
const upperPrice   = currentPrice.mul(1.1);

// Snap to valid ticks for this pool's tick_spacing.
// The class is TickUtil (singular), and it takes decimals + tickSpacing, not poolInfo.
const { tick: tickLower } = TickUtil.getPriceAndTick({
  price: lowerPrice,
  mintADecimals: poolInfo.mintA.decimals,
  mintBDecimals: poolInfo.mintB.decimals,
  zeroForOne: true,
  tickSpacing: poolInfo.config.tickSpacing,
});
const { tick: tickUpper } = TickUtil.getPriceAndTick({
  price: upperPrice,
  mintADecimals: poolInfo.mintA.decimals,
  mintBDecimals: poolInfo.mintB.decimals,
  zeroForOne: true,
  tickSpacing: poolInfo.config.tickSpacing,
});

// How much of each token to deposit.
const inputAmount = new BN(10_000_000);  // 0.01 SOL

// getLiquidityAmountOutFromAmountIn is async — await it.
const res = await PoolUtils.getLiquidityAmountOutFromAmountIn({
  poolInfo,
  slippage: 0.01,
  inputA: true,
  tickUpper,
  tickLower,
  amount: inputAmount,
  add: true,
  amountHasFee: true,
  epochInfo: await raydium.fetchEpochInfo(),
});

const { execute } = await raydium.clmm.openPositionFromBase({
  poolInfo,
  poolKeys,
  tickUpper,
  tickLower,
  base: "MintA",
  ownerInfo: { useSOLBalance: true },
  baseAmount: inputAmount,
  otherAmountMax: res.amountSlippageB.amount,
  txVersion: TxVersion.V0,
});

const { txId } = await execute({ sendAndConfirm: true });
console.log("Position opened, tx:", txId);
```

The SDK computes which tick arrays the range touches and passes them as accounts. It does not need to bundle any init instruction — there is no init-tick-array instruction; `OpenPosition*` allocates a missing tick array itself, at the payer's expense.

## Increase liquidity on an existing position

Source: [`src/clmm/increaseLiquidity.ts`](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/clmm/increaseLiquidity.ts)

```ts theme={null}
const positionNftMint = new PublicKey("<POSITION_NFT_MINT>");

// There is no getPositionInfo — enumerate the wallet's positions and pick one.
const allPositions = await raydium.clmm.getOwnerPositionInfo({
  programId: poolInfo.programId,
});
const positionAccount = allPositions.find((p) => p.nftMint.equals(positionNftMint));
if (!positionAccount) throw new Error("position not found for this wallet");

const { execute } = await raydium.clmm.increasePositionFromBase({
  poolInfo,
  ownerPosition: positionAccount,
  ownerInfo: { useSOLBalance: true },   // required
  base: "MintA",
  baseAmount: new BN(5_000_000),
  otherAmountMax: new BN(1_000_000_000),
  txVersion: TxVersion.V0,              // no poolKeys member on this call
});

await execute({ sendAndConfirm: true });
```

## Decrease liquidity (and collect fees at the same time)

Source: [`src/clmm/decreaseLiquidity.ts`](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/clmm/decreaseLiquidity.ts) and [`src/clmm/closePosition.ts`](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/clmm/closePosition.ts)

```ts theme={null}
const { execute } = await raydium.clmm.decreaseLiquidity({
  poolInfo,
  poolKeys,
  ownerPosition: positionAccount,
  ownerInfo: {                       // required; closePosition lives in here
    useSOLBalance: true,
    closePosition: false,
  },
  liquidity: positionAccount.liquidity.divn(2),   // halve
  amountMinA: new BN(0),
  amountMinB: new BN(0),
  txVersion: TxVersion.V0,
});

await execute({ sendAndConfirm: true });
```

To **collect fees and rewards only**, call `decreaseLiquidity` with `liquidity = new BN(0)`. The instruction's side-effect is settling `token_fees_owed_{0,1}` and `reward_amount_owed` and transferring them out — this is the only way to collect either.

To close the position entirely after zeroing liquidity and fees, pass `ownerInfo: { closePosition: true }` on the final `decreaseLiquidity` call. The SDK appends `ClosePosition` and burns the NFT.

<Warning>
  **Restricted-issuer positions require a compatible close builder.** These positions have a frozen NFT token account. `ClosePosition` must append the position's pool ID as the first remaining account so CLMM can thaw the account before burning it. The program-source branch does not include an SDK change. Confirm that your SDK release explicitly supports the frozen close path before enabling restricted-issuer position creation.
</Warning>

For a direct Anchor client, keep the declared accounts unchanged and append the pool:

```ts theme={null}
// CLMM program: see reference/program-addresses; source ecb1577;
// Solana mainnet-beta; last verified 2026-08-13.
await program.methods
  .closePosition()
  .accounts({
    nftOwner,
    positionNftMint,
    positionNftAccount,
    personalPosition,
    systemProgram: SystemProgram.programId,
    tokenProgram: positionNftTokenProgram,
  })
  .remainingAccounts([
    { pubkey: poolId, isWritable: false, isSigner: false },
  ])
  .rpc();
```

You may pass `poolId` on every close. CLMM reads it only when `positionNftAccount` is frozen, which keeps one client path compatible with old and new positions.

## Collect reward(s)

Source: [`src/clmm/harvestAllRewards.ts`](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/clmm/harvestAllRewards.ts)

```ts theme={null}
import { CLMM_PROGRAM_ID } from "@raydium-io/raydium-sdk-v2";

const { execute } = await raydium.clmm.harvestAllRewards({
  ownerInfo: { useSOLBalance: true },
  allPoolInfo: { [poolInfo.id]: poolInfo },
  allPositions: { [poolInfo.id]: [positionAccount] },
  programId: CLMM_PROGRAM_ID,
  txVersion: TxVersion.V0,
});

// harvestAllRewards is a MULTI-transaction builder: `sequentially` is required
// and the result is `txIds`, not `txId`.
const { txIds } = await execute({ sequentially: true });
```

`harvestAllRewards` walks every position on every pool passed in, batches the zero-liquidity `DecreaseLiquidity` calls that settle fees and rewards (plus any `UpdateRewardInfos`), and splits them across transactions if needed.

## Swap

Source: [`src/clmm/swap.ts`](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/clmm/swap.ts)

````ts theme={null}
This mirrors `src/clmm/swap.ts`, which simulates with `swapInternal` rather than
`PoolUtils.computeAmountOutFormat`.

```ts
import {
  swapInternal,
  getPdaExBitmapAccount,
  TickArrayBitmapExtensionLayout,
  CLMM_PROGRAM_ID,
} from "@raydium-io/raydium-sdk-v2";

const amountIn   = new BN(10_000_000);
const zeroForOne = true;             // swap A (SOL) → B (USDC)

const poolIdPub = new PublicKey(poolInfo.id);
const { rpcData, tickArrays, configInfo } =
  await raydium.clmm.getSwapPoolInfo(poolInfo.id, zeroForOne);

const exBitmap = getPdaExBitmapAccount(CLMM_PROGRAM_ID, poolIdPub).publicKey;
const exBitmapInfo = await raydium.connection.getAccountInfo(exBitmap);

const simulation = swapInternal({
  programId: CLMM_PROGRAM_ID,
  poolId: poolIdPub,
  poolInfo: rpcData,
  tickArrays,
  configInfo,
  tickarrayBitmapExtension: TickArrayBitmapExtensionLayout.decode(exBitmapInfo!.data),
  amountSpecified: amountIn,
  sqrtPriceLimitX64: new BN(0),
  zeroForOne,
  isBaseInput: true,               // false for exact-output
  blockTimestamp: Math.floor(Date.now() / 1000),
  includeExtraTickArrays: true,
});

const { execute } = await raydium.clmm.swap({
  poolInfo,
  poolKeys,
  inputMint: poolInfo.mintA.address,
  amountIn,
  amountOutMin: simulation.amountCalculated,
  observationId: rpcData.observationId,
  ownerInfo: { useSOLBalance: true },   // required
  remainingAccounts: simulation.accounts,
  txVersion: TxVersion.V0,
});

await execute({ sendAndConfirm: true });
````

The simulation walks the tick map off-chain with the same logic as the on-chain program and returns the amount out (`amountCalculated`) plus the exact account list the swap will touch (`accounts`).

Always pass the `remainingAccounts` the simulation returns: too few and the swap reverts mid-walk with `NotEnoughTickArrayAccount`; stale ones just waste compute.

<Note>
  `PoolUtils.computeAmountOutFormat` still exists, but it needs a `ComputeClmmPoolInfo` (the
  `computePoolInfo` from `getPoolInfoFromRpc`, not an API pool object) plus two more required
  arguments — `tickarrayBitmapExtension` and `blockTimestamp` — and there is no
  `raydium.clmm.fetchTickArrays` method (`fetchTickArrays` is a free function; the module-level
  helpers are `PoolUtils.fetchMultiplePoolTickArrays` and the `tickData` / `tickArrays` returned by
  `getPoolInfoFromRpc`).
</Note>

## Create a customizable CLMM pool

`createCustomizablePool` is the entry point that exposes the dynamic-fee and single-sided-fee toggles at pool-creation time. It takes `createPool`'s shape plus **two** additions:

```ts theme={null}
import { CLMM_PROGRAM_ID, CollectFeeOn } from "@raydium-io/raydium-sdk-v2";

const dynamicFeeConfigs = await raydium.api.getClmmDynamicConfigs();    // GET /main/clmm-dynamic-config
const dynamicFeeConfig  = dynamicFeeConfigs.find((c) => c.index === 0); // pick a calibration tier

const { execute, extInfo } = await raydium.clmm.createCustomizablePool({
  programId: CLMM_PROGRAM_ID,
  mint1:     mintA,
  mint2:     mintB,
  ammConfig,
  initialPrice,
  // The two additions:
  collectFeeOn:      CollectFeeOn.TokenOnlyB,   // FromInput | TokenOnlyA | TokenOnlyB
  dynamicFeeConfig:  new PublicKey(dynamicFeeConfig!.id),
  txVersion: TxVersion.V0,
});

await execute({ sendAndConfirm: true });
console.log("Customizable pool:", extInfo.address.id);   // base58 string
```

<Warning>
  There is no `enableDynamicFee` and no `dynamicFeeConfigId` parameter, and no `startTime`.
  **Supplying `dynamicFeeConfig` is what enables dynamic fees** — omit it and you get a static-fee
  pool, with no error. Note also that the SDK enum members are `TokenOnlyA` / `TokenOnlyB`, whereas
  the on-chain Rust enum spells them `Token0Only` / `Token1Only`; the numeric values match
  (`FromInput` = 0).
</Warning>

`createPool` continues to work for the default-fee, no-dynamic-fee path. Use `createCustomizablePool` whenever you need either knob. See [`products/clmm/instructions`](/products/clmm/instructions) for the on-chain account list.

## Limit orders

A limit order parks user input at a single tick and is filled FIFO when a swap crosses that tick. Outputs are pushed to the owner's ATA at settle time; the owner does not need to be online to be filled.

### Open a limit order

````ts theme={null}
There is no per-pool limit-order config account and no `getClmmLimitOrderConfigs()` helper — orders are keyed by tick.

```ts
import {
  getOrderTick,
  getPdaExBitmapAccount,
  CLMM_PROGRAM_ID,
} from "@raydium-io/raydium-sdk-v2";

const { poolInfo, rpcData } = await raydium.clmm.getSimplePoolInfo(POOL_ID);

// Limit price MUST be quantized to tick_spacing — getOrderTick does that.
const targetPrice = new Decimal(180);                              // sell SOL at 180 USDC
const orderData = getOrderTick({
  baseIn: true,                                                    // selling mintA
  mintADecimal: poolInfo.mintA.decimals,
  mintBDecimal: poolInfo.mintB.decimals,
  tickSpacing: poolInfo.config.tickSpacing,
  price: targetPrice,
});

// A sell order must sit above the current tick; a buy order below it.
if (orderData.tick < rpcData.tickCurrent) throw new Error("sell price must be > current price");

const { execute, extInfo } = await raydium.clmm.openLimitOrder({
  poolInfo,
  baseIn: true,
  orderTick: orderData.tick,
  amount: new BN(50_000_000),                // 0.05 SOL
  tickArrayBitmap: getPdaExBitmapAccount(
    CLMM_PROGRAM_ID,
    new PublicKey(poolInfo.id),
  ).publicKey,
  // noneIndex: 0,                           // optional, defaults to 0
  txVersion: TxVersion.V0,
});

const { txId } = await execute({ sendAndConfirm: true });
console.log("Limit order opened:", extInfo.limitOrder.toBase58(), "tx:", txId);
````

The SDK derives the `LimitOrderState` PDA from `(owner, nonce PDA, order nonce)`, bumps the per-wallet `LimitOrderNonce`, and inserts the order into the FIFO cohort at that tick.

### Increase / decrease an open order

```ts theme={null}
const limitOrder = new PublicKey("<LIMIT_ORDER_PDA>");

await raydium.clmm.increaseLimitOrder({
  poolInfo,
  limitOrder,
  amount: new BN(20_000_000),
  txVersion: TxVersion.V0,
}).then((b) => b.execute({ sendAndConfirm: true }));

await raydium.clmm.decreaseLimitOrder({
  poolInfo,
  limitOrder,
  amount: new BN(10_000_000),
  slippage: 100,                 // optional; DecreaseLimitOrder adds this over Increase
  txVersion: TxVersion.V0,
}).then((b) => b.execute({ sendAndConfirm: true }));
```

`decreaseLimitOrder` can only remove from the **unfilled** portion of the order; the filled portion is locked until settlement. Both instructions revert with `InvalidOrderPhase` if the order has already been fully filled.

### Settle a filled order

```ts theme={null}
// settleLimitOrder takes ONLY the order PDA — no poolInfo, no poolKeys.
await raydium.clmm.settleLimitOrder({
  limitOrder,
  txVersion: TxVersion.V0,
}).then((b) => b.execute({ sendAndConfirm: true }));
```

`settleLimitOrder` reads the order's `unfilled_ratio_x64` against the cohort tracker, computes the filled output, and transfers it to the owner's ATA. The owner can call this themselves; `limit_order_admin` (an off-chain operational keeper) can also call it on the owner's behalf — the output still goes to the owner.

For closing fully-settled orders to recover rent, use `closeLimitOrder` (single) or `closeAllLimitOrder` (batch). For settling many at once, `settleAllLimitOrder` packs as many `SettleLimitOrder` calls as fit into a v0 tx.

### List a wallet's parked orders (off-chain)

```ts theme={null}
// API helper. See api-reference/temp-api-v1.
const active = await fetch(
  `https://temp-api-v1.raydium.io/limit-order/order/list?wallet=<your-wallet-pubkey>`,
).then((r) => r.json());
```

The active-orders endpoint returns both unfilled and partially-filled orders in one payload (`totalAmount` / `filledAmount` / `pendingSettle` distinguish the phases). For closed-order history use `/limit-order/history/order/list-by-user?wallet=…` (per-wallet, paginated by `nextPageId`); for the full event log of a specific order use `/limit-order/history/event/list-by-pda?pda=…`.

## Rust CPI skeleton

```rust theme={null}
// Cargo.toml — CLMM is still on Anchor 0.32, so it cannot share a crate with the
// CPMM sample in products/cpmm/code-demos (which pins =1.0.2).
// anchor-lang  = "=0.32.1"
// anchor-spl   = "=0.32.1"
// raydium-clmm = { git = "https://github.com/raydium-io/raydium-clmm",
//                  branch = "master", features = ["cpi"] }

use anchor_lang::prelude::*;
use raydium_clmm::cpi;
use raydium_clmm::program::RaydiumClmm;
use raydium_clmm::cpi::accounts::SwapSingleV2;   // `SwapV2` is the instruction name,
                                                 // `SwapSingleV2` is the accounts struct

#[derive(Accounts)]
pub struct ProxyClmmSwap<'info> {
    #[account(mut)]
    pub payer: Signer<'info>,
    /// CHECK:
    pub amm_config: UncheckedAccount<'info>,
    #[account(mut)]
    /// CHECK:
    pub pool_state: UncheckedAccount<'info>,
    #[account(mut)]
    /// CHECK:
    pub input_token_account: UncheckedAccount<'info>,
    #[account(mut)]
    /// CHECK:
    pub output_token_account: UncheckedAccount<'info>,
    #[account(mut)]
    /// CHECK:
    pub input_vault: UncheckedAccount<'info>,
    #[account(mut)]
    /// CHECK:
    pub output_vault: UncheckedAccount<'info>,
    #[account(mut)]
    /// CHECK:
    pub observation_state: UncheckedAccount<'info>,
    /// CHECK:
    pub token_program: UncheckedAccount<'info>,
    /// CHECK:
    pub token_program_2022: UncheckedAccount<'info>,
    /// CHECK:
    pub memo_program: UncheckedAccount<'info>,
    /// CHECK:
    pub input_vault_mint: UncheckedAccount<'info>,
    /// CHECK:
    pub output_vault_mint: UncheckedAccount<'info>,
    pub clmm_program: Program<'info, RaydiumClmm>,
    // `remaining_accounts` carries the tick_array and bitmap_extension accounts.
}

pub fn proxy_swap(
    ctx: Context<ProxyClmmSwap>,
    amount: u64,
    other_amount_threshold: u64,
    sqrt_price_limit_x64: u128,
    is_base_input: bool,
) -> Result<()> {
    let cpi_accounts = SwapSingleV2 {
        payer:                 ctx.accounts.payer.to_account_info(),
        amm_config:            ctx.accounts.amm_config.to_account_info(),
        pool_state:            ctx.accounts.pool_state.to_account_info(),
        input_token_account:   ctx.accounts.input_token_account.to_account_info(),
        output_token_account:  ctx.accounts.output_token_account.to_account_info(),
        input_vault:           ctx.accounts.input_vault.to_account_info(),
        output_vault:          ctx.accounts.output_vault.to_account_info(),
        observation_state:     ctx.accounts.observation_state.to_account_info(),
        token_program:         ctx.accounts.token_program.to_account_info(),
        token_program_2022:    ctx.accounts.token_program_2022.to_account_info(),
        memo_program:          ctx.accounts.memo_program.to_account_info(),
        input_vault_mint:      ctx.accounts.input_vault_mint.to_account_info(),
        output_vault_mint:     ctx.accounts.output_vault_mint.to_account_info(),
    };
    // Anchor 0.32 takes the program's AccountInfo here. Do NOT change this to a
    // Pubkey — that is the Anchor 1.0 form, which CLMM does not use.
    let cpi_ctx = CpiContext::new(ctx.accounts.clmm_program.to_account_info(), cpi_accounts)
        .with_remaining_accounts(ctx.remaining_accounts.to_vec());
    cpi::swap_v2(cpi_ctx, amount, other_amount_threshold, sqrt_price_limit_x64, is_base_input)
}
```

Remaining-account order for `SwapV2`:

```
[tick_array_bitmap_extension?, tick_array_0, tick_array_1, …]
```

If the swap never needs the extension, omit it; otherwise it is the first remaining account.

## Common pitfalls

* **Off-spacing tick endpoints** → `TickAndSpacingNotMatch`. Always snap via `TickUtil.getPriceAndTick` (singular `TickUtil`).
* **Not enough tick arrays supplied in `SwapV2`** → `NotEnoughTickArrayAccount`. Take the list from `swapInternal(...).accounts`.
* **Full-range position without the bitmap extension** → the extension PDA must be writable; the SDK handles this automatically.
* **Mistaking `sqrt_price_x64` for `price`** → a factor-of-2 confusion here is particularly painful. When in doubt, let the SDK compute it from a human-readable price.
* **Collecting rewards too eagerly** → each collect is a zero-liquidity `DecreaseLiquidity` and costs one transaction. Batch via `harvestAllRewards` across many positions, and remember its `execute` needs `{ sequentially: true }`.
* **Closing NFT accounts yourself** → `ClosePosition` burns the NFT and closes its ATA. It also closes a Token-2022 NFT mint; a classic SPL Token mint remains at supply zero because that program cannot close mints. Do not close supported accounts separately or the instruction will revert.
* **Opening a limit order at a non-spaced tick** → `TickAndSpacingNotMatch`. Always quantize via the exported `getOrderTick` helper.
* **Calling `decreaseLimitOrder` on a fully-filled order** → `InvalidOrderPhase`. Use `settleLimitOrder` then `closeLimitOrder` instead.
* **Expecting an `enableDynamicFee` flag** → there is none. Omitting `dynamicFeeConfig` simply creates a static-fee pool, silently and with no error. If you wanted dynamic fees, pass the config account's `PublicKey`, picked from `/main/clmm-dynamic-config`.

## Where to go next

* [`sdk-api/typescript-sdk`](/sdk-api/typescript-sdk) — complete SDK surface.
* [`sdk-api/rest-api`](/sdk-api/rest-api) — quote and pool-metadata endpoints.
* [`user-flows/create-clmm-pool`](/user-flows/create-clmm-pool) — non-code walkthrough.
* [`integration-guides/aggregator`](/integration-guides/aggregator) — routing CLMM as part of a path.

Sources:

* [Raydium SDK v2](https://github.com/raydium-io/raydium-sdk-V2)
* [Raydium SDK v2 demos](https://github.com/raydium-io/raydium-sdk-V2-demo)
* [`raydium-io/raydium-clmm`](https://github.com/raydium-io/raydium-clmm)
