Skip to Content
DevelopersSDK Reference

SDK Reference

orbit-dlmm is the official TypeScript SDK for the Orbit Finance DLMM protocol on Solana. All write methods return unsigned VersionedTransaction[], your app signs and sends them.

Install

npm install orbit-dlmm @coral-xyz/anchor @solana/web3.js

Quick Start

Three lines from zero to a live swap:

import { Connection, PublicKey } from "@solana/web3.js"; import { CipherDlmm } from "orbit-dlmm"; // 1. Connect to the CIPHER/USDC pool const connection = new Connection("https://api.mainnet-beta.solana.com"); const dlmm = await CipherDlmm.create( connection, new PublicKey("EoLGqHKvtK9NcxjjnvSxTYYuFMYDeWTFFyKYj1DcJyPB") ); // 2. Build a swap transaction (buy CIPHER with 1 USDC) const tx = await dlmm.swap({ user: walletPubkey, amountIn: 1_000_000n, // 1 USDC (6 decimals) minAmountOut: 200_000_000n, // min 0.2 CIPHER (9 decimals) swapForBase: true, }); // 3. Sign and send const signed = await wallet.signTransaction(tx); await connection.sendRawTransaction(signed.serialize());

CipherDlmm Class

The main entry point. Create one instance per pool.

Create

import { CipherDlmm, type CipherDlmmConfig } from "orbit-dlmm"; // Default program ID (mainnet) const dlmm = await CipherDlmm.create(connection, poolAddress); // Custom program ID (devnet / forked) const config: CipherDlmmConfig = { programId: new PublicKey("your_program_id"), }; const dlmm = await CipherDlmm.create(connection, poolAddress, config);

Refresh pool state

The class caches pool state on creation. Call refetch() before operations that depend on current price or reserves:

await dlmm.refetch(); const { binId, price } = dlmm.getActiveBin();

Read pool state

// Current active bin and price (uses cache) const { binId, price, priceQ64_64 } = dlmm.getActiveBin(); // Bin liquidity data. Pass a range or fetch all. const bins = await dlmm.getBinArrays(-1100, -900); const allBins = await dlmm.getBinArrays(); // no args = all bins // Positions for a wallet on this pool const positions = await dlmm.getPositionsByUser(walletPubkey); // Positions across ALL pools (static method) const all = await CipherDlmm.getAllPositionsByUser(connection, walletPubkey);

Add liquidity, by strategy

Returns VersionedTransaction[]. Multiple transactions are returned automatically if the bin range exceeds 32 bins.

