Skip to content

Instantly share code, notes, and snippets.

@kalloc
Last active November 19, 2025 13:20
Show Gist options
  • Select an option

  • Save kalloc/f2a1b4b3053c45523b3f9fb315b4afa6 to your computer and use it in GitHub Desktop.

Select an option

Save kalloc/f2a1b4b3053c45523b3f9fb315b4afa6 to your computer and use it in GitHub Desktop.
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