Last active
November 19, 2025 13:20
-
-
Save kalloc/f2a1b4b3053c45523b3f9fb315b4afa6 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { CpmmParsedRpcData, CurveCalculator, FeeOn } from '@raydium-io/raydium-sdk-v2' | |
| import { initSdk } from './src/config' | |
| import BN from 'bn.js' | |
| import { Connection, PublicKey } from '@solana/web3.js'; | |
| import { | |
| getMint, | |
| TOKEN_PROGRAM_ID, | |
| TOKEN_2022_PROGRAM_ID, | |
| } from '@solana/spl-token'; | |
| export type MintFullInfo = { | |
| mint: PublicKey; | |
| supply: bigint; | |
| supplyUi: number; | |
| decimals: number; | |
| owner: PublicKey | null; // mint authority | |
| symbol: string | null; | |
| name: string | null; | |
| uri: string | null; | |
| }; | |
| const METAPLEX_METADATA_PROGRAM_ID = new PublicKey( | |
| 'metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s', | |
| ); | |
| async function getMintWithAnyTokenProgram( | |
| connection: Connection, | |
| mint: PublicKey, | |
| ) { | |
| try { | |
| return await getMint(connection, mint, undefined, TOKEN_PROGRAM_ID); | |
| } catch { | |
| return await getMint(connection, mint, undefined, TOKEN_2022_PROGRAM_ID); | |
| } | |
| } | |
| function readMetaplexString( | |
| data: Buffer, | |
| offset: number, | |
| ): [string, number] { | |
| const len = data.readUInt32LE(offset); | |
| const start = offset + 4; | |
| const end = start + len; | |
| const raw = data.slice(start, end).toString('utf8'); | |
| return [raw.replace(/\0/g, ''), end]; | |
| } | |
| export async function getMintInfo( | |
| connection: Connection, | |
| mintStr: string, | |
| ): Promise<MintFullInfo> { | |
| const mint = new PublicKey(mintStr); | |
| const mintInfo = await getMintWithAnyTokenProgram(connection, mint); | |
| const decimals = mintInfo.decimals; | |
| const supply = BigInt(mintInfo.supply.toString()); | |
| const supplyUi = Number(supply) / 10 ** decimals; | |
| const owner = mintInfo.mintAuthority ?? null; | |
| const [metadataPda] = PublicKey.findProgramAddressSync( | |
| [ | |
| Buffer.from('metadata'), | |
| METAPLEX_METADATA_PROGRAM_ID.toBuffer(), | |
| mint.toBuffer(), | |
| ], | |
| METAPLEX_METADATA_PROGRAM_ID, | |
| ); | |
| let symbol: string | null = null; | |
| let name: string | null = null; | |
| let uri: string | null = null; | |
| const metadataAccountInfo = await connection.getAccountInfo(metadataPda); | |
| if (metadataAccountInfo) { | |
| const data = metadataAccountInfo.data as Buffer; | |
| // layout: | |
| // u8 key | |
| // pubkey update_authority | |
| // pubkey mint | |
| // string name | |
| // string symbol | |
| // string uri | |
| let offset = 0; | |
| offset += 1; // key | |
| offset += 32; // update_authority | |
| offset += 32; // mint | |
| [name, offset] = readMetaplexString(data, offset); | |
| [symbol, offset] = readMetaplexString(data, offset); | |
| [uri, offset] = readMetaplexString(data, offset); | |
| } | |
| return { | |
| mint, | |
| supply, | |
| supplyUi, | |
| decimals, | |
| owner, | |
| symbol, | |
| name, | |
| uri, | |
| }; | |
| } | |
| const swapBaseOut = async (rpcData: CpmmParsedRpcData, outputAmount: BN, baseIn: boolean = false, forceSkipFee = false) => { | |
| const swapResult = CurveCalculator.swapBaseOutput( | |
| outputAmount.gt(rpcData[baseIn ? 'quoteReserve' : 'baseReserve']) | |
| ? rpcData[baseIn ? 'quoteReserve' : 'baseReserve'].sub(new BN(1)) | |
| : outputAmount, | |
| baseIn ? rpcData.baseReserve : rpcData.quoteReserve, | |
| baseIn ? rpcData.quoteReserve : rpcData.baseReserve, | |
| forceSkipFee ? new BN(0) : rpcData.configInfo?.tradeFeeRate ?? new BN(0), | |
| forceSkipFee ? new BN(0) : rpcData.configInfo?.creatorFeeRate ?? new BN(0), | |
| forceSkipFee ? new BN(0) : rpcData.configInfo?.protocolFeeRate ?? new BN(0), | |
| forceSkipFee ? new BN(0) : rpcData.configInfo?.fundFeeRate ?? new BN(0), | |
| forceSkipFee ? false : (rpcData.feeOn === FeeOn.BothToken || rpcData.feeOn === FeeOn.OnlyTokenB) | |
| ) | |
| return swapResult | |
| } | |
| const swapBaseIn = async (rpcData: CpmmParsedRpcData, inputAmount: BN, baseIn: boolean = true, forceSkipFee = false) => { | |
| const swapResult = CurveCalculator.swapBaseInput( | |
| inputAmount.gt(rpcData[baseIn ? 'quoteReserve' : 'baseReserve']) | |
| ? rpcData[baseIn ? 'quoteReserve' : 'baseReserve'].sub(new BN(1)) | |
| : inputAmount, | |
| baseIn ? rpcData.baseReserve : rpcData.quoteReserve, | |
| baseIn ? rpcData.quoteReserve : rpcData.baseReserve, | |
| forceSkipFee ? new BN(0) : rpcData.configInfo?.tradeFeeRate ?? new BN(0), | |
| forceSkipFee ? new BN(0) : rpcData.configInfo?.creatorFeeRate ?? new BN(0), | |
| forceSkipFee ? new BN(0) : rpcData.configInfo?.protocolFeeRate ?? new BN(0), | |
| forceSkipFee ? new BN(0) : rpcData.configInfo?.fundFeeRate ?? new BN(0), | |
| forceSkipFee ? false : (rpcData.feeOn === FeeOn.BothToken || rpcData.feeOn === FeeOn.OnlyTokenB) | |
| ) | |
| return swapResult | |
| } | |
| function toPowerOfTen(num: number) { | |
| const digits = String(Math.round(num)).length; | |
| return 10 ** (digits - 1); | |
| } | |
| const fetchRpcPoolInfo = async (poolStr: string, amountBpsStr: string) => { | |
| const amountBps = 100000 / Number(amountBpsStr) | |
| const raydium = await initSdk() | |
| const res = await raydium.cpmm.getRpcPoolInfos([poolStr], true) | |
| const mintAInfo = await getMintInfo(raydium.connection, res[poolStr].mintA.toString()) | |
| const mintBInfo = await getMintInfo(raydium.connection, res[poolStr].mintB.toString()) | |
| const mintASymbol = mintAInfo.symbol | |
| const mintBSymbol = mintBInfo.symbol | |
| const poolInfo = res[poolStr] | |
| poolInfo.baseReserve = new BN("1000000000000000"); | |
| poolInfo.quoteReserve = new BN("5000000000000000000"); | |
| const swapFee = (poolInfo.configInfo?.tradeFeeRate?.toNumber() || 0 )+ | |
| (poolInfo.configInfo?.creatorFeeRate?.toNumber() || 0) + | |
| (poolInfo.configInfo?.protocolFeeRate?.toNumber() || 0) + | |
| (poolInfo.configInfo?.fundFeeRate?.toNumber() || 0); | |
| console.log(`${mintASymbol}-${mintBSymbol} pool id:`, poolStr) | |
| console.log(`\tprice:`, poolInfo.poolPrice) | |
| console.log(`\tbase:`, poolInfo.baseReserve.toString()) | |
| console.log(`\tquote:`, poolInfo.quoteReserve.toString()) | |
| console.log(`\tmintA: ${mintAInfo.symbol} - ${mintAInfo.mint.toString()} (decimals: ${mintAInfo.decimals})`) | |
| console.log(`\tmintB: ${mintBInfo.symbol} - ${mintBInfo.mint.toString()} (decimals: ${mintBInfo.decimals})`) | |
| console.log(`\tfeeOn:`, poolInfo.feeOn) | |
| console.log(`\tconfigInfo:`) | |
| if(poolInfo.configInfo) { | |
| console.log(`\t\tswapFee (sum):`, swapFee) | |
| console.log(`\t\ttradeFeeRate:`, poolInfo.configInfo.tradeFeeRate.toString()) | |
| console.log(`\t\tcreatorFeeRate:`, poolInfo.configInfo.creatorFeeRate.toString()) | |
| console.log(`\t\tprotocolFeeRate:`, poolInfo.configInfo.protocolFeeRate.toString()) | |
| console.log(`\t\tfundFeeRate:`, poolInfo.configInfo.fundFeeRate.toString()) | |
| } else { | |
| console.log('not config') | |
| } | |
| console.log(`Swap amount 1:${amountBpsStr} ratio`) | |
| const baseAmount = new BN(toPowerOfTen(poolInfo.baseReserve.div(new BN(amountBps).mul(new BN(10 ** mintAInfo.decimals))).toNumber())).mul(new BN(10 ** mintAInfo.decimals)) | |
| const quoteAmount = new BN(toPowerOfTen(poolInfo.quoteReserve.div(new BN(amountBps).mul(new BN(10 ** mintBInfo.decimals))).toNumber())).mul(new BN(10 ** mintBInfo.decimals)) | |
| console.log(``) | |
| { | |
| console.log(`swapBaseInput`) | |
| console.log(`\tbaseAmount:`, baseAmount.toString()) | |
| const swapResult = await swapBaseIn(poolInfo, baseAmount, true) | |
| console.log(`\tpreviousInputVaultAmount:`, poolInfo.baseReserve.toString()) | |
| console.log(`\tpreviousOutputVaultAmount:`, poolInfo.quoteReserve.toString()) | |
| console.log(`\tnewInputVaultAmount:`, swapResult.newInputVaultAmount.toString()) | |
| console.log(`\tnewOutputVaultAmount:`, swapResult.newOutputVaultAmount.toString()) | |
| console.log(`\tinputAmount:`, swapResult.inputAmount.toString()) | |
| console.log(`\toutputAmount:`, swapResult.outputAmount.toString()) | |
| console.log(`\ttradeFee:`, swapResult.tradeFee.toString()) | |
| console.log(`\tprotocolFee:`, swapResult.protocolFee.toString()) | |
| console.log(`\tfundFee:`, swapResult.fundFee.toString()) | |
| console.log(`\tcreatorFee:`, swapResult.creatorFee.toString()) | |
| } | |
| console.log(``) | |
| { | |
| console.log(`swapBaseOutput`) | |
| console.log(`\tquoteAmount:`, quoteAmount.toString()) | |
| const swapResult = await swapBaseOut(poolInfo, quoteAmount, true) | |
| swapResult.newOutputVaultAmount = poolInfo.quoteReserve.sub(swapResult.outputAmount); | |
| console.log(`\tpreviousInputVaultAmount:`, poolInfo.baseReserve.toString()) | |
| console.log(`\tpreviousOutputVaultAmount:`, poolInfo.quoteReserve.toString()) | |
| console.log(`\tnewInputVaultAmount:`, swapResult.newInputVaultAmount.toString()) | |
| console.log(`\tnewOutputVaultAmount:`, swapResult.newOutputVaultAmount.toString()) | |
| console.log(`\tinputAmount:`, swapResult.inputAmount.toString()) | |
| console.log(`\toutputAmount:`, swapResult.outputAmount.toString()) | |
| console.log(`\ttradeFee:`, swapResult.tradeFee.toString()) | |
| console.log(`\tprotocolFee:`, swapResult.protocolFee.toString()) | |
| console.log(`\tfundFee:`, swapResult.fundFee.toString()) | |
| console.log(`\tcreatorFee:`, swapResult.creatorFee.toString()) | |
| } | |
| console.log(``) | |
| { | |
| console.log(`swapBaseInput (Skip fee)`) | |
| console.log(`\tbaseAmount:`, baseAmount.toString()) | |
| const swapResult = await swapBaseIn(poolInfo, baseAmount, true, true) | |
| console.log(`\tpreviousInputVaultAmount:`, poolInfo.baseReserve.toString()) | |
| console.log(`\tpreviousOutputVaultAmount:`, poolInfo.quoteReserve.toString()) | |
| console.log(`\tnewInputVaultAmount:`, swapResult.newInputVaultAmount.toString()) | |
| console.log(`\tnewOutputVaultAmount:`, swapResult.newOutputVaultAmount.toString()) | |
| console.log(`\tinputAmount:`, swapResult.inputAmount.toString()) | |
| console.log(`\toutputAmount:`, swapResult.outputAmount.toString()) | |
| console.log(`\ttradeFee:`, swapResult.tradeFee.toString()) | |
| console.log(`\tprotocolFee:`, swapResult.protocolFee.toString()) | |
| console.log(`\tfundFee:`, swapResult.fundFee.toString()) | |
| console.log(`\tcreatorFee:`, swapResult.creatorFee.toString()) | |
| } | |
| console.log(``) | |
| { | |
| console.log(`swapBaseOutput (Skip fee)`) | |
| console.log(`\tquoteAmount:`, quoteAmount.toString()) | |
| const swapResult = await swapBaseOut(poolInfo, quoteAmount, true, true) | |
| swapResult.newOutputVaultAmount = poolInfo.quoteReserve.sub(swapResult.outputAmount); | |
| console.log(`\tpreviousInputVaultAmount:`, poolInfo.baseReserve.toString()) | |
| console.log(`\tpreviousOutputVaultAmount:`, poolInfo.quoteReserve.toString()) | |
| console.log(`\tnewInputVaultAmount:`, swapResult.newInputVaultAmount.toString()) | |
| console.log(`\tnewOutputVaultAmount:`, swapResult.newOutputVaultAmount.toString()) | |
| console.log(`\tinputAmount:`, swapResult.inputAmount.toString()) | |
| console.log(`\toutputAmount:`, swapResult.outputAmount.toString()) | |
| console.log(`\ttradeFee:`, swapResult.tradeFee.toString()) | |
| console.log(`\tprotocolFee:`, swapResult.protocolFee.toString()) | |
| console.log(`\tfundFee:`, swapResult.fundFee.toString()) | |
| console.log(`\tcreatorFee:`, swapResult.creatorFee.toString()) | |
| } | |
| } | |
| const poolId = process.argv[2]; | |
| const amountBps = process.argv[3] || '10'; | |
| if (poolId) { | |
| fetchRpcPoolInfo(poolId, amountBps); | |
| } else { | |
| console.error("Please provide a poolId as a command-line argument."); | |
| process.exit(1); | |
| } | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment