Migrate to Raydium SDK v2 (v1 is end-of-life)#28
Conversation
@raydium-io/raydium-sdk (v1) was archived in June 2025 and never supported CPMM/ CLMM pools, so this example no longer reflects current Raydium. Migrate to @raydium-io/raydium-sdk-v2: - New src/config.ts with the initSdk() singleton (Raydium.load) + connection/owner from .env, modeled on the official raydium-sdk-V2-demo. - Rebuild RaydiumSwap on v2: fetchPoolById + getAmmPoolKeys + getRpcPoolInfo -> liquidity.computeAmountOut -> liquidity.swap -> execute()/simulate. - v2 fetches pool data from the API/RPC, so the ~500MB mainnet.json download + trim step is gone (removed trimMainnet.ts + trimmed_mainnet.json). Pools are now identified by poolId in swapConfig.ts (default SOL-USDC), with a configurable priority fee (computeUnitPriceMicroLamports). - package.json: drop @raydium-io/raydium-sdk + @coral-xyz/anchor; add @raydium-io/raydium-sdk-v2, @solana/web3.js, bn.js. README updated. Verified: tsc --noEmit clean, and the full flow runs live (init -> fetch pool -> computeAmountOut [0.001 SOL -> ~0.068 USDC] -> build -> simulate). Broadcast not run (no funds).
📝 WalkthroughWalkthroughMigrates the Raydium swap example to SDK v2, replacing the local pool-file workflow with env-based initialization, a new pool/config shape, rewritten swap construction, and updated execute/simulate handling. README guidance and dependency versions were updated to match. ChangesRaydium SDK v2 migration
Sequence Diagram(s)sequenceDiagram
participant Index as src/index.ts
participant Swap as RaydiumSwap
participant Config as src/config.ts
participant SDK as Raydium SDK v2
participant Conn as Connection
Index->>Swap: initialize()
Swap->>Config: initSdk()
Config->>SDK: load Raydium with owner and connection
SDK-->>Config: Raydium instance
Config-->>Swap: Raydium instance
Index->>Swap: getSwapTransaction(poolId, inputMint, tokenAAmount, slippage, computeUnitPriceMicroLamports)
Swap->>SDK: getPoolData(poolId)
Swap->>SDK: computeAmountOut(...)
SDK-->>Swap: swap result + transaction
Swap-->>Index: swap result
alt executeSwap = true
Index->>Swap: result.execute({ sendAndConfirm: true })
Swap->>Conn: sendAndConfirm versioned transaction
Conn-->>Index: txId
else executeSwap = false
Index->>Swap: simulate(result.transaction)
Swap->>Conn: simulateTransaction(..., { sigVerify: false })
Conn-->>Index: simulation result
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (1)
src/index.ts (1)
26-28: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
maxRetriesis still public config, but this path no longer uses it.
src/swapConfig.tsstill exposesmaxRetries, and the README still documents it, yet the new execute path hard-codesresult.execute({ sendAndConfirm: true }). If the SDK has an equivalent retry option, wire it through here; otherwise removemaxRetriesfrom the config surface so users are not editing a no-op.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/index.ts` around lines 26 - 28, The swap execution path is ignoring the public maxRetries setting, since src/index.ts now calls result.execute with only sendAndConfirm enabled. Update the execute flow to either pass through the equivalent retry option from swapConfig.maxRetries if the SDK supports it, or remove maxRetries from the config surface entirely. Also align the swapConfig definition and README documentation so the exposed configuration matches the behavior of executeSwap and result.execute.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/config.ts`:
- Around line 9-10: Fail fast on missing or malformed environment values before
creating the SDK objects in config initialization: the top-level Connection and
Keypair.fromSecretKey calls currently use empty fallbacks and can crash
implicitly at import time. Add a small required-env helper in this module and
use it for RPC_URL and WALLET_PRIVATE_KEY so the startup path throws a clear
actionable error before constructing connection or owner, keeping the fix
localized around the connection and owner exports.
In `@src/index.ts`:
- Around line 31-32: The simulation path is forcing every swap transaction into
a VersionedTransaction, which breaks LEGACY support when result.transaction is a
legacy Transaction. Update RaydiumSwap.simulate to accept both Transaction and
VersionedTransaction and pass that union through to
connection.simulateTransaction, then remove the hard cast in src/index.ts by
using the raw result.transaction from the swap result.
In `@src/RaydiumSwap.ts`:
- Around line 59-62: The raw amount calculation in RaydiumSwap’s swap path uses
floating-point math on amountIn, which can silently round large values. Update
the code around the baseIn/mintIn selection and rawAmountIn construction to
avoid number * 10 ** decimals; instead convert amountIn to a string or change
the API to accept a string, then build the integer amount with BN-based
decimal-safe arithmetic using mintIn.decimals so the final rawAmountIn is exact.
- Around line 34-37: Validate the result of RaydiumAPI.fetchPoolById in
RaydiumSwap before using it: the current poolInfo assignment assumes data[0]
always exists and matches ApiV3PoolInfoStandardItem. Add a guard after
fetchPoolById (before getAmmPoolKeys/getRpcPoolInfo) to verify the returned
array is present, non-empty, and the pool is the expected AMM v4 type, then fail
fast with a clear error if not found. Keep the check close to the data, and only
proceed to poolInfo, poolKeys, and rpcData once validation passes.
---
Nitpick comments:
In `@src/index.ts`:
- Around line 26-28: The swap execution path is ignoring the public maxRetries
setting, since src/index.ts now calls result.execute with only sendAndConfirm
enabled. Update the execute flow to either pass through the equivalent retry
option from swapConfig.maxRetries if the SDK supports it, or remove maxRetries
from the config surface entirely. Also align the swapConfig definition and
README documentation so the exposed configuration matches the behavior of
executeSwap and result.execute.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 1c2a7f3c-90b5-4a74-b74d-89a17b1070b4
⛔ Files ignored due to path filters (1)
yarn.lockis excluded by!**/yarn.lock,!**/*.lock
📒 Files selected for processing (7)
README.mdpackage.jsonsrc/RaydiumSwap.tssrc/config.tssrc/index.tssrc/swapConfig.tssrc/trimMainnet.ts
💤 Files with no reviewable changes (1)
- src/trimMainnet.ts
| export const connection = new Connection(process.env.RPC_URL || '', 'confirmed') | ||
| export const owner: Keypair = Keypair.fromSecretKey(bs58.decode(process.env.WALLET_PRIVATE_KEY || '')) |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Fail fast on missing .env values before constructing the SDK objects.
These expressions run at import time, so an empty or malformed RPC_URL / WALLET_PRIVATE_KEY crashes the CLI before it can print an actionable setup error. A tiny getRequiredEnv() helper here would turn that into a clear startup failure instead of Connection('') / fromSecretKey(...) blowing up implicitly.
Suggested change
+const getRequiredEnv = (name: 'RPC_URL' | 'WALLET_PRIVATE_KEY') => {
+ const value = process.env[name]
+ if (!value) throw new Error(`Missing required environment variable: ${name}`)
+ return value
+}
+
-export const connection = new Connection(process.env.RPC_URL || '', 'confirmed')
-export const owner: Keypair = Keypair.fromSecretKey(bs58.decode(process.env.WALLET_PRIVATE_KEY || ''))
+export const connection = new Connection(getRequiredEnv('RPC_URL'), 'confirmed')
+export const owner: Keypair = Keypair.fromSecretKey(
+ bs58.decode(getRequiredEnv('WALLET_PRIVATE_KEY'))
+)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export const connection = new Connection(process.env.RPC_URL || '', 'confirmed') | |
| export const owner: Keypair = Keypair.fromSecretKey(bs58.decode(process.env.WALLET_PRIVATE_KEY || '')) | |
| const getRequiredEnv = (name: 'RPC_URL' | 'WALLET_PRIVATE_KEY') => { | |
| const value = process.env[name] | |
| if (!value) throw new Error(`Missing required environment variable: ${name}`) | |
| return value | |
| } | |
| export const connection = new Connection(getRequiredEnv('RPC_URL'), 'confirmed') | |
| export const owner: Keypair = Keypair.fromSecretKey( | |
| bs58.decode(getRequiredEnv('WALLET_PRIVATE_KEY')) | |
| ) |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/config.ts` around lines 9 - 10, Fail fast on missing or malformed
environment values before creating the SDK objects in config initialization: the
top-level Connection and Keypair.fromSecretKey calls currently use empty
fallbacks and can crash implicitly at import time. Add a small required-env
helper in this module and use it for RPC_URL and WALLET_PRIVATE_KEY so the
startup path throws a clear actionable error before constructing connection or
owner, keeping the fix localized around the connection and owner exports.
| // Simulate without sending. | ||
| const simRes = await raydiumSwap.simulate(result.transaction as VersionedTransaction); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major
Hard-coded VersionedTransaction cast breaks simulation for legacy transactions
While src/config.ts documents support for TxVersion.LEGACY, the simulation logic in src/index.ts unconditionally casts the transaction to VersionedTransaction. This fails if the SDK returns a legacy Transaction object when txVersion is LEGACY, causing a mismatch in connection.simulateTransaction.
Update src/RaydiumSwap.ts to accept both Transaction and VersionedTransaction in simulate(), and remove the hard cast in src/index.ts.
Implementation
In src/index.ts:
- Replace
result.transaction as VersionedTransactionwith the rawresult.transactionobject.
In src/RaydiumSwap.ts:
- import { VersionedTransaction } from '`@solana/web3.js`';
+ import { Transaction, VersionedTransaction } from '`@solana/web3.js`';- async simulate(transaction: VersionedTransaction) {
+ async simulate(transaction: Transaction | VersionedTransaction) {
return this.connection.simulateTransaction(transaction, { sigVerify: false });
}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/index.ts` around lines 31 - 32, The simulation path is forcing every swap
transaction into a VersionedTransaction, which breaks LEGACY support when
result.transaction is a legacy Transaction. Update RaydiumSwap.simulate to
accept both Transaction and VersionedTransaction and pass that union through to
connection.simulateTransaction, then remove the hard cast in src/index.ts by
using the raw result.transaction from the swap result.
| const data = await this.raydium.api.fetchPoolById({ ids: poolId }) | ||
| const poolInfo = data[0] as ApiV3PoolInfoStandardItem | ||
| const poolKeys = await this.raydium.liquidity.getAmmPoolKeys(poolId) | ||
| const rpcData = await this.raydium.liquidity.getRpcPoolInfo(poolId) |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major
🧩 Analysis chain
🏁 Script executed:
cat -n src/RaydiumSwap.ts | sed -n '1,80p'Repository: chainstacklabs/raydium-sdk-swap-example-typescript
Length of output: 3537
🏁 Script executed:
cd /sandbox && python3 - <<'PY'
import json
import subprocess
# Check imports and package.json to determine exact usage of fetchPoolById
result = subprocess.run(['cat', 'package.json'], capture_output=True, text=True)
pkg_json = json.loads(result.stdout)
deps = pkg_json.get('dependencies', {})
print("Dependency versions:")
print(json.dumps({k: v for k, v in deps.items() if 'raydium' in k}, indent=2))
# Verify if fetchPoolById logic requires a type guard in the SDK
# We will inspect the return type behavior by checking if there are any wrapper checks or if it's expected to return []
print("\nAnalyzing flow in RaydiumSwap.ts line 34-37:")
print("1. fetchPoolById returns 'data'")
print("2. poolInfo accesses data[0] directly (no type guard)")
print("3. If poolId is invalid, data is likely [] or null, causing 'data[0]' to be undefined")
print("4. Casting 'undefined' to ApiV3PoolInfoStandardItem bypasses TS checks but crashes at runtime on line 55 or 61")
PYRepository: chainstacklabs/raydium-sdk-swap-example-typescript
Length of output: 242
🏁 Script executed:
#!/bin/bash
# Check package.json for Raydium SDK version
cat package.json 2>/dev/null | grep -A 5 "raydium"
# Search for any existing validation logic or error handling around getPoolData
# or definition of the API return type
rg -A 10 -B 2 "fetchPoolById" src/Repository: chainstacklabs/raydium-sdk-swap-example-typescript
Length of output: 1367
🏁 Script executed:
```shell
node - <<'JS'
// Verify floating point precision for token amounts vs BN
const mintDecimals = 6; // Example: USDC
const humanAmount = 123.456789;
// Current code logic simulation:
const currentRaw = Math.floor(humanAmount * (10 ** mintDecimals));
const expectedRaw = 123456789;
console.log(`Human Amount: ${humanAmount}`);
console.log(`Decimals: ${mintDecimals}`);
console.log(`Current Logic: ${currentRaw}`);
console.log(`Expected: ${expectedRaw}`);
console.log(`Match: ${currentRaw === expectedRaw}`);
// Test edge case: High precision float
const badAmount = 1.111111; // Example of precision issues
const currentBad = Math.floor(badAmount * (10 ** 6));
// Expected: 1111111
console.log(`\nEdge case value: ${badAmount}`);
console.log(`Current Logic: ${currentBad}`);
console.log(`Expected (manual): 1111111`);
console.log(`Match: ${currentBad === 1111111}`);
JS
```Repository: chainstacklabs/raydium-sdk-swap-example-typescript
Length of output: 302
🏁 Script executed:
node - <<'JS'
const mintDecimals = 6;
const humanAmount = 123.456789;
const currentRaw = Math.floor(humanAmount * (10 ** mintDecimals));
const expectedRaw = 123456789;
console.log('Human Amount:', humanAmount);
console.log('Decimals:', mintDecimals);
console.log('Current Logic:', currentRaw);
console.log('Expected:', expectedRaw);
console.log('Match:', currentRaw === expectedRaw);
const badAmount = 1.111111;
const currentBad = Math.floor(badAmount * (10 ** 6));
console.log("\nEdge case value:", badAmount);
console.log('Current Logic:', currentBad);
console.log('Expected (manual): 1111111');
console.log('Match:', currentBad === 1111111);
JSRepository: chainstacklabs/raydium-sdk-swap-example-typescript
Length of output: 371
🏁 Script executed:
node - <<'JS'
const bigNum = 1.111111111111;
const res = Math.floor(bigNum * (10 ** 9));
console.log("Test large float:", res);
// Check if floating point error affects rounding
const floatIssue = 0.123456789 * (10 ** 9);
console.log("Float precise check:", floatIssue);
console.log("Floor:", Math.floor(floatIssue));
JSRepository: chainstacklabs/raydium-sdk-swap-example-typescript
Length of output: 265
Validate the fetched pool data before deserializing or using it.
fetchPoolById can return an empty array or undefined if the poolId does not match an existing AMM v4 pool. Accessing data[0] and casting it implies success unconditionally, leading to a runtime error (e.g., "Cannot read properties of undefined") or an opaque SDK failure later when poolInfo properties are accessed.
Add a validation check to ensure the pool was found and is of the expected type before proceeding.
Diff snippet showing the current risky line
const data = await this.raydium.api.fetchPoolById({ ids: poolId })
- const poolInfo = data[0] as ApiV3PoolInfoStandardItem
+ const poolInfo = data?.[0]
+ if (!poolInfo) {
+ throw new Error(`Pool not found for ID: ${poolId}`)
+ }
const poolKeys = await this.raydium.liquidity.getAmmPoolKeys(poolId)
const rpcData = await this.raydium.liquidity.getRpcPoolInfo(poolId)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const data = await this.raydium.api.fetchPoolById({ ids: poolId }) | |
| const poolInfo = data[0] as ApiV3PoolInfoStandardItem | |
| const poolKeys = await this.raydium.liquidity.getAmmPoolKeys(poolId) | |
| const rpcData = await this.raydium.liquidity.getRpcPoolInfo(poolId) | |
| const data = await this.raydium.api.fetchPoolById({ ids: poolId }) | |
| const poolInfo = data?.[0] | |
| if (!poolInfo) { | |
| throw new Error(`Pool not found for ID: ${poolId}`) | |
| } | |
| const poolKeys = await this.raydium.liquidity.getAmmPoolKeys(poolId) | |
| const rpcData = await this.raydium.liquidity.getRpcPoolInfo(poolId) |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/RaydiumSwap.ts` around lines 34 - 37, Validate the result of
RaydiumAPI.fetchPoolById in RaydiumSwap before using it: the current poolInfo
assignment assumes data[0] always exists and matches ApiV3PoolInfoStandardItem.
Add a guard after fetchPoolById (before getAmmPoolKeys/getRpcPoolInfo) to verify
the returned array is present, non-empty, and the pool is the expected AMM v4
type, then fail fast with a clear error if not found. Keep the check close to
the data, and only proceed to poolInfo, poolKeys, and rpcData once validation
passes.
| const baseIn = inputMint === poolInfo.mintA.address | ||
| const [mintIn, mintOut] = baseIn ? [poolInfo.mintA, poolInfo.mintB] : [poolInfo.mintB, poolInfo.mintA] | ||
| const rawAmountIn = new BN(Math.floor(amountIn * 10 ** mintIn.decimals)) | ||
|
|
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major
🧩 Analysis chain
🏁 Script executed:
cat -n src/RaydiumSwap.ts | sed -n '40,80p'Repository: chainstacklabs/raydium-sdk-swap-example-typescript
Length of output: 1971
🏁 Script executed:
# Check the typical range of decimals for Solana tokens (usually 1-9, maybe up to 18)
# and verify if number multiplication is safe for range like 1e9 * 1e9 = 1e18 (safe) vs 1e15 * 1e9
# JavaScript number max safe integer is 2^53 - 1 ≈ 9e15.
# If amountIn is in human units (e.g. 1000 USDC) and decimals is 6, we get 1e9.
# If amountIn is large (e.g. 1,000,000 USDC) and decimals is 9, we get 1e15.
# If amountIn is 1 million and decimals is 18 (e.g. USDT on other chains or specific wrapped tokens), 1e24 is NOT safe.
# Let's look for how the user calls this function to understand typical values.
grep -r "getSwapTransaction" --include="*.ts" --include="*.tsx" --include="*.js" --include="*.jsx" -A 2 -B 2Repository: chainstacklabs/raydium-sdk-swap-example-typescript
Length of output: 656
🏁 Script executed:
cat -n src/index.tsRepository: chainstacklabs/raydium-sdk-swap-example-typescript
Length of output: 1710
🏁 Script executed:
cat -n src/swapConfig.tsRepository: chainstacklabs/raydium-sdk-swap-example-typescript
Length of output: 920
🏁 Script executed:
# Check if any token inputs use very high decimals (e.g. 18) or large amounts that might exceed safe integer limits.
# JS Number.MAX_SAFE_INTEGER is approx 9e15.
# 10 ** 18 is the problem area.
# If amountIn is 0.001 (like in config) and decimals is 18, result is 10^15 (safe).
# If amountIn is 1000 and decimals is 18, result is 10^21 (unsafe).
# Most Solana tokens are 6-9 decimals. Some UWB (wrapped) tokens go to 18.
# The original comment warns about "deriving signed token amount through number math".
# Even for 9 decimals: 1,000,000 (amount) * 1,000,000,000 (10^9) = 10^15 (Safe).
# But 100,000,000 (amount) * 10^9 = 10^17 (Unsafe).
# So large amounts + high decimals = unsafe.
# The concern is valid for edge cases and robustness.
# Let's verify if BN is imported and used elsewhere correctly.
grep -n "import.*BN" src/RaydiumSwap.tsRepository: chainstacklabs/raydium-sdk-swap-example-typescript
Length of output: 213
Replace floating-point arithmetic to prevent precision loss
amountIn is typed as number, and the calculation amountIn * 10 ** mintIn.decimals relies on IEEE-754 double-precision floats. While the current example value (0.001) is safe, larger inputs (e.g., 1M+ tokens) combined with high decimal token counts (e.g., 18 decimals) exceed Number.MAX_SAFE_INTEGER, causing silent rounding errors in rawAmountIn. Convert the input to a string (or enforce a string argument type) and use a decimal-safe constructor (like new BN(amountIn.toString()).mul(new BN(10).pow(new BN(mintIn.decimals))) or spl-token utilities) to ensure exact integer arithmetic.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/RaydiumSwap.ts` around lines 59 - 62, The raw amount calculation in
RaydiumSwap’s swap path uses floating-point math on amountIn, which can silently
round large values. Update the code around the baseIn/mintIn selection and
rawAmountIn construction to avoid number * 10 ** decimals; instead convert
amountIn to a string or change the API to accept a string, then build the
integer amount with BN-based decimal-safe arithmetic using mintIn.decimals so
the final rawAmountIn is exact.
Problem
@raydium-io/raydium-sdk(v1) was archived in June 2025 and never supported CPMM/CLMM pools. This example was built entirely on v1 (Liquidity.makeSwapInstructionSimple,jsonInfo2PoolKeys, the ~500 MBmainnet.jsontrim), so it no longer reflects current Raydium.Changes — migrate to
@raydium-io/raydium-sdk-v2src/config.ts(new):initSdk()singleton (Raydium.load) with connection + owner from.env, modeled on the official raydium-sdk-V2-demo.RaydiumSwap.ts: rebuilt on v2 —api.fetchPoolById+liquidity.getAmmPoolKeys+liquidity.getRpcPoolInfo→liquidity.computeAmountOut→liquidity.swap→execute()/ simulate.mainnet.json: v2 fetches pool data from the API/RPC, so the ~500 MB download + trim is gone (removedtrimMainnet.ts+trimmed_mainnet.json). Pools are identified bypoolIdinswapConfig.ts(default SOL-USDC), with a configurable priority fee (computeUnitPriceMicroLamports).package.json: drop@raydium-io/raydium-sdk+@coral-xyz/anchor; add@raydium-io/raydium-sdk-v2,@solana/web3.js,bn.js. README +yarn.lockupdated.Verification
tsc --noEmitclean against the installed v2 SDK.computeAmountOut(0.001 SOL → ~0.068 USDC, matching Jupiter/Trade-API quotes) → build → simulate. Broadcast not run (no funds).Note
v2 is published as
0.2.x-alpha— pin the version you verify against. Companion docs: Solana: How to perform token swaps using Raydium (updated in parallel to the Trade API).Summary by CodeRabbit
New Features
Documentation
Bug Fixes