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 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.
Orca Whirlpools provides:
| Network | Program ID |
|---|
| Solana Mainnet | whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc |
| Solana Devnet | whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc |
| Eclipse Mainnet | whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc |
| Eclipse Testnet | whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc |
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/[email protected] @solana/web3.js @solana/spl-token decimal.js
Core Utilities (optional):
npm install @orca-so/whirlpools-core @orca-so/whirlpools-client
import {
setWhirlpoolsConfig,
setRpc,
setPayerFromBytes
} from "@orca-so/whirlpools";
import { createSolanaRpc, devnet } from "@solana/kit";
import fs from "fs";
// Configure for Solana Devnet
await setWhirlpoolsConfig("solanaDevnet");
await setRpc("https://api.devnet.solana.com");
// Load wallet from keypair file
const keyPairBytes = new Uint8Array(
JSON.parse(fs.readFileSync("./keypair.json", "utf8"))
);
const wallet = await setPayerFromBytes(keyPairBytes);
// Create RPC connection
const rpc = createSolanaRpc(devnet("https://api.devnet.solana.com"));
console.log("Wallet:", wallet.address);
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";
// Setup connection
const connection = new Connection("https://api.mainnet-beta.solana.com", "confirmed");
// Load wallet
const secretKey = JSON.parse(fs.readFileSync("./keypair.json", "utf8"));
const wallet = new Wallet(Keypair.fromSecretKey(new Uint8Array(secretKey)));
// Create provider and context
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());
import { swap, swapInstructions, setWhirlpoolsConfig } from "@orca-so/whirlpools";
import { address } from "@solana/kit";
await setWhirlpoolsConfig("solanaMainnet");
const poolAddress = address("POOL_ADDRESS_HERE");
const inputMint = address("INPUT_TOKEN_MINT");
const amount = 1_000_000n; // Amount in smallest units
const slippageTolerance = 100; // 100 bps = 1%
// Option 1: Use the simple swap function (builds and sends)
const txId = await swap(
rpc,
{ inputAmount: amount, mint: inputMint },
poolAddress,
slippageTolerance,
wallet
);
console.log("Swap transaction:", txId);
// Option 2: Get instructions for custom transaction building
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);
console.log("Price impact:", quote.priceImpact);
// Swap to get exact output amount
const { instructions, quote } = await swapInstructions(
rpc,
{ outputAmount: 1_000_000n, mint: outputMint },
poolAddress,
slippageTolerance,
wallet
);
console.log("Max input required:", quote.tokenMaxIn);
import { WhirlpoolContext, swapQuoteByInputToken, buildWhirlpoolClient } from "@orca-so/whirlpools-sdk";
import { DecimalUtil, Percentage } from "@orca-so/common-sdk";
import Decimal from "decimal.js";
// Get the pool
const whirlpool = await client.getPool(poolAddress);
const whirlpoolData = whirlpool.getData();
// Get swap quote
const inputAmount = new Decimal("1.0"); // 1 token
const quote = await swapQuoteByInputToken(
whirlpool,
whirlpoolData.tokenMintA,
DecimalUtil.toBN(inputAmount, 9), // 9 decimals
Percentage.fromFraction(1, 100), // 1% slippage
ctx.program.programId,
ctx.fetcher
);
console.log("Estimated output:", quote.estimatedAmountOut.toString());
// Execute swap
const tx = await whirlpool.swap(quote);
const signature = await tx.buildAndExecute();
console.log("Swap signature:", signature);
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.
import { openPosition, openPositionInstructions } from "@orca-so/whirlpools";
import { address } from "@solana/kit";
const poolAddress = address("POOL_ADDRESS");
const lowerPrice = 0.001; // Lower bound of price range
const upperPrice = 100.0; // Upper bound of price range
const slippageTolerance = 100; // 1%
// Specify liquidity by token amount
const param = { tokenA: 1_000_000_000n }; // 1 token with 9 decimals
// Option 1: Simple function that builds and sends
const txId = await openPosition(
rpc,
poolAddress,
param,
lowerPrice,
upperPrice,
slippageTolerance,
wallet
);
// Option 2: Get instructions for custom transaction
const {
instructions,
quote,
positionMint,
initializationCost
} = await openPositionInstructions(
rpc,
poolAddress,
param,
lowerPrice,
upperPrice,
slippageTolerance,
wallet
);
console.log("Position mint:", positionMint);
console.log("Token A required:", quote.tokenEstA);
console.log("Token B required:", quote.tokenEstB);
console.log("Initialization cost:", initializationCost);
import { openFullRangePosition, openFullRangePositionInstructions } from "@orca-so/whirlpools";
const poolAddress = address("POOL_ADDRESS");
const param = { tokenA: 1_000_000_000n };
const slippageTolerance = 100;
const {
instructions,
quote,
positionMint,
callback: sendTx
} = await openFullRangePositionInstructions(
rpc,
poolAddress,
param,
slippageTolerance,
wallet
);
console.log("Position mint:", positionMint);
console.log("Token max B:", quote.tokenMaxB);
// Send the transaction
const txId = await sendTx();
console.log("Transaction:", txId);
import { increaseLiquidity, increaseLiquidityInstructions } from "@orca-so/whirlpools";
const positionMint = address("POSITION_MINT");
const param = { tokenA: 500_000_000n }; // Add 0.5 tokens
const slippageTolerance = 100;
const { instructions, quote } = await increaseLiquidityInstructions(
rpc,
positionMint,
param,
slippageTolerance,
wallet
);
console.log("Additional Token A:", quote.tokenEstA);
console.log("Additional Token B:", quote.tokenEstB);
import { decreaseLiquidity, decreaseLiquidityInstructions } from "@orca-so/whirlpools";
const positionMint = address("POSITION_MINT");
const param = { liquidity: 1000000n }; // Or use tokenA/tokenB
const slippageTolerance = 100;
const { instructions, quote } = await decreaseLiquidityInstructions(
rpc,
positionMint,
param,
slippageTolerance,
wallet
);
console.log("Token A received:", quote.tokenEstA);
console.log("Token B received:", quote.tokenEstB);
import { harvestPosition, harvestPositionInstructions, harvestAllPositionFees } from "@orca-so/whirlpools";
// Harvest single position
const positionMint = address("POSITION_MINT");
const { instructions, feesQuote, rewardsQuote } = await harvestPositionInstructions(
rpc,
positionMint,
wallet
);
console.log("Fee Token A:", feesQuote.feeOwedA);
console.log("Fee Token B:", feesQuote.feeOwedB);
console.log("Rewards:", rewardsQuote);
// Harvest all positions at once
const { instructions: harvestAllIx, fees, rewards } = await harvestAllPositionFees(
rpc,
wallet.address
);
import { closePosition, closePositionInstructions } from "@orca-so/whirlpools";
const positionMint = address("POSITION_MINT");
const slippageTolerance = 100;
const { instructions, quote, feesQuote } = await closePositionInstructions(
rpc,
positionMint,
slippageTolerance,
wallet
);
console.log("Token A returned:", quote.tokenEstA);
console.log("Token B returned:", quote.tokenEstB);
console.log("Fees collected:", feesQuote);
import { createSplashPool, createSplashPoolInstructions } from "@orca-so/whirlpools";
import { address } from "@solana/kit";
const tokenMintA = address("TOKEN_A_MINT");
const tokenMintB = address("TOKEN_B_MINT");
const initialPrice = 1.5; // Price of token B in terms of token A
const {
instructions,
poolAddress,
initializationCost
} = await createSplashPoolInstructions(
rpc,
tokenMintA,
tokenMintB,
initialPrice,
wallet
);
console.log("Pool address:", poolAddress);
console.log("Initialization cost:", initializationCost, "lamports");
import { createConcentratedLiquidityPool, createConcentratedLiquidityPoolInstructions } from "@orca-so/whirlpools";
const tokenMintA = address("TOKEN_A_MINT");
const tokenMintB = address("TOKEN_B_MINT");
const tickSpacing = 64; // Common values: 1, 8, 64, 128
const initialPrice = 1.5;
const {
instructions,
poolAddress,
initializationCost
} = await createConcentratedLiquidityPoolInstructions(
rpc,
tokenMintA,
tokenMintB,
tickSpacing,
initialPrice,
wallet
);
console.log("Pool address:", poolAddress);
import {
fetchSplashPool,
fetchConcentratedLiquidityPool,
fetchWhirlpoolsByTokenPair
} from "@orca-so/whirlpools";
// Fetch specific pool
const pool = await fetchConcentratedLiquidityPool(rpc, poolAddress);
console.log("Current price:", pool.price);
console.log("Liquidity:", pool.liquidity);
console.log("Fee rate:", pool.feeRate);
// Fetch all pools for a token pair
const pools = await fetchWhirlpoolsByTokenPair(rpc, tokenMintA, tokenMintB);
for (const pool of pools) {
console.log("Pool:", pool.address, "Tick spacing:", pool.tickSpacing);
}
import { fetchPositionsForOwner, fetchPositionsInWhirlpool } from "@orca-so/whirlpools";
// Get all positions owned by a wallet
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);
}
// Get all positions in a specific pool
const poolPositions = await fetchPositionsInWhirlpool(rpc, poolAddress);
import { setWhirlpoolsConfig } from "@orca-so/whirlpools";
// Built-in presets
await setWhirlpoolsConfig("solanaMainnet");
await setWhirlpoolsConfig("solanaDevnet");
await setWhirlpoolsConfig("eclipseMainnet");
await setWhirlpoolsConfig("eclipseTestnet");
// Custom configuration
await setWhirlpoolsConfig({
whirlpoolsConfigAddress: address("CUSTOM_CONFIG_ADDRESS"),
// ... other custom settings
});
import {
setRpc,
setPriorityFeeSetting,
setJitoTipSetting,
setComputeUnitMarginMultiplier,
setDefaultSlippageToleranceBps,
setDefaultFunder,
setNativeMintWrappingStrategy
} from "@orca-so/whirlpools";
// Set RPC endpoint
await setRpc("https://api.mainnet-beta.solana.com");
// Configure priority fees
await setPriorityFeeSetting({
type: "dynamic",
percentile: 75, // Use 75th percentile of recent fees
});
// Or set fixed priority fee
await setPriorityFeeSetting({
type: "fixed",
microLamports: 10000,
});
// Configure Jito tips for MEV protection
await setJitoTipSetting({
type: "dynamic",
percentile: 50,
});
// Set compute unit margin (for larger transactions)
await setComputeUnitMarginMultiplier(1.2);
// Set default slippage tolerance (in bps)
await setDefaultSlippageToleranceBps(100); // 1%
// Set default funder for transactions
await setDefaultFunder(wallet);
// Configure SOL wrapping behavior
await setNativeMintWrappingStrategy("ata"); // or "none"
import { resetConfiguration } from "@orca-so/whirlpools";
// Reset all settings to defaults
await resetConfiguration();
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% |
try {
const txId = await swap(rpc, swapParams, poolAddress, slippage, wallet);
} 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;
}
}
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