Skip to main content
What this does. Creates a new CLMM pool at the fee tier of your choice, then opens an initial concentrated position. Two transactions across four files — config.ts, createPool.ts, createPosition.ts, and utils.ts — run with npx tsx. Code is lifted from the official demos in raydium-sdk-V2-demo/src/clmm and adapted to run standalone.

Setup

Make sure you’ve read the Quick start prerequisites and have RPC_URL, KEYPAIR, and the deps installed. In addition to the deps in the quickstart, you need:
  • decimal.js — price and tick math in createPool.ts and createPosition.ts.
  • bs58 — decodes a base58-encoded keypair in config.ts if you use KEYPAIR_BS58 instead of a JSON keyfile.
  • tsx — runs the .ts files below directly via npx tsx; unlike the swap and CPMM scripts, this one isn’t a plain .mjs file run with node.
CLMM pool creation has a one-time fee plus per-tick-array rent for the initial position. You’ll also need both seed mints in your wallet — opening a position when the price sits inside the chosen range requires liquidity on both sides. Running this on devnet. Changing cluster to "devnet" in config.ts is not enough by itself. createPool.ts passes an explicit programId: CLMM_PROGRAM_ID, a fixed mainnet constant that doesn’t follow cluster — swap it for the commented-out DEVNET_PROGRAM_ID.CLMM_PROGRAM_ID line right above it. createPosition.ts also hardcodes a mainnet poolId (the RAY-USDC pool); replace it with the ID of the devnet pool you created in Step 2.

Step 1 — config.ts

Save as config.ts. This is the same shape as the demo repo’s src/config.ts.templatedisableFeatureCheck is forced to true (recommended for any non-trivial integration so the SDK does not block on its startup feature-detect call):
config.ts

Step 2 — createPool.ts

Save alongside config.ts. Source: src/clmm/createPool.ts.
createPool.ts

Step 3 — createPosition.ts

Source: src/clmm/createPosition.ts.
createPosition.ts

Step 4 — utils.ts

Source: src/clmm/utils.ts.
utils.ts

Run it

What just happened

Transaction 1 — raydium.clmm.createPool initialized:
  • the pool state at the canonical PDA for (mint1, mint2, ammConfig),
  • token_0_vault and token_1_vault (sorted by mint byte order),
  • the observation ring buffer,
  • the inline tick-array bitmap,
and set the initial sqrt_price_x64 from your initialPrice. Transaction 2 — raydium.clmm.openPositionFromBase opened a concentrated position:
  • minted a position NFT to your wallet (the NFT is the position; transferring it transfers the position),
  • allocated tick arrays at the lower and upper bounds (one-time rent if first position in those ranges; tick arrays are never closed by the program, so subsequent positions in the same arrays pay no extra rent),
  • deposited inputAmount of mint1 and the matching pair amount of mint2 (computed by PoolUtils.getLiquidityAmountOutFromAmountIn),
  • credited the position with liquidity proportional to the range width.
The narrower the range, the higher the capital efficiency per dollar of TVL — and the more painful the impermanent loss when price drifts out of range. The range used above ([0.000001, 100000]) is effectively full-range; tighten it to concentrate fees near current spot.

Picking a fee tier

clmmConfigs[0] is the lowest-fee tier. The full set is published at GET https://api-v3.raydium.io/main/clmm-config: See user-flows/choosing-a-pool-type for a full decision matrix.

Common errors

  • Pool already exists for this config — A CLMM pool already exists for this (mint1, mint2, ammConfig) triple. Look up the existing pool ID and skip Step 2.
  • Insufficient funds for amount B — Your wallet has the requested amount of mintA but not the matching mintB. Opening a position when the price sits inside the range requires liquidity on both sides.
  • Tick out of range — Your lowerPrice or upperPrice falls outside the representable price range. Use a more reasonable range relative to current price.
  • Stale price — A quote from the API can be 5–60 seconds stale. If executePosition fails on slippage, uncomment the getRpcClmmPoolInfo block in createPosition.ts to re-fetch the live price right before signing.

Caveats

  • Position NFT is your only handle. Lose the NFT or transfer it, lose access to the position. Treat it like a key.
  • Out-of-range positions earn no fees. If price moves outside [lowerPrice, upperPrice], your position is parked entirely in one asset and earns nothing until you rebalance.
  • Tick array rent is one-way. The first position to touch a never-initialised tick array pays its rent; the program does not expose a path to close tick arrays, so that rent is permanent. Subsequent positions in the same array are free.

Next

Sources: