| name | orca |
| creator | raunit-dev |
| description | Complete guide for Orca - Solana's leading concentrated liquidity AMM (CLMM). Covers Whirlpools SDK for swaps, liquidity provision, pool creation, position management, and fee harvesting on Solana and Eclipse networks. |
Orca Whirlpools Development Guide
Orca is the most trusted DEX on Solana and Eclipse, built on a concentrated liquidity automated market maker (CLMM) called Whirlpools. This guide covers the Whirlpools SDK for building trading, liquidity provision, and pool management applications.
CRITICAL: Two API Levels — Wrapper Functions vs Instructions Functions
The Orca SDK v7 has two API levels. Using the wrong function signatures is the #1 cause of errors.
Wrapper Functions (swap, openConcentratedPosition, etc.)
These use global config set by setRpc() and setPayerFromBytes(). They do NOT take rpc or wallet arguments. They return { ..., callback } — call callback() to send the transaction.
await setWhirlpoolsConfig("solanaMainnet");
await setRpc(config.preferences.rpcUrl);
const wallet = await setPayerFromBytes(keypairBytes);
const result = await swap(
{ inputAmount: 10_000_000n, mint: SOL_MINT },
poolAddress,
100
);
console.log("Quote:", result.quote.tokenEstOut);
const txId = await result.callback();
const result = await swap(rpc, { inputAmount, mint }, poolAddress, slippage, wallet);
Instructions Functions (swapInstructions, openPositionInstructions, etc.)
These take rpc and signer/funder explicitly. Use these when you need the quote/instructions without sending.
import { createSolanaRpc } from "@solana/kit";
const rpc = createSolanaRpc(config.preferences.rpcUrl);
const { instructions, quote } = await swapInstructions(
rpc,
{ inputAmount: 10_000_000n, mint: SOL_MINT },
poolAddress,
100,
wallet
);
Complete Function Reference
Wrapper functions (use global config, return { ..., callback }):
| Function | Arguments (NO rpc, NO wallet) |
|---|
swap | (input, poolAddress, slippage?) |
openConcentratedPosition | (poolAddress, param, lowerPrice, upperPrice, slippage?, withMetadata?) |
openFullRangePosition | (poolAddress, param, slippage?, withMetadata?) |
increasePosLiquidity | (positionMint, param, slippage?) |
decreaseLiquidity | (positionMint, param, slippage?) |
harvestPosition | (positionMint) |
closePosition | (positionMint, slippage?) |
createSplashPool | (tokenMintA, tokenMintB, initialPrice?) |
createConcentratedLiquidityPool | (tokenMintA, tokenMintB, tickSpacing, initialPrice?) |
Instructions functions (take rpc + signer explicitly):
| Function | First arg | Last arg |
|---|
swapInstructions | rpc | signer |
openPositionInstructions | rpc | funder |
openFullRangePositionInstructions | rpc | funder |
increaseLiquidityInstructions | rpc | authority |
decreaseLiquidityInstructions | rpc | authority |
harvestPositionInstructions | rpc | authority |
closePositionInstructions | rpc | authority |
Fetch functions (take rpc explicitly, read-only):
| Function | Arguments |
|---|
fetchConcentratedLiquidityPool | (rpc, mintOne, mintTwo, tickSpacing) |
fetchSplashPool | (rpc, mintOne, mintTwo) |
fetchWhirlpoolsByTokenPair | (rpc, mintOne, mintTwo) |
fetchPositionsForOwner | (rpc, ownerAddress) |
fetchPositionsInWhirlpool | (rpc, whirlpoolAddress) |
RPC setup
import { createSolanaRpc } from "@solana/kit";
await setRpc(config.preferences.rpcUrl);
const rpc = createSolanaRpc(config.preferences.rpcUrl);
Do NOT use new Connection() from @solana/web3.js — it is incompatible with the Orca SDK.
Overview
Orca Whirlpools provides:
- Token Swaps - Efficient token exchanges with low slippage and competitive rates
- Concentrated Liquidity - Provide liquidity within custom price ranges for higher capital efficiency
- Splash Pools - Simple full-range liquidity provision for beginners
- Position Management - Open, increase, decrease, and close liquidity positions
- Fee Harvesting - Collect trading fees and rewards from positions
- Pool Creation - Permissionless creation of new liquidity pools
Program IDs
| Network | Program ID |
|---|
| Solana Mainnet | whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc |
| Solana Devnet | whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc |
| Eclipse Mainnet | whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc |
| Eclipse Testnet | whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc |
Quick Start
Installation
New SDK (Solana Web3.js v2):
npm install @orca-so/whirlpools @solana/kit
Legacy SDK (Solana Web3.js v1):
npm install @orca-so/whirlpools-sdk @orca-so/common-sdk @coral-xyz/anchor@0.29.0 @solana/web3.js @solana/spl-token decimal.js
Core Utilities (optional):
npm install @orca-so/whirlpools-core @orca-so/whirlpools-client
Basic Setup (New SDK)
import {
setWhirlpoolsConfig,
setRpc,
setPayerFromBytes
} from "@orca-so/whirlpools";
import { createSolanaRpc, devnet } from "@solana/kit";
import fs from "fs";
await setWhirlpoolsConfig("solanaDevnet");
await setRpc("https://api.devnet.solana.com");
const keyPairBytes = new Uint8Array(
JSON.parse(fs.readFileSync("./keypair.json", "utf8"))
);
const wallet = await setPayerFromBytes(keyPairBytes);
const rpc = createSolanaRpc(devnet("https://api.devnet.solana.com"));
console.log("Wallet:", wallet.address);
Basic Setup (Legacy SDK)
import { WhirlpoolContext, buildWhirlpoolClient, ORCA_WHIRLPOOL_PROGRAM_ID } from "@orca-so/whirlpools-sdk";
import { AnchorProvider, Wallet } from "@coral-xyz/anchor";
import { Connection, Keypair } from "@solana/web3.js";
import fs from "fs";
const connection = new Connection("https://api.breeze.baby/agent/rpc-mainnet-beta", "confirmed");
const secretKey = JSON.parse(fs.readFileSync("./keypair.json", "utf8"));
const wallet = new Wallet(Keypair.fromSecretKey(new Uint8Array(secretKey)));
const provider = new AnchorProvider(connection, wallet, {});
const ctx = WhirlpoolContext.from(connection, wallet, ORCA_WHIRLPOOL_PROGRAM_ID);
const client = buildWhirlpoolClient(ctx);
console.log("Wallet:", wallet.publicKey.toString());
Token Swaps
Swap with New SDK
import { swap, swapInstructions, setWhirlpoolsConfig, setRpc, setPayerFromBytes } from "@orca-so/whirlpools";
import { address, createSolanaRpc } from "@solana/kit";
await setWhirlpoolsConfig("solanaMainnet");
await setRpc(rpcUrl);
const wallet = await setPayerFromBytes(keypairBytes);
const poolAddress = address("POOL_ADDRESS_HERE");
const inputMint = address("INPUT_TOKEN_MINT");
const amount = 1_000_000n;
const slippageTolerance = 100;
const result = await swap(
{ inputAmount: amount, mint: inputMint },
poolAddress,
slippageTolerance
);
console.log("Expected output:", result.quote.tokenEstOut);
const txId = await result.callback();
console.log("Swap transaction:", txId);
const rpc = createSolanaRpc(rpcUrl);
const { instructions, quote } = await swapInstructions(
rpc,
{ inputAmount: amount, mint: inputMint },
poolAddress,
slippageTolerance,
wallet
);
console.log("Expected output:", quote.tokenEstOut);
console.log("Minimum output:", quote.tokenMinOut);
Exact Output Swap
const result = await swap(
{ outputAmount: 1_000_000n, mint: outputMint },
poolAddress,
slippageTolerance
);
console.log("Max input required:", result.quote.tokenMaxIn);
const txId = await result.callback();
Swap with Legacy SDK
import { WhirlpoolContext, swapQuoteByInputToken, buildWhirlpoolClient } from "@orca-so/whirlpools-sdk";
import { DecimalUtil, Percentage } from "@orca-so/common-sdk";
import Decimal from "decimal.js";
const whirlpool = await client.getPool(poolAddress);
const whirlpoolData = whirlpool.getData();
const inputAmount = new Decimal("1.0");
const quote = await swapQuoteByInputToken(
whirlpool,
whirlpoolData.tokenMintA,
DecimalUtil.toBN(inputAmount, 9),
Percentage.fromFraction(1, 100),
ctx.program.programId,
ctx.fetcher
);
console.log("Estimated output:", quote.estimatedAmountOut.toString());
const tx = await whirlpool.swap(quote);
const signature = await tx.buildAndExecute();
console.log("Swap signature:", signature);
Liquidity Provision
Position Types
Concentrated Liquidity Position: Provide liquidity within a specific price range for higher capital efficiency.
Full Range Position (Splash Pool): Provide liquidity across the entire price range, similar to traditional AMMs.
Open Concentrated Liquidity Position
IMPORTANT: IncreaseLiquidityParam format in SDK v7
In SDK v7, openPositionInstructions / openConcentratedPosition internally call getIncreaseLiquidityInstructions which destructures param: { tokenMaxA, tokenMaxB } directly. Passing { tokenA: bigint } will cause TypeError: Cannot convert undefined to a BigInt because tokenMaxA ends up as undefined.
The correct pattern is to compute the quote first with increaseLiquidityQuoteA (or B) from @orca-so/whirlpools-core, then pass { tokenMaxA: quote.tokenMaxA, tokenMaxB: quote.tokenMaxB }.
import { openConcentratedPosition, openPositionInstructions } from "@orca-so/whirlpools";
import { increaseLiquidityQuoteA, priceToTickIndex, getInitializableTickIndex } from "@orca-so/whirlpools-core";
import { address, createSolanaRpc } from "@solana/kit";
const poolAddress = address("POOL_ADDRESS");
const slippageTolerance = 100;
const pools = await fetchWhirlpoolsByTokenPair(rpc, tokenMintA, tokenMintB);
const pool = pools.find(p => p.address === "POOL_ADDRESS");
const decimalsA = 9;
const decimalsB = 6;
const lowerTick = getInitializableTickIndex(
priceToTickIndex(pool.price * 0.75, decimalsA, decimalsB),
pool.tickSpacing, false
);
const upperTick = getInitializableTickIndex(
priceToTickIndex(pool.price * 1.25, decimalsA, decimalsB),
pool.tickSpacing, true
);
const solAmount = 10_000_000n;
const quote = increaseLiquidityQuoteA(
solAmount,
slippageTolerance,
pool.sqrtPrice,
lowerTick,
upperTick,
);
const param = { tokenMaxA: quote.tokenMaxA, tokenMaxB: quote.tokenMaxB };
const result = await openConcentratedPosition(
poolAddress, param, pool.price * 0.75, pool.price * 1.25, slippageTolerance
);
console.log("Position mint:", result.positionMint);
const txId = await result.callback();
const rpc = createSolanaRpc(rpcUrl);
const { instructions, positionMint, initializationCost } =
await openPositionInstructions(rpc, poolAddress, param, pool.price * 0.75, pool.price * 1.25, slippageTolerance, true, wallet);
SOL balance requirement for openPositionInstructions
OpenPositionWithTokenExtensions creates several on-chain accounts (position PDA, Token-2022 NFT mint with metadata, Token-2022 ATA for the NFT). This costs approximately ~10M lamports (~0.01 SOL) in overhead, independent of the liquidity amount. The initializationCost returned by the SDK is incorrectly reported as 0 — do not rely on it.
Budget rule: walletSOL ≥ 0.01 (overhead) + 0.002 (keypair rent, refunded) + tokenMaxA + txFees
For a wallet with 0.019 SOL, the maximum depositable SOL is approximately 0.006 SOL.
Open Full Range Position
import { openFullRangePosition, openFullRangePositionInstructions } from "@orca-so/whirlpools";
const poolAddress = address("POOL_ADDRESS");
const param = { tokenA: 1_000_000_000n };
const slippageTolerance = 100;
const result = await openFullRangePosition(poolAddress, param, slippageTolerance);
console.log("Position mint:", result.positionMint);
const txId = await result.callback();
const rpc = createSolanaRpc(rpcUrl);
const { instructions, quote, positionMint, callback } =
await openFullRangePositionInstructions(rpc, poolAddress, param, slippageTolerance, true, wallet);
const txId2 = await callback();
Increase Position Liquidity
import { increasePosLiquidity, increaseLiquidityInstructions } from "@orca-so/whirlpools";
const positionMint = address("POSITION_MINT");
const param = { tokenA: 500_000_000n };
const slippageTolerance = 100;
const result = await increasePosLiquidity(positionMint, param, slippageTolerance);
const txId = await result.callback();
const rpc = createSolanaRpc(rpcUrl);
const { instructions, quote } = await increaseLiquidityInstructions(rpc, positionMint, param, slippageTolerance, wallet);
console.log("Additional Token A:", quote.tokenEstA);
Decrease Position Liquidity
import { decreaseLiquidity, decreaseLiquidityInstructions } from "@orca-so/whirlpools";
const positionMint = address("POSITION_MINT");
const param = { liquidity: 1000000n };
const slippageTolerance = 100;
const result = await decreaseLiquidity(positionMint, param, slippageTolerance);
const txId = await result.callback();
const rpc = createSolanaRpc(rpcUrl);
const { instructions, quote } = await decreaseLiquidityInstructions(rpc, positionMint, param, slippageTolerance, wallet);
console.log("Token A received:", quote.tokenEstA);
Harvest Fees and Rewards
import { harvestPosition, harvestPositionInstructions } from "@orca-so/whirlpools";
const positionMint = address("POSITION_MINT");
const result = await harvestPosition(positionMint);
const txId = await result.callback();
const rpc = createSolanaRpc(rpcUrl);
const { instructions, feesQuote, rewardsQuote } = await harvestPositionInstructions(rpc, positionMint, wallet);
console.log("Fee Token A:", feesQuote.feeOwedA);
console.log("Fee Token B:", feesQuote.feeOwedB);
Close Position
import { closePosition, closePositionInstructions } from "@orca-so/whirlpools";
const positionMint = address("POSITION_MINT");
const slippageTolerance = 100;
const result = await closePosition(positionMint, slippageTolerance);
const txId = await result.callback();
const rpc = createSolanaRpc(rpcUrl);
const { instructions, quote, feesQuote } = await closePositionInstructions(rpc, positionMint, slippageTolerance, wallet);
console.log("Token A returned:", quote.tokenEstA);
Pool Management
Create Splash Pool (Full Range)
import { createSplashPool, createSplashPoolInstructions } from "@orca-so/whirlpools";
import { address, createSolanaRpc } from "@solana/kit";
const tokenMintA = address("TOKEN_A_MINT");
const tokenMintB = address("TOKEN_B_MINT");
const initialPrice = 1.5;
const result = await createSplashPool(tokenMintA, tokenMintB, initialPrice);
console.log("Pool address:", result.poolAddress);
const txId = await result.callback();
const rpc = createSolanaRpc(rpcUrl);
const { instructions, poolAddress, initializationCost } =
await createSplashPoolInstructions(rpc, tokenMintA, tokenMintB, initialPrice, wallet);
Create Concentrated Liquidity Pool
import { createConcentratedLiquidityPool, createConcentratedLiquidityPoolInstructions } from "@orca-so/whirlpools";
const tokenMintA = address("TOKEN_A_MINT");
const tokenMintB = address("TOKEN_B_MINT");
const tickSpacing = 64;
const initialPrice = 1.5;
const result = await createConcentratedLiquidityPool(tokenMintA, tokenMintB, tickSpacing, initialPrice);
const txId = await result.callback();
const rpc = createSolanaRpc(rpcUrl);
const { instructions, poolAddress } =
await createConcentratedLiquidityPoolInstructions(rpc, tokenMintA, tokenMintB, tickSpacing, initialPrice, wallet);
Fetch Pool Data
import {
fetchSplashPool,
fetchConcentratedLiquidityPool,
fetchWhirlpoolsByTokenPair
} from "@orca-so/whirlpools";
const pool = await fetchConcentratedLiquidityPool(rpc, poolAddress);
console.log("Current price:", pool.price);
console.log("Liquidity:", pool.liquidity);
console.log("Fee rate:", pool.feeRate);
const pools = await fetchWhirlpoolsByTokenPair(rpc, tokenMintA, tokenMintB);
for (const pool of pools) {
console.log("Pool:", pool.address, "Tick spacing:", pool.tickSpacing);
}
Fetch Positions
import { fetchPositionsForOwner, fetchPositionsInWhirlpool } from "@orca-so/whirlpools";
const positions = await fetchPositionsForOwner(rpc, wallet.address);
for (const position of positions) {
console.log("Position:", position.positionMint);
console.log("Liquidity:", position.liquidity);
console.log("Lower tick:", position.tickLowerIndex);
console.log("Upper tick:", position.tickUpperIndex);
}
const poolPositions = await fetchPositionsInWhirlpool(rpc, poolAddress);
SDK Configuration
Network Configuration
import { setWhirlpoolsConfig } from "@orca-so/whirlpools";
await setWhirlpoolsConfig("solanaMainnet");
await setWhirlpoolsConfig("solanaDevnet");
await setWhirlpoolsConfig("eclipseMainnet");
await setWhirlpoolsConfig("eclipseTestnet");
await setWhirlpoolsConfig({
whirlpoolsConfigAddress: address("CUSTOM_CONFIG_ADDRESS"),
});
Transaction Settings
import {
setRpc,
setPriorityFeeSetting,
setJitoTipSetting,
setComputeUnitMarginMultiplier,
setDefaultSlippageToleranceBps,
setDefaultFunder,
setNativeMintWrappingStrategy
} from "@orca-so/whirlpools";
await setRpc("https://api.breeze.baby/agent/rpc-mainnet-beta");
await setPriorityFeeSetting({
type: "dynamic",
percentile: 75,
});
await setPriorityFeeSetting({
type: "fixed",
microLamports: 10000,
});
await setJitoTipSetting({
type: "dynamic",
percentile: 50,
});
await setComputeUnitMarginMultiplier(1.2);
await setDefaultSlippageToleranceBps(100);
await setDefaultFunder(wallet);
await setNativeMintWrappingStrategy("none");
Reset Configuration
import { resetConfiguration } from "@orca-so/whirlpools";
await resetConfiguration();
Tick Spacing Reference
Tick spacing determines the granularity of price ranges:
| Tick Spacing | Use Case | Fee Tier |
|---|
| 1 | Stablecoins (e.g., USDC/USDT) | 0.01% |
| 8 | Correlated pairs | 0.04% |
| 64 | Standard pairs | 0.30% |
| 128 | Exotic/volatile pairs | 1.00% |
Best Practices
Security
- Never expose private keys - Use environment variables or secure key management
- Verify pool addresses - Always confirm you're interacting with legitimate pools
- Use slippage protection - Set appropriate slippage tolerance to prevent front-running
- Test on devnet first - Validate all operations before mainnet deployment
Performance
- Use priority fees - Essential for mainnet transactions during high congestion
- Batch operations - Combine multiple instructions when possible
- Pre-fetch data - Cache pool and position data to reduce RPC calls
Liquidity Provision
- Monitor positions - Track when positions go out of range
- Rebalance strategically - Consider gas costs when adjusting positions
- Harvest regularly - Collect fees and rewards to compound returns
- Understand impermanent loss - Concentrated positions have amplified IL risk
Error Handling
try {
const result = await swap(swapParams, poolAddress, slippage);
const txId = await result.callback();
} catch (error) {
if (error.message.includes("SlippageExceeded")) {
console.error("Slippage tolerance exceeded, try increasing slippage");
} else if (error.message.includes("InsufficientFunds")) {
console.error("Not enough tokens in wallet");
} else if (error.message.includes("TickArrayNotInitialized")) {
console.error("Price range tick arrays not initialized");
} else {
throw error;
}
}
Resources
Official Documentation
GitHub Repositories
NPM Packages
Security Audits
- Kudelski Security audit
- Neodyme audit
Skill Structure
orca/
├── SKILL.md # This file
├── resources/
│ ├── api-reference.md # Complete SDK function reference
│ └── program-addresses.md # Program IDs and common pool addresses
├── examples/
│ ├── swap/
│ │ └── token-swap.ts # Token swap examples
│ ├── liquidity/
│ │ ├── open-position.ts # Open liquidity positions
│ │ ├── manage-position.ts # Increase/decrease liquidity
│ │ └── harvest-fees.ts # Collect fees and rewards
│ └── pools/
│ └── create-pool.ts # Pool creation examples
├── templates/
│ └── setup.ts # Ready-to-use client template
└── docs/
└── troubleshooting.md # Common issues and solutions