const txs = await dlmm.addLiquidityByStrategy({ user: walletPubkey, positionNonce: 0n, // increment for each new position totalBaseAmount: 1_000_000_000n, // 1 CIPHER (9 decimals) totalQuoteAmount: 5_000_000n, // 5 USDC (6 decimals) strategy: "concentrated", // see Distribution Strategies below decay: 0.3, // tightness (0 = wide, 1 = narrow) minBinId: -1020, maxBinId: -960, slippageBps: 100, // optional, default no check }); for (const tx of txs) { const signed = await wallet.signTransaction(tx); await connection.sendRawTransaction(signed.serialize()); }

Add liquidity, raw (per-bin)

For precise control over individual bin amounts:

import { buildAddLiquidityIxs } from "orbit-dlmm"; const ixChunks = await buildAddLiquidityIxs(connection, poolAddress, user, nonce, [ { binIndex: -1000, baseAtoms: 500_000_000n, quoteAtoms: 0n }, { binIndex: -999, baseAtoms: 300_000_000n, quoteAtoms: 2_500_000n }, { binIndex: -998, baseAtoms: 0n, quoteAtoms: 2_500_000n }, ]);

Remove liquidity

// Withdraw everything const tx = await dlmm.removeLiquidity({ user: walletPubkey, position: positionPubkey, }); // Partial withdrawal. 50% from specific bins. const tx = await dlmm.removeLiquidity({ user: walletPubkey, position: positionPubkey, binIndices: [-1000, -999, -998], bpsPct: 5000, // 50% (10000 = 100%) }); // Full withdrawal + claim fees + close position in one shot const tx = await dlmm.removeLiquidity({ user: walletPubkey, position: positionPubkey, shouldClaimAndClose: true, });

Swap

// Buy base token (swapForBase: true = spend quote, receive base) const tx = await dlmm.swap({ user: walletPubkey, amountIn: 1_000_000n, // 1 USDC minAmountOut: 200_000_000n, // min 0.2 CIPHER swapForBase: true, }); // Sell base token const tx = await dlmm.swap({ user: walletPubkey, amountIn: 500_000_000n, // 0.5 CIPHER minAmountOut: 1_500_000n, // min 1.5 USDC swapForBase: false, });

Positions

// Create a new position (increment nonce for each one) const initTx = await dlmm.initializePosition(walletPubkey, 0n); // Close an empty position (must remove all liquidity first) const closeTx = await dlmm.closePosition(walletPubkey, positionPubkey);

CIPHER staker rewards

CIPHER staked on Streamflow earns USDC from protocol fee revenue. The initHolderState call is a one-time setup per wallet.

// One-time init. Required before first claim. const initTx = await dlmm.initHolderState(walletPubkey); // Claim accumulated USDC rewards const claimTx = await dlmm.claimHolderRewards(walletPubkey);

Distribution Strategies

Seven strategies control how liquidity is spread across bins. decay (0–1) controls tightness for bell-curve shapes.

Strategy shapes (schematic)
uniform ████████████████████████████████ flat across all bins balanced ░░▒▒▓███████████████████▓▒▒░░░░ soft bell curve concentrated ░░░░░░░░▒▓██████████▓▒░░░░░░░░░ tight around active bin skew_bid ░░░░░░▒▒▓████████████░░░░░░░░░░ weighted below active price skew_ask ░░░░░░░░░████████████▓▒▒░░░░░░░ weighted above active price bid-ask ████░░░░░░░░░░░░░░░░░░░░░████░ liquidity at both extremes curve ░░▒▓████░░░░░░░░░░░░████▓▒░░░░ U-shape, earns on volatility

Pass the strategy name to addLiquidityByStrategy, or import individual weight functions for custom builds:

import { weightsUniform, weightsBalanced, weightsConcentrated, weightsSkewBid, weightsSkewAsk, weightsBidAsk, weightsCurve, calculateDistributionWeights, allocateToBins, validateDistribution, } from "orbit-dlmm"; // Manual weight generation const weights = weightsConcentrated(20, 0.4); // 20 bins, decay 0.4 // Or via the dispatcher: const weights = calculateDistributionWeights("concentrated", 20, 0.4); // Allocate to bins const allocs = allocateToBins(binIndices, weights, baseAtoms, quoteAtoms, activeBin); // Validate before submitting const { valid, errors, warnings } = validateDistribution(allocs, baseAtoms, quoteAtoms); if (!valid) throw new Error(errors.join(", "));

Account Fetching

Standalone read functions, no class instance needed.

import { fetchPool, fetchAllPools, fetchBinArraysForRange, fetchAllBinArrays, fetchPositions, fetchAllUserPositions, createReadOnlyProgram, } from "orbit-dlmm"; // Single pool const pool = await fetchPool(connection, poolAddress); // All pools deployed under the program const pools = await fetchAllPools(connection); // Bin arrays for a range const bins = await fetchBinArraysForRange(connection, poolAddress, -1100, -900); // All bin arrays for a pool const allBins = await fetchAllBinArrays(connection, poolAddress); // Positions for a user on a specific pool const positions = await fetchPositions(connection, walletPubkey, poolAddress); // All positions across all pools const allPositions = await fetchAllUserPositions(connection, walletPubkey); // Anchor program (read-only, for raw account access) const program = createReadOnlyProgram(connection); const poolRaw = await program.account.pool.fetch(poolAddress);

PDA Derivation

All account addresses used by the program can be derived deterministically.

Pool & bin arrays

import { derivePoolPda, deriveBinArrayPda, getBinArrayLowerIndex, getBinOffsetInArray, groupBinsByArray, getUniqueBinArrayPdas, derivePoolAuthorityPda, canonicalBinIndexU64, decodeBinIndexSigned, } from "orbit-dlmm"; // Derive pool address from token pair + fee config const [poolPda] = derivePoolPda(baseMint, quoteMint, binStepBps, baseFeeBps); // Derive pool authority (signer PDA for token vaults) const [authority] = derivePoolAuthorityPda(baseMint, quoteMint, binStepBps, baseFeeBps); // Find the bin array that contains a given bin const lowerIndex = getBinArrayLowerIndex(-993); // => -1024 const [binArrayPda] = deriveBinArrayPda(poolPda, lowerIndex); // Offset within the bin array (0–63) const offset = getBinOffsetInArray(-993); // => 31 // Group a list of bin IDs by their parent bin array const grouped = groupBinsByArray([-1000, -999, -998, -950]); // Get unique bin array PDAs needed for a set of bin IDs const pdas = getUniqueBinArrayPdas(poolPda, [-1000, -999, -950]); // Convert signed bin ID to on-chain u64 representation and back const asU64 = canonicalBinIndexU64(-993); const signed = decodeBinIndexSigned(asU64);

Positions

import { derivePositionPda, derivePositionBinPda, deriveLiquidityLockPda, } from "orbit-dlmm"; const [positionPda] = derivePositionPda(poolPda, walletPubkey, positionNonce); const [posBinPda] = derivePositionBinPda(positionPda, binIndex); const [lockPda] = deriveLiquidityLockPda(positionPda);

Reward states

import { deriveHolderGlobalStatePda, deriveUserHolderStatePda, deriveNftGlobalStatePda, deriveUserNftStatePda, } from "orbit-dlmm"; // CIPHER staker reward accounts const [holderGlobal] = deriveHolderGlobalStatePda(); const [userHolder] = deriveUserHolderStatePda(walletPubkey); // NFT staker reward accounts const [nftGlobal] = deriveNftGlobalStatePda(); const [userNft] = deriveUserNftStatePda(walletPubkey);

Price Math

import { calculatePriceQ64_64, q64_64ToDecimal, calculateRelativePrice, binIdToPrice, priceToBinId, estimatePriceRange, } from "orbit-dlmm"; // Decimal ↔ Q64.64 (on-chain format) const q = calculatePriceQ64_64(0.00439, 9, 6); // CIPHER price const price = q64_64ToDecimal(q, 9, 6); // => 0.00439 // Relative price between two Q64.64 values const rel = calculateRelativePrice(priceA, priceB); // Bin ID ↔ price const mult = binIdToPrice(-993, 125); // bin -993 at 125bps step const binId = priceToBinId(0.00439, 125); // nearest bin for price // Estimate bin range for a price range const { minBinId, maxBinId } = estimatePriceRange( 0.0040, // lower price 0.0048, // upper price 125 // bin step );

Instruction Builders

Low-level builders return raw TransactionInstruction[]. Use these when building custom transactions or batching instructions manually.

import { buildAddLiquidityIxs, buildRemoveLiquidityIx, buildSwapIxs, buildInitPositionIx, buildClosePositionIx, buildInitHolderStateIx, buildClaimHolderRewardsIxs, buildSyncHolderStakeIx, } from "orbit-dlmm";

Sync stake checkpoint

Call this after any Streamflow stake or unstake. Records a time-weighted checkpoint used for pro-rata reward distribution.

const syncIx = buildSyncHolderStakeIx( walletPubkey, stakeEntryNonce, // your Streamflow stake entry nonce stakePoolAddress, // defaults to CIPHER staking pool );

Claim holder rewards (manual)

Returns [ensureATA, syncRewardIndexes, claimHolderRewards], submit as a single transaction:

const ixs = await buildClaimHolderRewardsIxs(connection, walletPubkey, poolAddress); const { transaction } = await buildTransaction(connection, { payer: walletPubkey, instructions: ixs, includeHeapFrame: true, });

Transaction builder

All SDK methods use this internally. Available for custom batching:

import { buildTransaction } from "orbit-dlmm"; const { transaction } = await buildTransaction(connection, { payer: walletPubkey, instructions: [ix1, ix2, ix3], computeUnitPrice: 100_000, // micro-lamports per CU computeUnitLimit: 400_000, includeHeapFrame: true, // 256KB, required by the program });

Error Handling

import { parseProgramError, CipherDlmmError, CIPHER_DLMM_ERRORS } from "orbit-dlmm"; try { await connection.sendRawTransaction(signed.serialize()); } catch (err) { // Parse a numeric error code from a failed transaction const parsed = parseProgramError(6006); if (parsed) { console.log(parsed.errorName); // "SlippageExceeded" console.log(parsed.message); // "Swap did not meet minimum output" } } // Browse all error codes console.log(CIPHER_DLMM_ERRORS); // { 6000: { errorName: "InvalidBinStep", ... }, 6001: ..., ... } // CipherDlmmError is thrown by SDK methods for validation failures // before any RPC call is made, e.g. "All bins rounded to zero"

Types

Key types exported from the SDK:

import type { // Pool PoolState, FeeConfig, CompactBin, BinArrayState, ActiveBin, // Positions PositionState, PositionBinState, UserPosition, // Instructions AddLiquidityByStrategyParams, AddLiquidityParams, RemoveLiquidityParams, SwapParams, SwapQuoteResult, InitPositionParams, ClosePositionParams, ClaimRewardsParams, ClaimNftRewardsParams, // { user, nftMints: PublicKey[] } // Misc DistributionStrategy, BinAllocation, ValidationResult, CipherDlmmConfig, PriorityLevel, BuildTransactionOptions, PriceQ64_64, } from "orbit-dlmm";

Constants

import { DEFAULT_PROGRAM_ID, // Fn3fA3fjsmpULNL7E9U79jKTe1KHxPtQeWdURCbJXCnM BIN_ARRAY_SIZE, // 64 MAX_BINS_PER_TX, // 32 REQUIRED_HEAP_BYTES, // 262144 (256KB) KNOWN_MINTS, // { CIPHER, USDC, SOL } VALID_BIN_STEPS, // [1, 2, 4, 5, 8, 10, 15, 16, 20, 25, 30, 50, 75, ...] } from "orbit-dlmm"; // CIPHER/USDC pool (mainnet) const CIPHER_USDC_POOL = "EoLGqHKvtK9NcxjjnvSxTYYuFMYDeWTFFyKYj1DcJyPB"; // binStep: 125bps · fee: 200bps (2%)

Fee Architecture

Every swap through an Orbit Finance pool generates protocol fees. Here’s how they flow:

CIPHER/USDC pool, fee distribution (2% per swap)
Swap fee (2%) ├── 50% → Team (Squads multisig) ├── 37.5% → CIPHER token stakers ← claimHolderRewards() ├── 7.5% → Cipher Owl NFT stakers └── 5% → DAO treasury
Ecosystem pools, fee distribution (~0.054% variable)
Swap fee ├── 12.5% → Protocol (hardcoded on-chain → Squads) ├── holder vault → CIPHER stakers claim via Q128 ├── NFT vault → Owl holders claim via Q128 └── creator vault → keeper sweeps → splits team/DAO

CIPHER stakers earn from both fee streams. The more volume routed through Orbit pools, the higher the yield.

Protocol fee (CIPHER/USDC)

2.00%

per swap, all pools

CIPHER staker share

37.5%

of CIPHER/USDC pool fees

Distribution cadence

Weekly

paid in USDC


Building something on top of Orbit Finance?

Stake $CIPHER, earn from every swap.

Every integration that routes swap volume through Orbit pools contributes to the fee flywheel. CIPHER stakers earn 37.5% of all CIPHER/USDC pool fees, paid weekly in USDC.

Learn about $CIPHER staking →

Last updated on