Skip to main content
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 the chore/upgrade-anchor branch, Anchor 1.0.2, the same pin as the CPMM page, so the two can live in one crate. master still pins 0.32.1. Program IDs come from reference/program-addresses via the SDK.

Setup

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

Create a CLMM pool

Source: src/clmm/createPool.ts
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
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

Decrease liquidity (and collect fees at the same time)

Source: src/clmm/decreaseLiquidity.ts and src/clmm/closePosition.ts
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.
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.
For a direct Anchor client, keep the declared accounts unchanged and append the pool:
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
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
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.
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).

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:
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).
createPool continues to work for the default-fee, no-dynamic-fee path. Use createCustomizablePool whenever you need either knob. See 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

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

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

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)

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

Remaining-account order for SwapV2:
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

Sources: