feat: add global MEV protection and refactor TradeConfig to builder pattern

- Add `mev_protection: bool` to `TradeConfig` and `InfrastructureConfig` (default: false)
  - Astralane QUIC: switches to port 9000 (MEV-protected endpoint) when enabled
  - BlockRazor HTTP: uses `mode=sandwichMitigation` query param when enabled
  - BlockRazor gRPC: uses `mode=sandwichMitigation` when enabled
- Add `SWQOS_ENDPOINTS_ASTRALANE_QUIC_MEV` constants (port 9000) to `constants/swqos.rs`
- Fix `astralane_quic.rs` IP candidates to use the actual port from the address (supports both 7000 and 9000)
- Refactor `TradeConfig` to builder pattern via `TradeConfig::builder()`
  - Introduce `TradeConfigBuilder` with all optional fields and clear defaults
  - `TradeConfig::new()` kept as a shortcut (calls `builder().build()`) for backward compatibility
  - Remove old `with_wsol_ata_config` / `with_check_min_tip` / `with_swqos_cores_from_end` / `with_mev_protection` chain methods
- Update all 16 examples to use `TradeConfig::builder()` with commented-out options so users can discover all available settings at a glance
- Update README.md and README_CN.md code snippets to use builder pattern

🤖 Generated with [Qoder][https://qoder.com]
This commit is contained in:
0xfnzero
2026-04-08 02:13:37 +08:00
parent 35bfa93516
commit 971ef41fad
44 changed files with 6783 additions and 74 deletions
+7
View File
@@ -0,0 +1,7 @@
/**
* Sol Trade SDK - Node.js
*
* Production-grade instruction builders for Solana DEX protocols.
*/
export * from "./instruction";
@@ -0,0 +1,646 @@
/**
* Bonk Protocol Instruction Builder
*
* Production-grade instruction builder for Bonk AMM protocol.
* Supports buy and sell operations with WSOL and USD1 pools.
*/
import {
PublicKey,
Keypair,
AccountMeta,
Instruction,
SystemProgram,
} from "@solana/web3.js";
import {
getAssociatedTokenAddressSync,
createAssociatedTokenAccountInstruction,
TOKEN_PROGRAM_ID,
createCloseAccountInstruction,
NATIVE_MINT,
createSyncNativeInstruction,
createAccount,
getAccount,
getMint,
Account as TokenAccount,
} from "@solana/spl-token";
// ============================================
// Program IDs and Constants
// ============================================
/** Bonk program ID */
export const BONK_PROGRAM_ID = new PublicKey(
"LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj"
);
/** Authority */
export const AUTHORITY = new PublicKey(
"WLhv2UAZm6z4KyaaELi5pjdbJh6RESMva1Rnn8pJVVh"
);
/** Global Config */
export const GLOBAL_CONFIG = new PublicKey(
"6s1xP3hpbAfFoNtUNF8mfHsjr2Bd97JxFJRWLbL6aHuX"
);
/** USD1 Global Config */
export const USD1_GLOBAL_CONFIG = new PublicKey(
"EPiZbnrThjyLnoQ6QQzkxeFqyL5uyg9RzNHHAudUPxBz"
);
/** Event Authority */
export const EVENT_AUTHORITY = new PublicKey(
"2DPAtwB8L12vrMRExbLuyGnC7n2J5LNoZQSejeQGpwkr"
);
/** WSOL Token Account (mint) */
export const WSOL_TOKEN_ACCOUNT = new PublicKey(
"So11111111111111111111111111111111111111112"
);
/** USD1 Token Account (mint) */
export const USD1_TOKEN_ACCOUNT = new PublicKey(
"USD1ttGY1N17NEEHLmELoaybftRBUSErhqYiQzvEmuB"
);
/** Fee rates */
export const PLATFORM_FEE_RATE = 100n; // 1%
export const PROTOCOL_FEE_RATE = 25n; // 0.25%
export const SHARE_FEE_RATE = 0n; // 0%
// ============================================
// Discriminators
// ============================================
/** Buy exact in instruction discriminator */
export const BUY_EXACT_IN_DISCRIMINATOR: Buffer = Buffer.from([
250, 234, 13, 123, 213, 156, 19, 236,
]);
/** Sell exact in instruction discriminator */
export const SELL_EXACT_IN_DISCRIMINATOR: Buffer = Buffer.from([
149, 39, 222, 155, 211, 124, 152, 26,
]);
// ============================================
// Seeds
// ============================================
export const POOL_SEED = Buffer.from("pool");
export const POOL_VAULT_SEED = Buffer.from("pool_vault");
// ============================================
// PDA Derivation Functions
// ============================================
/**
* Derive the pool PDA for given base and quote mints
*/
export function getPoolPda(baseMint: PublicKey, quoteMint: PublicKey): PublicKey {
const [pda] = PublicKey.findProgramAddressSync(
[POOL_SEED, baseMint.toBuffer(), quoteMint.toBuffer()],
BONK_PROGRAM_ID
);
return pda;
}
/**
* Derive the vault PDA for a pool and mint
*/
export function getVaultPda(poolState: PublicKey, mint: PublicKey): PublicKey {
const [pda] = PublicKey.findProgramAddressSync(
[POOL_VAULT_SEED, poolState.toBuffer(), mint.toBuffer()],
BONK_PROGRAM_ID
);
return pda;
}
/**
* Derive platform associated account
*/
export function getPlatformAssociatedAccount(platformConfig: PublicKey): PublicKey {
const [pda] = PublicKey.findProgramAddressSync(
[platformConfig.toBuffer(), WSOL_TOKEN_ACCOUNT.toBuffer()],
BONK_PROGRAM_ID
);
return pda;
}
/**
* Derive creator associated account
*/
export function getCreatorAssociatedAccount(creator: PublicKey): PublicKey {
const [pda] = PublicKey.findProgramAddressSync(
[creator.toBuffer(), WSOL_TOKEN_ACCOUNT.toBuffer()],
BONK_PROGRAM_ID
);
return pda;
}
// ============================================
// Helper Functions
// ============================================
/**
* Calculate amount in net after fees
*/
export function getAmountInNet(
amountIn: bigint,
protocolFeeRate: bigint,
platformFeeRate: bigint,
shareFeeRate: bigint
): bigint {
const protocolFee = (amountIn * protocolFeeRate) / 10000n;
const platformFee = (amountIn * platformFeeRate) / 10000n;
const shareFee = (amountIn * shareFeeRate) / 10000n;
return amountIn - protocolFee - platformFee - shareFee;
}
/**
* Calculate amount out for a swap
*/
export function getAmountOut(
amountIn: bigint,
protocolFeeRate: bigint,
platformFeeRate: bigint,
shareFeeRate: bigint,
virtualBase: bigint,
virtualQuote: bigint,
realBase: bigint,
realQuote: bigint,
slippageBasisPoints: bigint
): bigint {
const amountInNet = getAmountInNet(
amountIn,
protocolFeeRate,
platformFeeRate,
shareFeeRate
);
const inputReserve = virtualQuote + realQuote;
const outputReserve = virtualBase - realBase;
const numerator = amountInNet * outputReserve;
const denominator = inputReserve + amountInNet;
let amountOut = numerator / denominator;
// Apply slippage
amountOut = amountOut - (amountOut * slippageBasisPoints) / 10000n;
return amountOut;
}
/**
* Calculate amount in required for a desired output
*/
export function getAmountIn(
amountOut: bigint,
protocolFeeRate: bigint,
platformFeeRate: bigint,
shareFeeRate: bigint,
virtualBase: bigint,
virtualQuote: bigint,
realBase: bigint,
realQuote: bigint,
slippageBasisPoints: bigint
): bigint {
// Consider slippage, actual required output amount is higher
const amountOutWithSlippage = (amountOut * 10000n) / (10000n - slippageBasisPoints);
const inputReserve = virtualQuote + realQuote;
const outputReserve = virtualBase - realBase;
// Reverse calculate using AMM formula
const numerator = amountOutWithSlippage * inputReserve;
const denominator = outputReserve - amountOutWithSlippage;
const amountInNet = numerator / denominator;
// Calculate total fee rate
const totalFeeRate = protocolFeeRate + platformFeeRate + shareFeeRate;
const amountIn = (amountInNet * 10000n) / (10000n - totalFeeRate);
return amountIn;
}
// ============================================
// WSOL Helper Functions
// ============================================
/**
* Create instructions to wrap SOL into WSOL
*/
export function createWsolInstructions(
payer: PublicKey,
amount: bigint
): Instruction[] {
const instructions: Instruction[] = [];
const wsolAta = getAssociatedTokenAddressSync(NATIVE_MINT, payer, true);
// Create WSOL ATA if needed (using create account for simplicity)
// In production, use createAssociatedTokenAccountInstruction
// Sync native (wrap SOL)
instructions.push(
createSyncNativeInstruction(wsolAta)
);
return instructions;
}
/**
* Create instruction to close WSOL ATA and unwrap to SOL
*/
export function createCloseWsolInstruction(payer: PublicKey): Instruction {
const wsolAta = getAssociatedTokenAddressSync(NATIVE_MINT, payer, true);
return createCloseAccountInstruction(wsolAta, payer, payer, [], TOKEN_PROGRAM_ID);
}
/**
* Create WSOL ATA instruction
*/
export function createWsolAtaInstruction(payer: PublicKey): Instruction {
const wsolAta = getAssociatedTokenAddressSync(NATIVE_MINT, payer, true);
return createAssociatedTokenAccountInstruction(
payer,
wsolAta,
payer,
NATIVE_MINT,
TOKEN_PROGRAM_ID
);
}
// ============================================
// Types
// ============================================
export interface BonkParams {
poolState?: PublicKey;
globalConfig: PublicKey;
platformConfig: PublicKey;
platformAssociatedAccount: PublicKey;
creatorAssociatedAccount: PublicKey;
baseVault?: PublicKey;
quoteVault?: PublicKey;
mintTokenProgram: PublicKey;
virtualBase: bigint;
virtualQuote: bigint;
realBase: bigint;
realQuote: bigint;
}
export interface BuildBuyInstructionsParams {
payer: Keypair | PublicKey;
outputMint: PublicKey;
inputAmount: bigint;
slippageBasisPoints?: bigint;
fixedOutputAmount?: bigint;
createInputMintAta?: boolean;
createOutputMintAta?: boolean;
closeInputMintAta?: boolean;
protocolParams: BonkParams;
}
export interface BuildSellInstructionsParams {
payer: Keypair | PublicKey;
inputMint: PublicKey;
inputAmount: bigint;
slippageBasisPoints?: bigint;
fixedOutputAmount?: bigint;
createOutputMintAta?: boolean;
closeOutputMintAta?: boolean;
closeInputMintAta?: boolean;
protocolParams: BonkParams;
}
// ============================================
// Instruction Builders
// ============================================
/**
* Build buy instructions for Bonk protocol
*/
export function buildBuyInstructions(
params: BuildBuyInstructionsParams
): Instruction[] {
const {
payer,
outputMint,
inputAmount,
slippageBasisPoints = 1000n,
fixedOutputAmount,
createInputMintAta = true,
createOutputMintAta = true,
closeInputMintAta = false,
protocolParams,
} = params;
if (inputAmount === 0n) {
throw new Error("Amount cannot be zero");
}
const payerPubkey = payer instanceof Keypair ? payer.publicKey : payer;
const instructions: Instruction[] = [];
const {
globalConfig,
platformConfig,
platformAssociatedAccount,
creatorAssociatedAccount,
baseVault,
quoteVault,
mintTokenProgram,
virtualBase,
virtualQuote,
realBase,
realQuote,
} = protocolParams;
// Check if USD1 pool
const isUsd1Pool = globalConfig.equals(USD1_GLOBAL_CONFIG);
// Determine quote token mint
const quoteTokenMint = isUsd1Pool ? USD1_TOKEN_ACCOUNT : WSOL_TOKEN_ACCOUNT;
// Derive pool state
const poolState = protocolParams.poolState && !protocolParams.poolState.equals(PublicKey.default)
? protocolParams.poolState
: getPoolPda(outputMint, quoteTokenMint);
// Calculate minimum amount out
const minimumAmountOut = fixedOutputAmount
? fixedOutputAmount
: getAmountOut(
inputAmount,
PROTOCOL_FEE_RATE,
PLATFORM_FEE_RATE,
SHARE_FEE_RATE,
virtualBase,
virtualQuote,
realBase,
realQuote,
slippageBasisPoints
);
// Derive user token accounts
const userBaseTokenAccount = getAssociatedTokenAddressSync(
outputMint,
payerPubkey,
true,
mintTokenProgram
);
const userQuoteTokenAccount = getAssociatedTokenAddressSync(
quoteTokenMint,
payerPubkey,
true,
TOKEN_PROGRAM_ID
);
// Derive vault accounts
const baseVaultAccount = baseVault && !baseVault.equals(PublicKey.default)
? baseVault
: getVaultPda(poolState, outputMint);
const quoteVaultAccount = quoteVault && !quoteVault.equals(PublicKey.default)
? quoteVault
: getVaultPda(poolState, quoteTokenMint);
// Handle WSOL wrapping for non-USD1 pools
if (createInputMintAta && !isUsd1Pool) {
const wsolAta = getAssociatedTokenAddressSync(NATIVE_MINT, payerPubkey, true);
instructions.push(
createAssociatedTokenAccountInstruction(
payerPubkey,
wsolAta,
payerPubkey,
NATIVE_MINT,
TOKEN_PROGRAM_ID
)
);
instructions.push(createSyncNativeInstruction(wsolAta));
}
// Create output mint ATA if needed
if (createOutputMintAta) {
instructions.push(
createAssociatedTokenAccountInstruction(
payerPubkey,
userBaseTokenAccount,
payerPubkey,
outputMint,
mintTokenProgram
)
);
}
// Build instruction data
const shareFeeRate = 0n;
const data = Buffer.alloc(32);
BUY_EXACT_IN_DISCRIMINATOR.copy(data, 0);
data.writeBigUInt64LE(inputAmount, 8);
data.writeBigUInt64LE(minimumAmountOut, 16);
data.writeBigUInt64LE(shareFeeRate, 24);
// Build accounts
const accounts: AccountMeta[] = [
{ pubkey: payerPubkey, isSigner: true, isWritable: true },
{ pubkey: AUTHORITY, isSigner: false, isWritable: false },
{ pubkey: globalConfig, isSigner: false, isWritable: false },
{ pubkey: platformConfig, isSigner: false, isWritable: false },
{ pubkey: poolState, isSigner: false, isWritable: true },
{ pubkey: userBaseTokenAccount, isSigner: false, isWritable: true },
{ pubkey: userQuoteTokenAccount, isSigner: false, isWritable: true },
{ pubkey: baseVaultAccount, isSigner: false, isWritable: true },
{ pubkey: quoteVaultAccount, isSigner: false, isWritable: true },
{ pubkey: outputMint, isSigner: false, isWritable: false },
{ pubkey: quoteTokenMint, isSigner: false, isWritable: false },
{ pubkey: mintTokenProgram, isSigner: false, isWritable: false },
{ pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
{ pubkey: EVENT_AUTHORITY, isSigner: false, isWritable: false },
{ pubkey: BONK_PROGRAM_ID, isSigner: false, isWritable: false },
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
{ pubkey: platformAssociatedAccount, isSigner: false, isWritable: true },
{ pubkey: creatorAssociatedAccount, isSigner: false, isWritable: true },
];
instructions.push(
new Instruction({
keys: accounts,
programId: BONK_PROGRAM_ID,
data,
})
);
// Close WSOL ATA if requested
if (closeInputMintAta && !isUsd1Pool) {
const wsolAta = getAssociatedTokenAddressSync(NATIVE_MINT, payerPubkey, true);
instructions.push(
createCloseAccountInstruction(wsolAta, payerPubkey, payerPubkey, [], TOKEN_PROGRAM_ID)
);
}
return instructions;
}
/**
* Build sell instructions for Bonk protocol
*/
export function buildSellInstructions(
params: BuildSellInstructionsParams
): Instruction[] {
const {
payer,
inputMint,
inputAmount,
slippageBasisPoints = 1000n,
fixedOutputAmount,
createOutputMintAta = true,
closeOutputMintAta = false,
closeInputMintAta = false,
protocolParams,
} = params;
if (inputAmount === 0n) {
throw new Error("Amount cannot be zero");
}
const payerPubkey = payer instanceof Keypair ? payer.publicKey : payer;
const instructions: Instruction[] = [];
const {
globalConfig,
platformConfig,
platformAssociatedAccount,
creatorAssociatedAccount,
baseVault,
quoteVault,
mintTokenProgram,
virtualBase,
virtualQuote,
realBase,
realQuote,
} = protocolParams;
// Check if USD1 pool
const isUsd1Pool = globalConfig.equals(USD1_GLOBAL_CONFIG);
// Determine quote token mint
const quoteTokenMint = isUsd1Pool ? USD1_TOKEN_ACCOUNT : WSOL_TOKEN_ACCOUNT;
// Derive pool state
const poolState = protocolParams.poolState && !protocolParams.poolState.equals(PublicKey.default)
? protocolParams.poolState
: getPoolPda(inputMint, quoteTokenMint);
// Calculate minimum amount out
const minimumAmountOut = fixedOutputAmount
? fixedOutputAmount
: getAmountOut(
inputAmount,
PROTOCOL_FEE_RATE,
PLATFORM_FEE_RATE,
SHARE_FEE_RATE,
virtualBase,
virtualQuote,
realBase,
realQuote,
slippageBasisPoints
);
// Derive user token accounts
const userBaseTokenAccount = getAssociatedTokenAddressSync(
inputMint,
payerPubkey,
true,
mintTokenProgram
);
const userQuoteTokenAccount = getAssociatedTokenAddressSync(
quoteTokenMint,
payerPubkey,
true,
TOKEN_PROGRAM_ID
);
// Derive vault accounts
const baseVaultAccount = baseVault && !baseVault.equals(PublicKey.default)
? baseVault
: getVaultPda(poolState, inputMint);
const quoteVaultAccount = quoteVault && !quoteVault.equals(PublicKey.default)
? quoteVault
: getVaultPda(poolState, quoteTokenMint);
// Create WSOL ATA for receiving SOL if needed
if (createOutputMintAta && !isUsd1Pool) {
const wsolAta = getAssociatedTokenAddressSync(NATIVE_MINT, payerPubkey, true);
instructions.push(
createAssociatedTokenAccountInstruction(
payerPubkey,
wsolAta,
payerPubkey,
NATIVE_MINT,
TOKEN_PROGRAM_ID
)
);
}
// Build instruction data
const shareFeeRate = 0n;
const data = Buffer.alloc(32);
SELL_EXACT_IN_DISCRIMINATOR.copy(data, 0);
data.writeBigUInt64LE(inputAmount, 8);
data.writeBigUInt64LE(minimumAmountOut, 16);
data.writeBigUInt64LE(shareFeeRate, 24);
// Build accounts
const accounts: AccountMeta[] = [
{ pubkey: payerPubkey, isSigner: true, isWritable: true },
{ pubkey: AUTHORITY, isSigner: false, isWritable: false },
{ pubkey: globalConfig, isSigner: false, isWritable: false },
{ pubkey: platformConfig, isSigner: false, isWritable: false },
{ pubkey: poolState, isSigner: false, isWritable: true },
{ pubkey: userBaseTokenAccount, isSigner: false, isWritable: true },
{ pubkey: userQuoteTokenAccount, isSigner: false, isWritable: true },
{ pubkey: baseVaultAccount, isSigner: false, isWritable: true },
{ pubkey: quoteVaultAccount, isSigner: false, isWritable: true },
{ pubkey: inputMint, isSigner: false, isWritable: false },
{ pubkey: quoteTokenMint, isSigner: false, isWritable: false },
{ pubkey: mintTokenProgram, isSigner: false, isWritable: false },
{ pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
{ pubkey: EVENT_AUTHORITY, isSigner: false, isWritable: false },
{ pubkey: BONK_PROGRAM_ID, isSigner: false, isWritable: false },
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
{ pubkey: platformAssociatedAccount, isSigner: false, isWritable: true },
{ pubkey: creatorAssociatedAccount, isSigner: false, isWritable: true },
];
instructions.push(
new Instruction({
keys: accounts,
programId: BONK_PROGRAM_ID,
data,
})
);
// Close WSOL ATA if requested
if (closeOutputMintAta && !isUsd1Pool) {
const wsolAta = getAssociatedTokenAddressSync(NATIVE_MINT, payerPubkey, true);
instructions.push(
createCloseAccountInstruction(wsolAta, payerPubkey, payerPubkey, [], TOKEN_PROGRAM_ID)
);
}
// Close input token ATA if requested
if (closeInputMintAta) {
instructions.push(
createCloseAccountInstruction(
userBaseTokenAccount,
payerPubkey,
payerPubkey,
[],
mintTokenProgram
)
);
}
return instructions;
}
@@ -0,0 +1,21 @@
/**
* Sol Trade SDK - Instruction Builders
*
* Production-grade instruction builders for Solana DEX protocols.
* Supports: PumpFun, Bonk, Raydium CPMM, Raydium AMM V4, Meteora DAMM V2
*/
// PumpFun Protocol
export * from "./pumpfun_builder";
// Bonk Protocol
export * from "./bonk_builder";
// Raydium CPMM Protocol
export * from "./raydium_cpmm_builder";
// Raydium AMM V4 Protocol
export * from "./raydium_amm_v4_builder";
// Meteora DAMM V2 Protocol
export * from "./meteora_damm_v2_builder";
@@ -0,0 +1,437 @@
/**
* Meteora DAMM V2 Protocol Instruction Builder
*
* Production-grade instruction builder for Meteora DAMM V2 protocol.
* Supports swap operations with WSOL and USDC pools.
*/
import {
PublicKey,
Keypair,
AccountMeta,
Instruction,
SystemProgram,
} from "@solana/web3.js";
import {
getAssociatedTokenAddressSync,
createAssociatedTokenAccountInstruction,
TOKEN_PROGRAM_ID,
createCloseAccountInstruction,
NATIVE_MINT,
createSyncNativeInstruction,
} from "@solana/spl-token";
// ============================================
// Program IDs and Constants
// ============================================
/** Meteora DAMM V2 program ID */
export const METEORA_DAMM_V2_PROGRAM_ID = new PublicKey(
"cpamdpZCGKUy5JxQXB4dcpGPiikHawvSWAd6mEn1sGG"
);
/** Authority */
export const AUTHORITY = new PublicKey(
"HLnpSz9h2S4hiLQ43rnSD9XkcUThA7B8hQMKmDaiTLcC"
);
/** WSOL Token Account (mint) */
export const WSOL_TOKEN_ACCOUNT = new PublicKey(
"So11111111111111111111111111111111111111112"
);
/** USDC Token Account (mint) */
export const USDC_TOKEN_ACCOUNT = new PublicKey(
"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
);
// ============================================
// Discriminators
// ============================================
/** Swap instruction discriminator */
export const SWAP_DISCRIMINATOR: Buffer = Buffer.from([
248, 198, 158, 145, 225, 117, 135, 200,
]);
// ============================================
// Seeds
// ============================================
export const EVENT_AUTHORITY_SEED = Buffer.from("__event_authority");
// ============================================
// PDA Derivation Functions
// ============================================
/**
* Derive the event authority PDA
*/
export function getEventAuthorityPda(): PublicKey {
const [pda] = PublicKey.findProgramAddressSync(
[EVENT_AUTHORITY_SEED],
METEORA_DAMM_V2_PROGRAM_ID
);
return pda;
}
// ============================================
// Helper Functions
// ============================================
/**
* Create instructions to wrap SOL into WSOL
*/
export function createWsolInstructions(
payer: PublicKey,
amount: bigint
): Instruction[] {
const instructions: Instruction[] = [];
const wsolAta = getAssociatedTokenAddressSync(NATIVE_MINT, payer, true);
// Sync native (wrap SOL)
instructions.push(createSyncNativeInstruction(wsolAta));
return instructions;
}
/**
* Create instruction to close WSOL ATA and unwrap to SOL
*/
export function createCloseWsolInstruction(payer: PublicKey): Instruction {
const wsolAta = getAssociatedTokenAddressSync(NATIVE_MINT, payer, true);
return createCloseAccountInstruction(wsolAta, payer, payer, [], TOKEN_PROGRAM_ID);
}
/**
* Create WSOL ATA instruction
*/
export function createWsolAtaInstruction(payer: PublicKey): Instruction {
const wsolAta = getAssociatedTokenAddressSync(NATIVE_MINT, payer, true);
return createAssociatedTokenAccountInstruction(
payer,
wsolAta,
payer,
NATIVE_MINT,
TOKEN_PROGRAM_ID
);
}
// ============================================
// Types
// ============================================
export interface MeteoraDammV2Params {
pool: PublicKey;
tokenAMint: PublicKey;
tokenBMint: PublicKey;
tokenAVault: PublicKey;
tokenBVault: PublicKey;
tokenAProgram: PublicKey;
tokenBProgram: PublicKey;
}
export interface BuildBuyInstructionsParams {
payer: Keypair | PublicKey;
inputMint: PublicKey;
outputMint: PublicKey;
inputAmount: bigint;
slippageBasisPoints?: bigint;
fixedOutputAmount?: bigint;
createInputMintAta?: boolean;
createOutputMintAta?: boolean;
closeInputMintAta?: boolean;
protocolParams: MeteoraDammV2Params;
}
export interface BuildSellInstructionsParams {
payer: Keypair | PublicKey;
inputMint: PublicKey;
outputMint: PublicKey;
inputAmount: bigint;
slippageBasisPoints?: bigint;
fixedOutputAmount?: bigint;
createOutputMintAta?: boolean;
closeOutputMintAta?: boolean;
closeInputMintAta?: boolean;
protocolParams: MeteoraDammV2Params;
}
// ============================================
// Instruction Builders
// ============================================
/**
* Build buy instructions for Meteora DAMM V2 protocol
*/
export function buildBuyInstructions(
params: BuildBuyInstructionsParams
): Instruction[] {
const {
payer,
inputMint,
outputMint,
inputAmount,
fixedOutputAmount,
createInputMintAta = true,
createOutputMintAta = true,
closeInputMintAta = false,
protocolParams,
} = params;
if (inputAmount === 0n) {
throw new Error("Amount cannot be zero");
}
if (!fixedOutputAmount) {
throw new Error("fixedOutputAmount must be set for Meteora DAMM V2 swap");
}
const payerPubkey = payer instanceof Keypair ? payer.publicKey : payer;
const instructions: Instruction[] = [];
const {
pool,
tokenAMint,
tokenBMint,
tokenAVault,
tokenBVault,
tokenAProgram,
tokenBProgram,
} = protocolParams;
// Check pool type
const isWsol = tokenAMint.equals(WSOL_TOKEN_ACCOUNT) || tokenBMint.equals(WSOL_TOKEN_ACCOUNT);
const isUsdc = tokenAMint.equals(USDC_TOKEN_ACCOUNT) || tokenBMint.equals(USDC_TOKEN_ACCOUNT);
if (!isWsol && !isUsdc) {
throw new Error("Pool must contain WSOL or USDC");
}
// Determine swap direction
const isAIn = tokenAMint.equals(WSOL_TOKEN_ACCOUNT) || tokenAMint.equals(USDC_TOKEN_ACCOUNT);
// Derive user token accounts
const inputTokenAccount = getAssociatedTokenAddressSync(
inputMint,
payerPubkey,
true,
isAIn ? tokenAProgram : tokenBProgram
);
const outputTokenAccount = getAssociatedTokenAddressSync(
outputMint,
payerPubkey,
true,
isAIn ? tokenBProgram : tokenAProgram
);
// Derive event authority
const eventAuthority = getEventAuthorityPda();
// Handle WSOL wrapping
if (createInputMintAta && isWsol) {
const wsolAta = getAssociatedTokenAddressSync(NATIVE_MINT, payerPubkey, true);
instructions.push(
createAssociatedTokenAccountInstruction(
payerPubkey,
wsolAta,
payerPubkey,
NATIVE_MINT,
TOKEN_PROGRAM_ID
)
);
instructions.push(createSyncNativeInstruction(wsolAta));
}
// Create output mint ATA if needed
if (createOutputMintAta) {
instructions.push(
createAssociatedTokenAccountInstruction(
payerPubkey,
outputTokenAccount,
payerPubkey,
outputMint,
TOKEN_PROGRAM_ID
)
);
}
// Build instruction data
const data = Buffer.alloc(24);
SWAP_DISCRIMINATOR.copy(data, 0);
data.writeBigUInt64LE(inputAmount, 8);
data.writeBigUInt64LE(fixedOutputAmount, 16);
// Build accounts
const accounts: AccountMeta[] = [
{ pubkey: AUTHORITY, isSigner: false, isWritable: false },
{ pubkey: pool, isSigner: false, isWritable: true },
{ pubkey: inputTokenAccount, isSigner: false, isWritable: true },
{ pubkey: outputTokenAccount, isSigner: false, isWritable: true },
{ pubkey: tokenAVault, isSigner: false, isWritable: true },
{ pubkey: tokenBVault, isSigner: false, isWritable: true },
{ pubkey: tokenAMint, isSigner: false, isWritable: false },
{ pubkey: tokenBMint, isSigner: false, isWritable: false },
{ pubkey: payerPubkey, isSigner: true, isWritable: true },
{ pubkey: tokenAProgram, isSigner: false, isWritable: false },
{ pubkey: tokenBProgram, isSigner: false, isWritable: false },
{ pubkey: METEORA_DAMM_V2_PROGRAM_ID, isSigner: false, isWritable: false }, // Referral Token Account (placeholder)
{ pubkey: eventAuthority, isSigner: false, isWritable: false },
{ pubkey: METEORA_DAMM_V2_PROGRAM_ID, isSigner: false, isWritable: false },
];
instructions.push(
new Instruction({
keys: accounts,
programId: METEORA_DAMM_V2_PROGRAM_ID,
data,
})
);
// Close WSOL ATA if requested
if (closeInputMintAta && isWsol) {
const wsolAta = getAssociatedTokenAddressSync(NATIVE_MINT, payerPubkey, true);
instructions.push(
createCloseAccountInstruction(wsolAta, payerPubkey, payerPubkey, [], TOKEN_PROGRAM_ID)
);
}
return instructions;
}
/**
* Build sell instructions for Meteora DAMM V2 protocol
*/
export function buildSellInstructions(
params: BuildSellInstructionsParams
): Instruction[] {
const {
payer,
inputMint,
outputMint,
inputAmount,
fixedOutputAmount,
createOutputMintAta = true,
closeOutputMintAta = false,
closeInputMintAta = false,
protocolParams,
} = params;
if (inputAmount === 0n) {
throw new Error("Amount cannot be zero");
}
if (!fixedOutputAmount) {
throw new Error("fixedOutputAmount must be set for Meteora DAMM V2 swap");
}
const payerPubkey = payer instanceof Keypair ? payer.publicKey : payer;
const instructions: Instruction[] = [];
const {
pool,
tokenAMint,
tokenBMint,
tokenAVault,
tokenBVault,
tokenAProgram,
tokenBProgram,
} = protocolParams;
// Check pool type
const isWsol = tokenBMint.equals(WSOL_TOKEN_ACCOUNT) || tokenAMint.equals(WSOL_TOKEN_ACCOUNT);
const isUsdc = tokenBMint.equals(USDC_TOKEN_ACCOUNT) || tokenAMint.equals(USDC_TOKEN_ACCOUNT);
if (!isWsol && !isUsdc) {
throw new Error("Pool must contain WSOL or USDC");
}
// Determine swap direction (selling token for WSOL/USDC)
const isAIn = tokenBMint.equals(WSOL_TOKEN_ACCOUNT) || tokenBMint.equals(USDC_TOKEN_ACCOUNT);
// Derive user token accounts
const inputTokenAccount = getAssociatedTokenAddressSync(
inputMint,
payerPubkey,
true,
isAIn ? tokenAProgram : tokenBProgram
);
const outputTokenAccount = getAssociatedTokenAddressSync(
outputMint,
payerPubkey,
true,
isAIn ? tokenBProgram : tokenAProgram
);
// Derive event authority
const eventAuthority = getEventAuthorityPda();
// Create WSOL ATA for receiving if needed
if (createOutputMintAta && isWsol) {
const wsolAta = getAssociatedTokenAddressSync(NATIVE_MINT, payerPubkey, true);
instructions.push(
createAssociatedTokenAccountInstruction(
payerPubkey,
wsolAta,
payerPubkey,
NATIVE_MINT,
TOKEN_PROGRAM_ID
)
);
}
// Build instruction data
const data = Buffer.alloc(24);
SWAP_DISCRIMINATOR.copy(data, 0);
data.writeBigUInt64LE(inputAmount, 8);
data.writeBigUInt64LE(fixedOutputAmount, 16);
// Build accounts
const accounts: AccountMeta[] = [
{ pubkey: AUTHORITY, isSigner: false, isWritable: false },
{ pubkey: pool, isSigner: false, isWritable: true },
{ pubkey: inputTokenAccount, isSigner: false, isWritable: true },
{ pubkey: outputTokenAccount, isSigner: false, isWritable: true },
{ pubkey: tokenAVault, isSigner: false, isWritable: true },
{ pubkey: tokenBVault, isSigner: false, isWritable: true },
{ pubkey: tokenAMint, isSigner: false, isWritable: false },
{ pubkey: tokenBMint, isSigner: false, isWritable: false },
{ pubkey: payerPubkey, isSigner: true, isWritable: true },
{ pubkey: tokenAProgram, isSigner: false, isWritable: false },
{ pubkey: tokenBProgram, isSigner: false, isWritable: false },
{ pubkey: METEORA_DAMM_V2_PROGRAM_ID, isSigner: false, isWritable: false }, // Referral Token Account
{ pubkey: eventAuthority, isSigner: false, isWritable: false },
{ pubkey: METEORA_DAMM_V2_PROGRAM_ID, isSigner: false, isWritable: false },
];
instructions.push(
new Instruction({
keys: accounts,
programId: METEORA_DAMM_V2_PROGRAM_ID,
data,
})
);
// Close WSOL ATA if requested
if (closeOutputMintAta && isWsol) {
const wsolAta = getAssociatedTokenAddressSync(NATIVE_MINT, payerPubkey, true);
instructions.push(
createCloseAccountInstruction(wsolAta, payerPubkey, payerPubkey, [], TOKEN_PROGRAM_ID)
);
}
// Close input token ATA if requested
if (closeInputMintAta) {
instructions.push(
createCloseAccountInstruction(
inputTokenAccount,
payerPubkey,
payerPubkey,
[],
isAIn ? tokenAProgram : tokenBProgram
)
);
}
return instructions;
}
@@ -0,0 +1,515 @@
/**
* PumpFun Protocol Instruction Builder
*
* Production-grade instruction builder for PumpFun bonding curve protocol.
* Supports buy, sell, and cashback claim operations.
*/
import {
PublicKey,
Keypair,
AccountMeta,
Instruction,
SYSVAR_RENT_PUBKEY,
SystemProgram,
} from "@solana/web3.js";
import {
getAssociatedTokenAddressSync,
createAssociatedTokenAccountInstruction,
TOKEN_PROGRAM_ID,
TOKEN_2022_PROGRAM_ID,
createCloseAccountInstruction,
} from "@solana/spl-token";
// ============================================
// Program IDs and Constants
// ============================================
/** PumpFun program ID */
export const PUMPFUN_PROGRAM_ID = new PublicKey(
"6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"
);
/** MPL Token Metadata program ID */
export const MPL_TOKEN_METADATA_PROGRAM_ID = new PublicKey(
"metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s"
);
/** Event Authority for PumpFun */
export const EVENT_AUTHORITY = new PublicKey(
"Ce6TQqeHC9p8KetsN6JsjHK7UTZk7nasjjnr7XxXp9F1"
);
/** Fee Program */
export const FEE_PROGRAM = new PublicKey(
"pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ"
);
/** Global Volume Accumulator */
export const GLOBAL_VOLUME_ACCUMULATOR = new PublicKey(
"Hq2wp8uJ9jCPsYgNHex8RtqdvMPfVGoYwjvF1ATiwn2Y"
);
/** Fee Config */
export const FEE_CONFIG = new PublicKey(
"8Wf5TiAheLUqBrKXeYg2JtAFFMWtKdG2BSFgqUcPVwTt"
);
/** Global Account */
export const GLOBAL_ACCOUNT = new PublicKey(
"4wTV1YmiEkRvAtNtsSGPtUrqRYQMe5SKy2uB4Jjaxnjf"
);
/** Authority */
export const AUTHORITY = new PublicKey(
"FFWtrEQ4B4PKQoVuHYzZq8FabGkVatYzDpEVHsK5rrhF"
);
/** Withdraw Authority */
export const WITHDRAW_AUTHORITY = new PublicKey(
"39azUYFWPz3VHgKCf3VChUwbpURdCHRxjWVowf5jUJjg"
);
/** Fee Recipient */
export const FEE_RECIPIENT = new PublicKey(
"62qc2CNXwrYqQScmEdiZFFAnJR262PxWEuNQtxfafNgV"
);
/** Mayhem Fee Recipients */
export const MAYHEM_FEE_RECIPIENTS: PublicKey[] = [
new PublicKey("GesfTA3X2arioaHp8bbKdjG9vJtskViWACZoYvxp4twS"),
new PublicKey("4budycTjhs9fD6xw62VBducVTNgMgJJ5BgtKq7mAZwn6"),
new PublicKey("8SBKzEQU4nLSzcwF4a74F2iaUDQyTfjGndn6qUWBnrpR"),
new PublicKey("4UQeTP1T39KZ9Sfxzo3WR5skgsaP6NZa87BAkuazLEKH"),
new PublicKey("8sNeir4QsLsJdYpc9RZacohhK1Y5FLU3nC5LXgYB4aa6"),
new PublicKey("Fh9HmeLNUMVCvejxCtCL2DbYaRyBFVJ5xrWkLnMH6fdk"),
new PublicKey("463MEnMeGyJekNZFQSTUABBEbLnvMTALbT6ZmsxAbAdq"),
new PublicKey("6AUH3WEHucYZyC61hqpqYUWVto5qA5hjHuNQ32GNnNxA"),
];
// ============================================
// Discriminators
// ============================================
/** Buy instruction discriminator */
export const BUY_DISCRIMINATOR: Buffer = Buffer.from([
102, 6, 61, 18, 1, 218, 235, 234,
]);
/** Buy exact SOL in discriminator */
export const BUY_EXACT_SOL_IN_DISCRIMINATOR: Buffer = Buffer.from([
56, 252, 116, 8, 158, 223, 205, 95,
]);
/** Sell instruction discriminator */
export const SELL_DISCRIMINATOR: Buffer = Buffer.from([
51, 230, 133, 164, 1, 127, 131, 173,
]);
/** Claim cashback discriminator */
export const CLAIM_CASHBACK_DISCRIMINATOR: Buffer = Buffer.from([
37, 58, 35, 126, 190, 53, 228, 197,
]);
// ============================================
// Seeds
// ============================================
export const BONDING_CURVE_SEED = Buffer.from("bonding-curve");
export const BONDING_CURVE_V2_SEED = Buffer.from("bonding-curve-v2");
export const CREATOR_VAULT_SEED = Buffer.from("creator-vault");
export const USER_VOLUME_ACCUMULATOR_SEED = Buffer.from("user_volume_accumulator");
// ============================================
// PDA Derivation Functions
// ============================================
/**
* Derive the bonding curve PDA for a given mint
*/
export function getBondingCurvePda(mint: PublicKey): PublicKey {
const [pda] = PublicKey.findProgramAddressSync(
[BONDING_CURVE_SEED, mint.toBuffer()],
PUMPFUN_PROGRAM_ID
);
return pda;
}
/**
* Derive the bonding curve v2 PDA for a given mint
*/
export function getBondingCurveV2Pda(mint: PublicKey): PublicKey {
const [pda] = PublicKey.findProgramAddressSync(
[BONDING_CURVE_V2_SEED, mint.toBuffer()],
PUMPFUN_PROGRAM_ID
);
return pda;
}
/**
* Derive the creator vault PDA for a given creator
*/
export function getCreatorVaultPda(creator: PublicKey): PublicKey {
const [pda] = PublicKey.findProgramAddressSync(
[CREATOR_VAULT_SEED, creator.toBuffer()],
PUMPFUN_PROGRAM_ID
);
return pda;
}
/**
* Derive the user volume accumulator PDA for a given user
*/
export function getUserVolumeAccumulatorPda(user: PublicKey): PublicKey {
const [pda] = PublicKey.findProgramAddressSync(
[USER_VOLUME_ACCUMULATOR_SEED, user.toBuffer()],
PUMPFUN_PROGRAM_ID
);
return pda;
}
/**
* Get a random Mayhem fee recipient
*/
export function getRandomMayhemFeeRecipient(): PublicKey {
const index = Math.floor(Math.random() * MAYHEM_FEE_RECIPIENTS.length);
return MAYHEM_FEE_RECIPIENTS[index];
}
// ============================================
// Helper Functions
// ============================================
/**
* Calculate buy amount with slippage protection
*/
export function calculateWithSlippageBuy(amount: bigint, basisPoints: bigint): bigint {
const maxBps = 9999n;
const bps = basisPoints > maxBps ? maxBps : basisPoints;
return amount + (amount * bps) / 10000n;
}
/**
* Calculate sell amount with slippage protection
*/
export function calculateWithSlippageSell(amount: bigint, basisPoints: bigint): bigint {
if (amount <= basisPoints / 10000n) {
return 1n;
}
return amount - (amount * basisPoints) / 10000n;
}
// ============================================
// Types
// ============================================
export interface BondingCurve {
account: PublicKey;
virtualTokenReserves: bigint;
virtualSolReserves: bigint;
realTokenReserves: bigint;
isMayhemMode: boolean;
isCashbackCoin: boolean;
}
export interface PumpFunParams {
bondingCurve: BondingCurve;
creatorVault: PublicKey;
tokenProgram: PublicKey;
associatedBondingCurve?: PublicKey;
closeTokenAccountWhenSell?: boolean;
}
export interface BuildBuyInstructionsParams {
payer: Keypair | PublicKey;
outputMint: PublicKey;
inputAmount: bigint;
slippageBasisPoints?: bigint;
fixedOutputAmount?: bigint;
createOutputMintAta?: boolean;
protocolParams: PumpFunParams;
useExactSolAmount?: boolean;
}
export interface BuildSellInstructionsParams {
payer: Keypair | PublicKey;
inputMint: PublicKey;
inputAmount: bigint;
slippageBasisPoints?: bigint;
fixedOutputAmount?: bigint;
closeInputMintAta?: boolean;
protocolParams: PumpFunParams;
}
// ============================================
// Instruction Builders
// ============================================
/**
* Build buy instructions for PumpFun protocol
*/
export function buildBuyInstructions(
params: BuildBuyInstructionsParams
): Instruction[] {
const {
payer,
outputMint,
inputAmount,
slippageBasisPoints = 1000n,
fixedOutputAmount,
createOutputMintAta = true,
protocolParams,
useExactSolAmount = true,
} = params;
if (inputAmount === 0n) {
throw new Error("Amount cannot be zero");
}
const payerPubkey = payer instanceof Keypair ? payer.publicKey : payer;
const instructions: Instruction[] = [];
const { bondingCurve, creatorVault, tokenProgram, associatedBondingCurve } = protocolParams;
// Derive bonding curve address
const bondingCurveAddr =
bondingCurve.account.equals(PublicKey.default) || bondingCurve.account === undefined
? getBondingCurvePda(outputMint)
: bondingCurve.account;
// Get token program
const tokenProgramId = tokenProgram || TOKEN_PROGRAM_ID;
// Derive associated bonding curve
const associatedBondingCurveAddr =
associatedBondingCurve && !associatedBondingCurve.equals(PublicKey.default)
? associatedBondingCurve
: getAssociatedTokenAddressSync(outputMint, bondingCurveAddr, true, tokenProgramId);
// Derive user token account
const userTokenAccount = getAssociatedTokenAddressSync(
outputMint,
payerPubkey,
true,
tokenProgramId
);
// Derive user volume accumulator
const userVolumeAccumulator = getUserVolumeAccumulatorPda(payerPubkey);
// Create ATA if needed
if (createOutputMintAta) {
instructions.push(
createAssociatedTokenAccountInstruction(
payerPubkey,
userTokenAccount,
payerPubkey,
outputMint,
tokenProgramId
)
);
}
// Determine fee recipient
const feeRecipient = bondingCurve.isMayhemMode
? getRandomMayhemFeeRecipient()
: FEE_RECIPIENT;
// Derive bonding curve v2
const bondingCurveV2 = getBondingCurveV2Pda(outputMint);
// Track volume for cashback coins
const trackVolume = bondingCurve.isCashbackCoin
? Buffer.from([1, 1])
: Buffer.from([1, 0]);
// Build instruction data
let data: Buffer;
if (useExactSolAmount) {
// buy_exact_sol_in(spendable_sol_in: u64, min_tokens_out: u64, track_volume)
const minTokensOut = fixedOutputAmount
? fixedOutputAmount
: calculateWithSlippageSell(inputAmount, slippageBasisPoints);
data = Buffer.alloc(26);
BUY_EXACT_SOL_IN_DISCRIMINATOR.copy(data, 0);
data.writeBigUInt64LE(inputAmount, 8);
data.writeBigUInt64LE(minTokensOut, 16);
trackVolume.copy(data, 24);
} else {
// buy(token_amount: u64, max_sol_cost: u64, track_volume)
const maxSolCost = calculateWithSlippageBuy(inputAmount, slippageBasisPoints);
data = Buffer.alloc(26);
BUY_DISCRIMINATOR.copy(data, 0);
data.writeBigUInt64LE(inputAmount, 8);
data.writeBigUInt64LE(maxSolCost, 16);
trackVolume.copy(data, 24);
}
// Build accounts
const accounts: AccountMeta[] = [
{ pubkey: GLOBAL_ACCOUNT, isSigner: false, isWritable: false },
{ pubkey: feeRecipient, isSigner: false, isWritable: true },
{ pubkey: outputMint, isSigner: false, isWritable: false },
{ pubkey: bondingCurveAddr, isSigner: false, isWritable: true },
{ pubkey: associatedBondingCurveAddr, isSigner: false, isWritable: true },
{ pubkey: userTokenAccount, isSigner: false, isWritable: true },
{ pubkey: payerPubkey, isSigner: true, isWritable: true },
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
{ pubkey: tokenProgramId, isSigner: false, isWritable: false },
{ pubkey: creatorVault, isSigner: false, isWritable: true },
{ pubkey: EVENT_AUTHORITY, isSigner: false, isWritable: false },
{ pubkey: PUMPFUN_PROGRAM_ID, isSigner: false, isWritable: false },
{ pubkey: GLOBAL_VOLUME_ACCUMULATOR, isSigner: false, isWritable: true },
{ pubkey: userVolumeAccumulator, isSigner: false, isWritable: true },
{ pubkey: FEE_CONFIG, isSigner: false, isWritable: false },
{ pubkey: FEE_PROGRAM, isSigner: false, isWritable: false },
{ pubkey: bondingCurveV2, isSigner: false, isWritable: false },
];
instructions.push(
new Instruction({
keys: accounts,
programId: PUMPFUN_PROGRAM_ID,
data,
})
);
return instructions;
}
/**
* Build sell instructions for PumpFun protocol
*/
export function buildSellInstructions(
params: BuildSellInstructionsParams
): Instruction[] {
const {
payer,
inputMint,
inputAmount,
slippageBasisPoints = 1000n,
fixedOutputAmount,
closeInputMintAta = false,
protocolParams,
} = params;
if (inputAmount === 0n) {
throw new Error("Amount cannot be zero");
}
const payerPubkey = payer instanceof Keypair ? payer.publicKey : payer;
const instructions: Instruction[] = [];
const { bondingCurve, creatorVault, tokenProgram, associatedBondingCurve, closeTokenAccountWhenSell } = protocolParams;
// Derive bonding curve address
const bondingCurveAddr =
bondingCurve.account.equals(PublicKey.default) || bondingCurve.account === undefined
? getBondingCurvePda(inputMint)
: bondingCurve.account;
// Get token program
const tokenProgramId = tokenProgram || TOKEN_PROGRAM_ID;
// Derive associated bonding curve
const associatedBondingCurveAddr =
associatedBondingCurve && !associatedBondingCurve.equals(PublicKey.default)
? associatedBondingCurve
: getAssociatedTokenAddressSync(inputMint, bondingCurveAddr, true, tokenProgramId);
// Derive user token account
const userTokenAccount = getAssociatedTokenAddressSync(
inputMint,
payerPubkey,
true,
tokenProgramId
);
// Determine fee recipient
const feeRecipient = bondingCurve.isMayhemMode
? getRandomMayhemFeeRecipient()
: FEE_RECIPIENT;
// Derive bonding curve v2
const bondingCurveV2 = getBondingCurveV2Pda(inputMint);
// Build instruction data (sell: token_amount, min_sol_output)
const minSolOutput = fixedOutputAmount
? fixedOutputAmount
: calculateWithSlippageSell(inputAmount, slippageBasisPoints);
const data = Buffer.alloc(24);
SELL_DISCRIMINATOR.copy(data, 0);
data.writeBigUInt64LE(inputAmount, 8);
data.writeBigUInt64LE(minSolOutput, 16);
// Build accounts
const accounts: AccountMeta[] = [
{ pubkey: GLOBAL_ACCOUNT, isSigner: false, isWritable: false },
{ pubkey: feeRecipient, isSigner: false, isWritable: true },
{ pubkey: inputMint, isSigner: false, isWritable: false },
{ pubkey: bondingCurveAddr, isSigner: false, isWritable: true },
{ pubkey: associatedBondingCurveAddr, isSigner: false, isWritable: true },
{ pubkey: userTokenAccount, isSigner: false, isWritable: true },
{ pubkey: payerPubkey, isSigner: true, isWritable: true },
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
{ pubkey: creatorVault, isSigner: false, isWritable: true },
{ pubkey: tokenProgramId, isSigner: false, isWritable: false },
{ pubkey: EVENT_AUTHORITY, isSigner: false, isWritable: false },
{ pubkey: PUMPFUN_PROGRAM_ID, isSigner: false, isWritable: false },
{ pubkey: FEE_CONFIG, isSigner: false, isWritable: false },
{ pubkey: FEE_PROGRAM, isSigner: false, isWritable: false },
];
// Add user volume accumulator for cashback coins
if (bondingCurve.isCashbackCoin) {
const userVolumeAccumulator = getUserVolumeAccumulatorPda(payerPubkey);
accounts.push({ pubkey: userVolumeAccumulator, isSigner: false, isWritable: true });
}
// Add bonding curve v2
accounts.push({ pubkey: bondingCurveV2, isSigner: false, isWritable: false });
instructions.push(
new Instruction({
keys: accounts,
programId: PUMPFUN_PROGRAM_ID,
data,
})
);
// Close token account if requested
if (closeInputMintAta || closeTokenAccountWhenSell) {
instructions.push(
createCloseAccountInstruction(
userTokenAccount,
payerPubkey,
payerPubkey,
[],
tokenProgramId
)
);
}
return instructions;
}
/**
* Build claim cashback instruction for PumpFun
*/
export function buildClaimCashbackInstruction(payer: PublicKey): Instruction {
const userVolumeAccumulator = getUserVolumeAccumulatorPda(payer);
const accounts: AccountMeta[] = [
{ pubkey: payer, isSigner: true, isWritable: true },
{ pubkey: userVolumeAccumulator, isSigner: false, isWritable: true },
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
{ pubkey: EVENT_AUTHORITY, isSigner: false, isWritable: false },
{ pubkey: PUMPFUN_PROGRAM_ID, isSigner: false, isWritable: false },
];
return new Instruction({
keys: accounts,
programId: PUMPFUN_PROGRAM_ID,
data: CLAIM_CASHBACK_DISCRIMINATOR,
});
}
@@ -0,0 +1,476 @@
/**
* Raydium AMM V4 Protocol Instruction Builder
*
* Production-grade instruction builder for Raydium AMM V4 protocol.
* Supports swap operations with WSOL and USDC pools.
*/
import {
PublicKey,
Keypair,
AccountMeta,
Instruction,
SystemProgram,
} from "@solana/web3.js";
import {
getAssociatedTokenAddressSync,
createAssociatedTokenAccountInstruction,
TOKEN_PROGRAM_ID,
createCloseAccountInstruction,
NATIVE_MINT,
createSyncNativeInstruction,
} from "@solana/spl-token";
// ============================================
// Program IDs and Constants
// ============================================
/** Raydium AMM V4 program ID */
export const RAYDIUM_AMM_V4_PROGRAM_ID = new PublicKey(
"675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8"
);
/** Authority */
export const AUTHORITY = new PublicKey(
"5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1"
);
/** WSOL Token Account (mint) */
export const WSOL_TOKEN_ACCOUNT = new PublicKey(
"So11111111111111111111111111111111111111112"
);
/** USDC Token Account (mint) */
export const USDC_TOKEN_ACCOUNT = new PublicKey(
"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
);
/** Fee rates */
export const TRADE_FEE_NUMERATOR = 25n;
export const TRADE_FEE_DENOMINATOR = 10000n;
export const SWAP_FEE_NUMERATOR = 25n;
export const SWAP_FEE_DENOMINATOR = 10000n;
// ============================================
// Discriminators
// ============================================
/** Swap base in instruction discriminator (single byte) */
export const SWAP_BASE_IN_DISCRIMINATOR: Buffer = Buffer.from([9]);
/** Swap base out instruction discriminator (single byte) */
export const SWAP_BASE_OUT_DISCRIMINATOR: Buffer = Buffer.from([11]);
// ============================================
// Seeds
// ============================================
export const POOL_SEED = Buffer.from("pool");
// ============================================
// Helper Functions
// ============================================
/**
* Compute swap amount for AMM V4
*/
export function computeSwapAmount(
coinReserve: bigint,
pcReserve: bigint,
isCoinIn: boolean,
amountIn: bigint,
slippageBasisPoints: bigint
): { amountOut: bigint; minAmountOut: bigint } {
// Apply trade fee (0.25%)
const amountInAfterFee = amountIn - (amountIn * TRADE_FEE_NUMERATOR) / TRADE_FEE_DENOMINATOR;
// Calculate output using constant product formula
let amountOut: bigint;
if (isCoinIn) {
// Selling coin for pc: output = (pcReserve * amountIn) / (coinReserve + amountIn)
const denominator = coinReserve + amountInAfterFee;
amountOut = (pcReserve * amountInAfterFee) / denominator;
} else {
// Selling pc for coin: output = (coinReserve * amountIn) / (pcReserve + amountIn)
const denominator = pcReserve + amountInAfterFee;
amountOut = (coinReserve * amountInAfterFee) / denominator;
}
// Apply slippage
const minAmountOut = amountOut - (amountOut * slippageBasisPoints) / 10000n;
return { amountOut, minAmountOut };
}
/**
* Create instructions to wrap SOL into WSOL
*/
export function createWsolInstructions(
payer: PublicKey,
amount: bigint
): Instruction[] {
const instructions: Instruction[] = [];
const wsolAta = getAssociatedTokenAddressSync(NATIVE_MINT, payer, true);
// Sync native (wrap SOL)
instructions.push(createSyncNativeInstruction(wsolAta));
return instructions;
}
/**
* Create instruction to close WSOL ATA and unwrap to SOL
*/
export function createCloseWsolInstruction(payer: PublicKey): Instruction {
const wsolAta = getAssociatedTokenAddressSync(NATIVE_MINT, payer, true);
return createCloseAccountInstruction(wsolAta, payer, payer, [], TOKEN_PROGRAM_ID);
}
/**
* Create WSOL ATA instruction
*/
export function createWsolAtaInstruction(payer: PublicKey): Instruction {
const wsolAta = getAssociatedTokenAddressSync(NATIVE_MINT, payer, true);
return createAssociatedTokenAccountInstruction(
payer,
wsolAta,
payer,
NATIVE_MINT,
TOKEN_PROGRAM_ID
);
}
// ============================================
// Types
// ============================================
export interface RaydiumAmmV4Params {
amm: PublicKey;
coinMint: PublicKey;
pcMint: PublicKey;
tokenCoin: PublicKey;
tokenPc: PublicKey;
coinReserve: bigint;
pcReserve: bigint;
}
export interface BuildBuyInstructionsParams {
payer: Keypair | PublicKey;
outputMint: PublicKey;
inputAmount: bigint;
slippageBasisPoints?: bigint;
fixedOutputAmount?: bigint;
createInputMintAta?: boolean;
createOutputMintAta?: boolean;
closeInputMintAta?: boolean;
protocolParams: RaydiumAmmV4Params;
}
export interface BuildSellInstructionsParams {
payer: Keypair | PublicKey;
inputMint: PublicKey;
inputAmount: bigint;
slippageBasisPoints?: bigint;
fixedOutputAmount?: bigint;
createOutputMintAta?: boolean;
closeOutputMintAta?: boolean;
closeInputMintAta?: boolean;
protocolParams: RaydiumAmmV4Params;
}
// ============================================
// Instruction Builders
// ============================================
/**
* Build buy instructions for Raydium AMM V4 protocol
*/
export function buildBuyInstructions(
params: BuildBuyInstructionsParams
): Instruction[] {
const {
payer,
outputMint,
inputAmount,
slippageBasisPoints = 1000n,
fixedOutputAmount,
createInputMintAta = true,
createOutputMintAta = true,
closeInputMintAta = false,
protocolParams,
} = params;
if (inputAmount === 0n) {
throw new Error("Amount cannot be zero");
}
const payerPubkey = payer instanceof Keypair ? payer.publicKey : payer;
const instructions: Instruction[] = [];
const {
amm,
coinMint,
pcMint,
tokenCoin,
tokenPc,
coinReserve,
pcReserve,
} = protocolParams;
// Check pool type
const isWsol = coinMint.equals(WSOL_TOKEN_ACCOUNT) || pcMint.equals(WSOL_TOKEN_ACCOUNT);
const isUsdc = coinMint.equals(USDC_TOKEN_ACCOUNT) || pcMint.equals(USDC_TOKEN_ACCOUNT);
if (!isWsol && !isUsdc) {
throw new Error("Pool must contain WSOL or USDC");
}
// Determine swap direction
const isBaseIn = coinMint.equals(WSOL_TOKEN_ACCOUNT) || coinMint.equals(USDC_TOKEN_ACCOUNT);
// Calculate output
const swapResult = computeSwapAmount(
coinReserve,
pcReserve,
isBaseIn,
inputAmount,
slippageBasisPoints
);
const minimumAmountOut = fixedOutputAmount || swapResult.minAmountOut;
// Determine input/output mints
const inputMint = isWsol ? WSOL_TOKEN_ACCOUNT : USDC_TOKEN_ACCOUNT;
// Derive user token accounts
const userSourceTokenAccount = getAssociatedTokenAddressSync(
inputMint,
payerPubkey,
true,
TOKEN_PROGRAM_ID
);
const userDestinationTokenAccount = getAssociatedTokenAddressSync(
outputMint,
payerPubkey,
true,
TOKEN_PROGRAM_ID
);
// Handle WSOL wrapping
if (createInputMintAta && isWsol) {
const wsolAta = getAssociatedTokenAddressSync(NATIVE_MINT, payerPubkey, true);
instructions.push(
createAssociatedTokenAccountInstruction(
payerPubkey,
wsolAta,
payerPubkey,
NATIVE_MINT,
TOKEN_PROGRAM_ID
)
);
instructions.push(createSyncNativeInstruction(wsolAta));
}
// Create output mint ATA if needed
if (createOutputMintAta) {
instructions.push(
createAssociatedTokenAccountInstruction(
payerPubkey,
userDestinationTokenAccount,
payerPubkey,
outputMint,
TOKEN_PROGRAM_ID
)
);
}
// Build instruction data (1 byte discriminator + 8 bytes amountIn + 8 bytes minAmountOut)
const data = Buffer.alloc(17);
SWAP_BASE_IN_DISCRIMINATOR.copy(data, 0);
data.writeBigUInt64LE(inputAmount, 1);
data.writeBigUInt64LE(minimumAmountOut, 9);
// Build accounts (Raydium AMM V4 has a specific account order)
const accounts: AccountMeta[] = [
{ pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
{ pubkey: amm, isSigner: false, isWritable: true },
{ pubkey: AUTHORITY, isSigner: false, isWritable: false },
{ pubkey: amm, isSigner: false, isWritable: true }, // Amm Open Orders (same as amm for simplicity)
{ pubkey: tokenCoin, isSigner: false, isWritable: true }, // Pool Coin Token Account
{ pubkey: tokenPc, isSigner: false, isWritable: true }, // Pool Pc Token Account
{ pubkey: amm, isSigner: false, isWritable: false }, // Serum Program (placeholder)
{ pubkey: amm, isSigner: false, isWritable: false }, // Serum Market (placeholder)
{ pubkey: amm, isSigner: false, isWritable: false }, // Serum Bids (placeholder)
{ pubkey: amm, isSigner: false, isWritable: false }, // Serum Asks (placeholder)
{ pubkey: amm, isSigner: false, isWritable: false }, // Serum Event Queue (placeholder)
{ pubkey: amm, isSigner: false, isWritable: false }, // Serum Coin Vault Account (placeholder)
{ pubkey: amm, isSigner: false, isWritable: false }, // Serum Pc Vault Account (placeholder)
{ pubkey: amm, isSigner: false, isWritable: false }, // Serum Vault Signer (placeholder)
{ pubkey: userSourceTokenAccount, isSigner: false, isWritable: true },
{ pubkey: userDestinationTokenAccount, isSigner: false, isWritable: true },
{ pubkey: payerPubkey, isSigner: true, isWritable: true },
];
instructions.push(
new Instruction({
keys: accounts,
programId: RAYDIUM_AMM_V4_PROGRAM_ID,
data,
})
);
// Close WSOL ATA if requested
if (closeInputMintAta && isWsol) {
const wsolAta = getAssociatedTokenAddressSync(NATIVE_MINT, payerPubkey, true);
instructions.push(
createCloseAccountInstruction(wsolAta, payerPubkey, payerPubkey, [], TOKEN_PROGRAM_ID)
);
}
return instructions;
}
/**
* Build sell instructions for Raydium AMM V4 protocol
*/
export function buildSellInstructions(
params: BuildSellInstructionsParams
): Instruction[] {
const {
payer,
inputMint,
inputAmount,
slippageBasisPoints = 1000n,
fixedOutputAmount,
createOutputMintAta = true,
closeOutputMintAta = false,
closeInputMintAta = false,
protocolParams,
} = params;
if (inputAmount === 0n) {
throw new Error("Amount cannot be zero");
}
const payerPubkey = payer instanceof Keypair ? payer.publicKey : payer;
const instructions: Instruction[] = [];
const {
amm,
coinMint,
pcMint,
tokenCoin,
tokenPc,
coinReserve,
pcReserve,
} = protocolParams;
// Check pool type
const isWsol = coinMint.equals(WSOL_TOKEN_ACCOUNT) || pcMint.equals(WSOL_TOKEN_ACCOUNT);
const isUsdc = coinMint.equals(USDC_TOKEN_ACCOUNT) || pcMint.equals(USDC_TOKEN_ACCOUNT);
if (!isWsol && !isUsdc) {
throw new Error("Pool must contain WSOL or USDC");
}
// Determine swap direction (selling token for WSOL/USDC means pc is output)
const isBaseIn = pcMint.equals(WSOL_TOKEN_ACCOUNT) || pcMint.equals(USDC_TOKEN_ACCOUNT);
// Calculate output
const swapResult = computeSwapAmount(
coinReserve,
pcReserve,
isBaseIn,
inputAmount,
slippageBasisPoints
);
const minimumAmountOut = fixedOutputAmount || swapResult.minAmountOut;
// Determine output mint
const outputMint = isWsol ? WSOL_TOKEN_ACCOUNT : USDC_TOKEN_ACCOUNT;
// Derive user token accounts
const userSourceTokenAccount = getAssociatedTokenAddressSync(
inputMint,
payerPubkey,
true,
TOKEN_PROGRAM_ID
);
const userDestinationTokenAccount = getAssociatedTokenAddressSync(
outputMint,
payerPubkey,
true,
TOKEN_PROGRAM_ID
);
// Create WSOL ATA for receiving if needed
if (createOutputMintAta && isWsol) {
const wsolAta = getAssociatedTokenAddressSync(NATIVE_MINT, payerPubkey, true);
instructions.push(
createAssociatedTokenAccountInstruction(
payerPubkey,
wsolAta,
payerPubkey,
NATIVE_MINT,
TOKEN_PROGRAM_ID
)
);
}
// Build instruction data
const data = Buffer.alloc(17);
SWAP_BASE_IN_DISCRIMINATOR.copy(data, 0);
data.writeBigUInt64LE(inputAmount, 1);
data.writeBigUInt64LE(minimumAmountOut, 9);
// Build accounts
const accounts: AccountMeta[] = [
{ pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
{ pubkey: amm, isSigner: false, isWritable: true },
{ pubkey: AUTHORITY, isSigner: false, isWritable: false },
{ pubkey: amm, isSigner: false, isWritable: true }, // Amm Open Orders
{ pubkey: tokenCoin, isSigner: false, isWritable: true }, // Pool Coin Token Account
{ pubkey: tokenPc, isSigner: false, isWritable: true }, // Pool Pc Token Account
{ pubkey: amm, isSigner: false, isWritable: false }, // Serum Program
{ pubkey: amm, isSigner: false, isWritable: false }, // Serum Market
{ pubkey: amm, isSigner: false, isWritable: false }, // Serum Bids
{ pubkey: amm, isSigner: false, isWritable: false }, // Serum Asks
{ pubkey: amm, isSigner: false, isWritable: false }, // Serum Event Queue
{ pubkey: amm, isSigner: false, isWritable: false }, // Serum Coin Vault Account
{ pubkey: amm, isSigner: false, isWritable: false }, // Serum Pc Vault Account
{ pubkey: amm, isSigner: false, isWritable: false }, // Serum Vault Signer
{ pubkey: userSourceTokenAccount, isSigner: false, isWritable: true },
{ pubkey: userDestinationTokenAccount, isSigner: false, isWritable: true },
{ pubkey: payerPubkey, isSigner: true, isWritable: true },
];
instructions.push(
new Instruction({
keys: accounts,
programId: RAYDIUM_AMM_V4_PROGRAM_ID,
data,
})
);
// Close WSOL ATA if requested
if (closeOutputMintAta && isWsol) {
const wsolAta = getAssociatedTokenAddressSync(NATIVE_MINT, payerPubkey, true);
instructions.push(
createCloseAccountInstruction(wsolAta, payerPubkey, payerPubkey, [], TOKEN_PROGRAM_ID)
);
}
// Close input token ATA if requested
if (closeInputMintAta) {
instructions.push(
createCloseAccountInstruction(
userSourceTokenAccount,
payerPubkey,
payerPubkey,
[],
TOKEN_PROGRAM_ID
)
);
}
return instructions;
}
@@ -0,0 +1,556 @@
/**
* Raydium CPMM (Concentrated Pool Market Maker) Protocol Instruction Builder
*
* Production-grade instruction builder for Raydium CPMM protocol.
* Supports swap operations with WSOL and USDC pools.
*/
import {
PublicKey,
Keypair,
AccountMeta,
Instruction,
SystemProgram,
} from "@solana/web3.js";
import {
getAssociatedTokenAddressSync,
createAssociatedTokenAccountInstruction,
TOKEN_PROGRAM_ID,
createCloseAccountInstruction,
NATIVE_MINT,
createSyncNativeInstruction,
} from "@solana/spl-token";
// ============================================
// Program IDs and Constants
// ============================================
/** Raydium CPMM program ID */
export const RAYDIUM_CPMM_PROGRAM_ID = new PublicKey(
"CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C"
);
/** Authority */
export const AUTHORITY = new PublicKey(
"GpMZbSM2GgvTKHJirzeGfMFoaZ8UR2X7F4v8vHTvxFbL"
);
/** WSOL Token Account (mint) */
export const WSOL_TOKEN_ACCOUNT = new PublicKey(
"So11111111111111111111111111111111111111112"
);
/** USDC Token Account (mint) */
export const USDC_TOKEN_ACCOUNT = new PublicKey(
"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
);
/** Fee rates */
export const FEE_RATE_DENOMINATOR_VALUE = 1_000_000n;
export const TRADE_FEE_RATE = 2500n;
export const CREATOR_FEE_RATE = 0n;
export const PROTOCOL_FEE_RATE = 120000n;
export const FUND_FEE_RATE = 40000n;
// ============================================
// Discriminators
// ============================================
/** Swap base in instruction discriminator */
export const SWAP_BASE_IN_DISCRIMINATOR: Buffer = Buffer.from([
143, 190, 90, 218, 196, 30, 51, 222,
]);
/** Swap base out instruction discriminator */
export const SWAP_BASE_OUT_DISCRIMINATOR: Buffer = Buffer.from([
55, 217, 98, 86, 163, 74, 180, 173,
]);
// ============================================
// Seeds
// ============================================
export const POOL_SEED = Buffer.from("pool");
export const POOL_VAULT_SEED = Buffer.from("pool_vault");
export const OBSERVATION_STATE_SEED = Buffer.from("observation");
// ============================================
// PDA Derivation Functions
// ============================================
/**
* Derive the pool PDA for given config and mints
*/
export function getPoolPda(
ammConfig: PublicKey,
mint1: PublicKey,
mint2: PublicKey
): PublicKey {
const [pda] = PublicKey.findProgramAddressSync(
[POOL_SEED, ammConfig.toBuffer(), mint1.toBuffer(), mint2.toBuffer()],
RAYDIUM_CPMM_PROGRAM_ID
);
return pda;
}
/**
* Derive the vault PDA for a pool and mint
*/
export function getVaultPda(poolState: PublicKey, mint: PublicKey): PublicKey {
const [pda] = PublicKey.findProgramAddressSync(
[POOL_VAULT_SEED, poolState.toBuffer(), mint.toBuffer()],
RAYDIUM_CPMM_PROGRAM_ID
);
return pda;
}
/**
* Derive the observation state PDA for a pool
*/
export function getObservationStatePda(poolState: PublicKey): PublicKey {
const [pda] = PublicKey.findProgramAddressSync(
[OBSERVATION_STATE_SEED, poolState.toBuffer()],
RAYDIUM_CPMM_PROGRAM_ID
);
return pda;
}
// ============================================
// Helper Functions
// ============================================
/**
* Compute swap amount for CPMM
*/
export function computeSwapAmount(
baseReserve: bigint,
quoteReserve: bigint,
isBaseIn: boolean,
amountIn: bigint,
slippageBasisPoints: bigint
): { amountOut: bigint; minAmountOut: bigint } {
// Apply trade fee (0.25%)
const feeRate = TRADE_FEE_RATE;
const feeDenominator = FEE_RATE_DENOMINATOR_VALUE;
const amountInAfterFee = amountIn - (amountIn * feeRate) / feeDenominator;
// Calculate output using constant product formula
let amountOut: bigint;
if (isBaseIn) {
// Selling base for quote: output = (quoteReserve * amountIn) / (baseReserve + amountIn)
const denominator = baseReserve + amountInAfterFee;
amountOut = (quoteReserve * amountInAfterFee) / denominator;
} else {
// Selling quote for base: output = (baseReserve * amountIn) / (quoteReserve + amountIn)
const denominator = quoteReserve + amountInAfterFee;
amountOut = (baseReserve * amountInAfterFee) / denominator;
}
// Apply slippage
const minAmountOut = amountOut - (amountOut * slippageBasisPoints) / 10000n;
return { amountOut, minAmountOut };
}
/**
* Create instructions to wrap SOL into WSOL
*/
export function createWsolInstructions(
payer: PublicKey,
amount: bigint
): Instruction[] {
const instructions: Instruction[] = [];
const wsolAta = getAssociatedTokenAddressSync(NATIVE_MINT, payer, true);
// Sync native (wrap SOL)
instructions.push(createSyncNativeInstruction(wsolAta));
return instructions;
}
/**
* Create instruction to close WSOL ATA and unwrap to SOL
*/
export function createCloseWsolInstruction(payer: PublicKey): Instruction {
const wsolAta = getAssociatedTokenAddressSync(NATIVE_MINT, payer, true);
return createCloseAccountInstruction(wsolAta, payer, payer, [], TOKEN_PROGRAM_ID);
}
/**
* Create WSOL ATA instruction
*/
export function createWsolAtaInstruction(payer: PublicKey): Instruction {
const wsolAta = getAssociatedTokenAddressSync(NATIVE_MINT, payer, true);
return createAssociatedTokenAccountInstruction(
payer,
wsolAta,
payer,
NATIVE_MINT,
TOKEN_PROGRAM_ID
);
}
// ============================================
// Types
// ============================================
export interface RaydiumCpmmParams {
poolState?: PublicKey;
ammConfig: PublicKey;
baseMint: PublicKey;
quoteMint: PublicKey;
baseTokenProgram: PublicKey;
quoteTokenProgram: PublicKey;
baseVault?: PublicKey;
quoteVault?: PublicKey;
baseReserve: bigint;
quoteReserve: bigint;
observationState?: PublicKey;
}
export interface BuildBuyInstructionsParams {
payer: Keypair | PublicKey;
outputMint: PublicKey;
inputAmount: bigint;
slippageBasisPoints?: bigint;
fixedOutputAmount?: bigint;
createInputMintAta?: boolean;
createOutputMintAta?: boolean;
closeInputMintAta?: boolean;
protocolParams: RaydiumCpmmParams;
}
export interface BuildSellInstructionsParams {
payer: Keypair | PublicKey;
inputMint: PublicKey;
inputAmount: bigint;
slippageBasisPoints?: bigint;
fixedOutputAmount?: bigint;
createOutputMintAta?: boolean;
closeOutputMintAta?: boolean;
closeInputMintAta?: boolean;
protocolParams: RaydiumCpmmParams;
}
// ============================================
// Instruction Builders
// ============================================
/**
* Build buy instructions for Raydium CPMM protocol
*/
export function buildBuyInstructions(
params: BuildBuyInstructionsParams
): Instruction[] {
const {
payer,
outputMint,
inputAmount,
slippageBasisPoints = 1000n,
fixedOutputAmount,
createInputMintAta = true,
createOutputMintAta = true,
closeInputMintAta = false,
protocolParams,
} = params;
if (inputAmount === 0n) {
throw new Error("Amount cannot be zero");
}
const payerPubkey = payer instanceof Keypair ? payer.publicKey : payer;
const instructions: Instruction[] = [];
const {
ammConfig,
baseMint,
quoteMint,
baseTokenProgram,
quoteTokenProgram,
baseVault,
quoteVault,
baseReserve,
quoteReserve,
observationState,
} = protocolParams;
// Check pool type
const isWsol = baseMint.equals(WSOL_TOKEN_ACCOUNT) || quoteMint.equals(WSOL_TOKEN_ACCOUNT);
const isUsdc = baseMint.equals(USDC_TOKEN_ACCOUNT) || quoteMint.equals(USDC_TOKEN_ACCOUNT);
if (!isWsol && !isUsdc) {
throw new Error("Pool must contain WSOL or USDC");
}
// Determine swap direction
const isBaseIn = baseMint.equals(WSOL_TOKEN_ACCOUNT) || baseMint.equals(USDC_TOKEN_ACCOUNT);
const mintTokenProgram = isBaseIn ? quoteTokenProgram : baseTokenProgram;
// Derive pool state
const poolState = protocolParams.poolState && !protocolParams.poolState.equals(PublicKey.default)
? protocolParams.poolState
: getPoolPda(ammConfig, baseMint, quoteMint);
// Calculate output
const swapResult = computeSwapAmount(
baseReserve,
quoteReserve,
isBaseIn,
inputAmount,
slippageBasisPoints
);
const minimumAmountOut = fixedOutputAmount || swapResult.minAmountOut;
// Determine input/output mints
const inputMint = isWsol ? WSOL_TOKEN_ACCOUNT : USDC_TOKEN_ACCOUNT;
// Derive user token accounts
const inputTokenAccount = getAssociatedTokenAddressSync(
inputMint,
payerPubkey,
true,
TOKEN_PROGRAM_ID
);
const outputTokenAccount = getAssociatedTokenAddressSync(
outputMint,
payerPubkey,
true,
mintTokenProgram
);
// Derive vault accounts
const inputVaultAccount = getVaultPda(poolState, inputMint);
const outputVaultAccount = getVaultPda(poolState, outputMint);
// Derive observation state
const observationStateAccount = observationState && !observationState.equals(PublicKey.default)
? observationState
: getObservationStatePda(poolState);
// Handle WSOL wrapping
if (createInputMintAta && isWsol) {
const wsolAta = getAssociatedTokenAddressSync(NATIVE_MINT, payerPubkey, true);
instructions.push(
createAssociatedTokenAccountInstruction(
payerPubkey,
wsolAta,
payerPubkey,
NATIVE_MINT,
TOKEN_PROGRAM_ID
)
);
instructions.push(createSyncNativeInstruction(wsolAta));
}
// Create output mint ATA if needed
if (createOutputMintAta) {
instructions.push(
createAssociatedTokenAccountInstruction(
payerPubkey,
outputTokenAccount,
payerPubkey,
outputMint,
mintTokenProgram
)
);
}
// Build instruction data
const data = Buffer.alloc(24);
SWAP_BASE_IN_DISCRIMINATOR.copy(data, 0);
data.writeBigUInt64LE(inputAmount, 8);
data.writeBigUInt64LE(minimumAmountOut, 16);
// Build accounts
const accounts: AccountMeta[] = [
{ pubkey: payerPubkey, isSigner: true, isWritable: true },
{ pubkey: AUTHORITY, isSigner: false, isWritable: false },
{ pubkey: ammConfig, isSigner: false, isWritable: false },
{ pubkey: poolState, isSigner: false, isWritable: true },
{ pubkey: inputTokenAccount, isSigner: false, isWritable: true },
{ pubkey: outputTokenAccount, isSigner: false, isWritable: true },
{ pubkey: inputVaultAccount, isSigner: false, isWritable: true },
{ pubkey: outputVaultAccount, isSigner: false, isWritable: true },
{ pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
{ pubkey: mintTokenProgram, isSigner: false, isWritable: false },
{ pubkey: inputMint, isSigner: false, isWritable: false },
{ pubkey: outputMint, isSigner: false, isWritable: false },
{ pubkey: observationStateAccount, isSigner: false, isWritable: true },
];
instructions.push(
new Instruction({
keys: accounts,
programId: RAYDIUM_CPMM_PROGRAM_ID,
data,
})
);
// Close WSOL ATA if requested
if (closeInputMintAta && isWsol) {
const wsolAta = getAssociatedTokenAddressSync(NATIVE_MINT, payerPubkey, true);
instructions.push(
createCloseAccountInstruction(wsolAta, payerPubkey, payerPubkey, [], TOKEN_PROGRAM_ID)
);
}
return instructions;
}
/**
* Build sell instructions for Raydium CPMM protocol
*/
export function buildSellInstructions(
params: BuildSellInstructionsParams
): Instruction[] {
const {
payer,
inputMint,
inputAmount,
slippageBasisPoints = 1000n,
fixedOutputAmount,
createOutputMintAta = true,
closeOutputMintAta = false,
closeInputMintAta = false,
protocolParams,
} = params;
if (inputAmount === 0n) {
throw new Error("Amount cannot be zero");
}
const payerPubkey = payer instanceof Keypair ? payer.publicKey : payer;
const instructions: Instruction[] = [];
const {
ammConfig,
baseMint,
quoteMint,
baseTokenProgram,
quoteTokenProgram,
baseReserve,
quoteReserve,
observationState,
} = protocolParams;
// Check pool type
const isWsol = baseMint.equals(WSOL_TOKEN_ACCOUNT) || quoteMint.equals(WSOL_TOKEN_ACCOUNT);
const isUsdc = baseMint.equals(USDC_TOKEN_ACCOUNT) || quoteMint.equals(USDC_TOKEN_ACCOUNT);
if (!isWsol && !isUsdc) {
throw new Error("Pool must contain WSOL or USDC");
}
// Determine swap direction
const isQuoteOut = quoteMint.equals(WSOL_TOKEN_ACCOUNT) || quoteMint.equals(USDC_TOKEN_ACCOUNT);
const mintTokenProgram = isQuoteOut ? baseTokenProgram : quoteTokenProgram;
// Derive pool state
const poolState = protocolParams.poolState && !protocolParams.poolState.equals(PublicKey.default)
? protocolParams.poolState
: getPoolPda(ammConfig, baseMint, quoteMint);
// Calculate output
const swapResult = computeSwapAmount(
baseReserve,
quoteReserve,
isQuoteOut,
inputAmount,
slippageBasisPoints
);
const minimumAmountOut = fixedOutputAmount || swapResult.minAmountOut;
// Determine output mint
const outputMint = isWsol ? WSOL_TOKEN_ACCOUNT : USDC_TOKEN_ACCOUNT;
// Derive user token accounts
const inputTokenAccount = getAssociatedTokenAddressSync(
inputMint,
payerPubkey,
true,
mintTokenProgram
);
const outputTokenAccount = getAssociatedTokenAddressSync(
outputMint,
payerPubkey,
true,
TOKEN_PROGRAM_ID
);
// Derive vault accounts
const inputVaultAccount = getVaultPda(poolState, inputMint);
const outputVaultAccount = getVaultPda(poolState, outputMint);
// Derive observation state
const observationStateAccount = observationState && !observationState.equals(PublicKey.default)
? observationState
: getObservationStatePda(poolState);
// Create WSOL ATA for receiving if needed
if (createOutputMintAta && isWsol) {
const wsolAta = getAssociatedTokenAddressSync(NATIVE_MINT, payerPubkey, true);
instructions.push(
createAssociatedTokenAccountInstruction(
payerPubkey,
wsolAta,
payerPubkey,
NATIVE_MINT,
TOKEN_PROGRAM_ID
)
);
}
// Build instruction data
const data = Buffer.alloc(24);
SWAP_BASE_IN_DISCRIMINATOR.copy(data, 0);
data.writeBigUInt64LE(inputAmount, 8);
data.writeBigUInt64LE(minimumAmountOut, 16);
// Build accounts
const accounts: AccountMeta[] = [
{ pubkey: payerPubkey, isSigner: true, isWritable: true },
{ pubkey: AUTHORITY, isSigner: false, isWritable: false },
{ pubkey: ammConfig, isSigner: false, isWritable: false },
{ pubkey: poolState, isSigner: false, isWritable: true },
{ pubkey: inputTokenAccount, isSigner: false, isWritable: true },
{ pubkey: outputTokenAccount, isSigner: false, isWritable: true },
{ pubkey: inputVaultAccount, isSigner: false, isWritable: true },
{ pubkey: outputVaultAccount, isSigner: false, isWritable: true },
{ pubkey: mintTokenProgram, isSigner: false, isWritable: false },
{ pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
{ pubkey: inputMint, isSigner: false, isWritable: false },
{ pubkey: outputMint, isSigner: false, isWritable: false },
{ pubkey: observationStateAccount, isSigner: false, isWritable: true },
];
instructions.push(
new Instruction({
keys: accounts,
programId: RAYDIUM_CPMM_PROGRAM_ID,
data,
})
);
// Close WSOL ATA if requested
if (closeOutputMintAta && isWsol) {
const wsolAta = getAssociatedTokenAddressSync(NATIVE_MINT, payerPubkey, true);
instructions.push(
createCloseAccountInstruction(wsolAta, payerPubkey, payerPubkey, [], TOKEN_PROGRAM_ID)
);
}
// Close input token ATA if requested
if (closeInputMintAta) {
instructions.push(
createCloseAccountInstruction(
inputTokenAccount,
payerPubkey,
payerPubkey,
[],
mintTokenProgram
)
);
}
return instructions;
}