Skip to content

Migrate to Raydium SDK v2 (v1 is end-of-life)#28

Merged
akegaviar merged 1 commit into
mainfrom
migrate/raydium-sdk-v2
Jun 25, 2026
Merged

Migrate to Raydium SDK v2 (v1 is end-of-life)#28
akegaviar merged 1 commit into
mainfrom
migrate/raydium-sdk-v2

Conversation

@akegaviar

@akegaviar akegaviar commented Jun 25, 2026

Copy link
Copy Markdown
Member

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 MB mainnet.json trim), so it no longer reflects current Raydium.

Changes — migrate to @raydium-io/raydium-sdk-v2

  • src/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.getRpcPoolInfoliquidity.computeAmountOutliquidity.swapexecute() / simulate.
  • No more mainnet.json: v2 fetches pool data from the API/RPC, so the ~500 MB download + trim is gone (removed trimMainnet.ts + trimmed_mainnet.json). Pools are 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 + yarn.lock updated.

Verification

  • tsc --noEmit clean against the installed v2 SDK.
  • Ran the full flow live: init → fetch pool → 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

    • Upgraded the swap example to a simpler Raydium SDK v2 flow with streamlined setup and configuration.
    • Added support for swap simulation or execution from the same configuration.
    • Updated transaction handling to use versioned transactions and configurable priority fees.
  • Documentation

    • Revised the README with a new getting-started guide, updated repository link, and refreshed usage examples.
  • Bug Fixes

    • Removed the need for a large local pool file and legacy configuration fields, reducing setup complexity.

@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).
@akegaviar akegaviar self-assigned this Jun 25, 2026
@coderabbitai

coderabbitai Bot commented Jun 25, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Migrates 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.

Changes

Raydium SDK v2 migration

Layer / File(s) Summary
SDK v2 setup and config
package.json, src/config.ts, src/swapConfig.ts, README.md
Dependencies, env-based SDK initialization, the swap config schema, and README setup/usage text are updated for the Raydium v2 flow.
Pool data and swap build
src/RaydiumSwap.ts
The swap class now initializes the SDK, fetches pool data, computes slippage-adjusted output amounts, and builds or simulates the v2 swap transaction.
CLI swap execution
src/index.ts
The entrypoint now constructs the helper without direct RPC/key arguments, uses the new config fields, and branches between execute and simulate handling on the swap result.

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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I hopped through v2 by moonlit code,
and left the giant JSON road.
Pools now bloom from config's light,
simulate or send — both feel just right.
Thump-thump, the burrow's all aglow!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and accurately summarizes the main change: migrating the example from Raydium SDK v1 to v2.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch migrate/raydium-sdk-v2

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (1)
src/index.ts (1)

26-28: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

maxRetries is still public config, but this path no longer uses it.

src/swapConfig.ts still exposes maxRetries, and the README still documents it, yet the new execute path hard-codes result.execute({ sendAndConfirm: true }). If the SDK has an equivalent retry option, wire it through here; otherwise remove maxRetries from 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

📥 Commits

Reviewing files that changed from the base of the PR and between d9587ca and 951940b.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (7)
  • README.md
  • package.json
  • src/RaydiumSwap.ts
  • src/config.ts
  • src/index.ts
  • src/swapConfig.ts
  • src/trimMainnet.ts
💤 Files with no reviewable changes (1)
  • src/trimMainnet.ts

Comment thread src/config.ts
Comment on lines +9 to +10
export const connection = new Connection(process.env.RPC_URL || '', 'confirmed')
export const owner: Keypair = Keypair.fromSecretKey(bs58.decode(process.env.WALLET_PRIVATE_KEY || ''))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 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.

Suggested change
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.

Comment thread src/index.ts
Comment on lines +31 to +32
// Simulate without sending.
const simRes = await raydiumSwap.simulate(result.transaction as VersionedTransaction);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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 VersionedTransaction with the raw result.transaction object.

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.

Comment thread src/RaydiumSwap.ts
Comment on lines +34 to +37
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)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 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")
PY

Repository: 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);
JS

Repository: 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));
JS

Repository: 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.

Suggested change
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.

Comment thread src/RaydiumSwap.ts
Comment on lines +59 to +62
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))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ 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 2

Repository: chainstacklabs/raydium-sdk-swap-example-typescript

Length of output: 656


🏁 Script executed:

cat -n src/index.ts

Repository: chainstacklabs/raydium-sdk-swap-example-typescript

Length of output: 1710


🏁 Script executed:

cat -n src/swapConfig.ts

Repository: 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.ts

Repository: 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.

@akegaviar akegaviar merged commit dad8ec2 into main Jun 25, 2026
5 checks passed
@akegaviar akegaviar deleted the migrate/raydium-sdk-v2 branch June 25, 2026 05:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant