chore: remove duplicate nodejs and python SDK subdirectories

These incomplete SDK implementations in the Rust repository were outdated
and redundant. The complete versions are maintained in separate repositories:
- sol-trade-sdk-ts (Node.js)
- sol-trade-sdk-python (Python)

🤖 Generated with [Qoder](https://qoder.com)
This commit is contained in:
0xfnzero
2026-04-10 15:37:53 +08:00
parent 971ef41fad
commit 8f2f99f3d9
20 changed files with 0 additions and 6460 deletions
-43
View File
@@ -1,43 +0,0 @@
{
"name": "sol-trade-sdk",
"version": "1.0.0",
"description": "Production-grade instruction builders for Solana DEX protocols",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"build": "tsc",
"clean": "rm -rf dist",
"prepublishOnly": "npm run build"
},
"keywords": [
"solana",
"dex",
"pumpfun",
"bonk",
"raydium",
"meteora",
"blockchain",
"crypto"
],
"author": "",
"license": "MIT",
"dependencies": {
"@solana/web3.js": "^1.95.0",
"@solana/spl-token": "^0.4.0"
},
"devDependencies": {
"typescript": "^5.0.0",
"@types/node": "^20.0.0"
},
"files": [
"dist",
"README.md"
],
"repository": {
"type": "git",
"url": "https://github.com/sol-trade/sol-trade-sdk-nodejs"
},
"engines": {
"node": ">=18.0.0"
}
}
-7
View File
@@ -1,7 +0,0 @@
/**
* Sol Trade SDK - Node.js
*
* Production-grade instruction builders for Solana DEX protocols.
*/
export * from "./instruction";
@@ -1,646 +0,0 @@
/**
* 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;
}
@@ -1,21 +0,0 @@
/**
* 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";
@@ -1,437 +0,0 @@
/**
* 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;
}
@@ -1,515 +0,0 @@
/**
* 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,
});
}
@@ -1,476 +0,0 @@
/**
* 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;
}
@@ -1,556 +0,0 @@
/**
* 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;
}
-29
View File
@@ -1,29 +0,0 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"lib": ["ES2020"],
"declaration": true,
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"noImplicitThis": true,
"alwaysStrict": true,
"noUnusedLocals": false,
"noUnusedParameters": false,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": false,
"inlineSourceMap": true,
"inlineSources": true,
"experimentalDecorators": true,
"strictPropertyInitialization": false,
"outDir": "./dist",
"rootDir": "./src",
"skipLibCheck": true,
"esModuleInterop": true,
"resolveJsonModule": true,
"moduleResolution": "node"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
-80
View File
@@ -1,80 +0,0 @@
# Solana Trading SDK - Python
Production-grade instruction builders for various Solana DEX protocols.
## Supported Protocols
- **PumpFun** - Bonding curve trading
- **PumpSwap** - Pump AMM trading
- **Bonk** - Bonk DEX trading
- **Raydium CPMM** - Raydium Concentrated Product Market Maker
- **Raydium AMM V4** - Raydium Automated Market Maker V4
- **Meteora DAMM V2** - Meteora Dynamic AMM V2
## Installation
```bash
pip install sol-trade-sdk
```
## Quick Start
```python
from solders.pubkey import Pubkey
from sol_trade_sdk.instruction import (
PumpFunParams,
build_pumpfun_buy_instructions,
get_bonding_curve_pda,
)
# Set up parameters
payer = Pubkey.from_string("YOUR_WALLET_ADDRESS")
output_mint = Pubkey.from_string("TOKEN_MINT_ADDRESS")
# Build parameters
params = PumpFunParams(
virtual_token_reserves=1000000000000,
virtual_sol_reserves=30000000000,
real_token_reserves=793000000000000,
creator_vault=Pubkey.from_string("CREATOR_VAULT_ADDRESS"),
token_program=Pubkey.from_string("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"),
)
# Build buy instructions
instructions = build_pumpfun_buy_instructions(
payer=payer,
output_mint=output_mint,
input_amount=1000000000, # 1 SOL
params=params,
slippage_bps=500, # 5%
)
```
## Features
- All program IDs and constants from Rust implementations
- Correct discriminators from Rust (not placeholder values)
- PDA derivation functions using solders.pubkey
- `build_buy_instructions` function for each protocol
- `build_sell_instructions` function for each protocol
- WSOL handling (wrap/close)
- ATA creation support
- Slippage calculation
## Development
```bash
# Install dev dependencies
pip install -e ".[dev]"
# Run tests
pytest
# Format code
black src/
isort src/
```
## License
MIT License
-53
View File
@@ -1,53 +0,0 @@
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "sol-trade-sdk"
version = "1.0.0"
description = "Production-grade instruction builders for Solana DEX protocols"
readme = "README.md"
requires-python = ">=3.8"
license = "MIT"
authors = [
{ name = "Sol Trade SDK Team" }
]
classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
]
dependencies = [
"solders>=0.21.0",
]
[project.optional-dependencies]
dev = [
"pytest>=7.0.0",
"pytest-asyncio>=0.21.0",
"black>=23.0.0",
"isort>=5.0.0",
"mypy>=1.0.0",
]
[tool.hatch.build.targets.wheel]
packages = ["src/sol_trade_sdk"]
[tool.black]
line-length = 100
target-version = ["py38", "py39", "py310", "py311", "py312"]
[tool.isort]
profile = "black"
line_length = 100
[tool.mypy]
python_version = "3.8"
warn_return_any = true
warn_unused_configs = true
-9
View File
@@ -1,9 +0,0 @@
"""
Solana Trading SDK Python
Production-grade instruction builders for various Solana DEX protocols.
"""
from .instruction import *
__version__ = "1.0.0"
@@ -1,366 +0,0 @@
"""
Solana Trading SDK - Instruction Builders
Production-grade instruction builders for various Solana DEX protocols:
- PumpFun
- PumpSwap
- Bonk
- Raydium CPMM
- Raydium AMM V4
- Meteora DAMM V2
Each module provides:
- Program IDs and constants
- Instruction discriminators
- PDA derivation functions
- build_buy_instructions function
- build_sell_instructions function
- Protocol-specific parameter dataclasses
"""
from .common import (
# Program IDs
SYSTEM_PROGRAM,
TOKEN_PROGRAM,
TOKEN_PROGRAM_2022,
ASSOCIATED_TOKEN_PROGRAM,
# Token Mints
SOL_TOKEN_ACCOUNT,
WSOL_TOKEN_ACCOUNT,
USDC_TOKEN_ACCOUNT,
USD1_TOKEN_ACCOUNT,
# Constants
DEFAULT_SLIPPAGE,
# Utility Functions
get_associated_token_address,
create_associated_token_account_idempotent_instruction,
create_wsol_account_instruction,
close_wsol_account_instruction,
close_token_account_instruction,
handle_wsol,
create_wsol_ata,
close_wsol,
# Calculation Functions
calculate_with_slippage_buy,
calculate_with_slippage_sell,
)
from .pumpfun_builder import (
# Program IDs and Constants
PUMPFUN_PROGRAM_ID,
FEE_RECIPIENT as PUMPFUN_FEE_RECIPIENT,
GLOBAL_ACCOUNT as PUMPFUN_GLOBAL_ACCOUNT,
EVENT_AUTHORITY as PUMPFUN_EVENT_AUTHORITY,
AUTHORITY as PUMPFUN_AUTHORITY,
FEE_PROGRAM as PUMPFUN_FEE_PROGRAM,
GLOBAL_VOLUME_ACCUMULATOR as PUMPFUN_GLOBAL_VOLUME_ACCUMULATOR,
FEE_CONFIG as PUMPFUN_FEE_CONFIG,
MAYHEM_FEE_RECIPIENTS as PUMPFUN_MAYHEM_FEE_RECIPIENTS,
# Discriminators
BUY_DISCRIMINATOR as PUMPFUN_BUY_DISCRIMINATOR,
BUY_EXACT_SOL_IN_DISCRIMINATOR as PUMPFUN_BUY_EXACT_SOL_IN_DISCRIMINATOR,
SELL_DISCRIMINATOR as PUMPFUN_SELL_DISCRIMINATOR,
CLAIM_CASHBACK_DISCRIMINATOR as PUMPFUN_CLAIM_CASHBACK_DISCRIMINATOR,
# PDA Functions
get_bonding_curve_pda,
get_bonding_curve_v2_pda,
get_creator_vault_pda,
get_user_volume_accumulator_pda as get_pumpfun_user_volume_accumulator_pda,
get_creator,
get_mayhem_fee_recipient_random,
# Params
PumpFunParams,
# Calculation Functions
get_buy_token_amount_from_sol_amount,
get_sell_sol_amount_from_token_amount,
# Instruction Builders
build_buy_instructions as build_pumpfun_buy_instructions,
build_sell_instructions as build_pumpfun_sell_instructions,
claim_cashback_pumpfun_instruction,
)
from .pumpswap_builder import (
# Program IDs and Constants
PUMPSWAP_PROGRAM_ID,
FEE_RECIPIENT as PUMPSWAP_FEE_RECIPIENT,
GLOBAL_ACCOUNT as PUMPSWAP_GLOBAL_ACCOUNT,
EVENT_AUTHORITY as PUMPSWAP_EVENT_AUTHORITY,
ASSOCIATED_TOKEN_PROGRAM as PUMPSWAP_ASSOCIATED_TOKEN_PROGRAM,
PUMP_PROGRAM_ID,
FEE_PROGRAM as PUMPSWAP_FEE_PROGRAM,
GLOBAL_VOLUME_ACCUMULATOR as PUMPSWAP_GLOBAL_VOLUME_ACCUMULATOR,
FEE_CONFIG as PUMPSWAP_FEE_CONFIG,
DEFAULT_COIN_CREATOR_VAULT_AUTHORITY,
MAYHEM_FEE_RECIPIENTS as PUMPSWAP_MAYHEM_FEE_RECIPIENTS,
LP_FEE_BASIS_POINTS,
PROTOCOL_FEE_BASIS_POINTS,
COIN_CREATOR_FEE_BASIS_POINTS,
# Discriminators
BUY_DISCRIMINATOR as PUMPSWAP_BUY_DISCRIMINATOR,
BUY_EXACT_QUOTE_IN_DISCRIMINATOR,
SELL_DISCRIMINATOR as PUMPSWAP_SELL_DISCRIMINATOR,
CLAIM_CASHBACK_DISCRIMINATOR as PUMPSWAP_CLAIM_CASHBACK_DISCRIMINATOR,
# PDA Functions
get_pool_v2_pda,
get_pump_pool_authority_pda,
get_canonical_pool_pda,
get_user_volume_accumulator_pda as get_pumpswap_user_volume_accumulator_pda,
get_user_volume_accumulator_wsol_ata,
get_user_volume_accumulator_quote_ata,
coin_creator_vault_authority,
coin_creator_vault_ata,
fee_recipient_ata,
get_mayhem_fee_recipient_random as get_pumpswap_mayhem_fee_recipient_random,
# Params
PumpSwapParams,
# Calculation Functions
buy_quote_input_internal,
sell_base_input_internal,
# Instruction Builders
build_buy_instructions as build_pumpswap_buy_instructions,
build_sell_instructions as build_pumpswap_sell_instructions,
claim_cashback_pumpswap_instruction,
)
from .bonk_builder import (
# Program IDs and Constants
BONK_PROGRAM_ID,
AUTHORITY as BONK_AUTHORITY,
GLOBAL_CONFIG,
USD1_GLOBAL_CONFIG,
EVENT_AUTHORITY as BONK_EVENT_AUTHORITY,
PLATFORM_FEE_RATE,
PROTOCOL_FEE_RATE as BONK_PROTOCOL_FEE_RATE,
SHARE_FEE_RATE,
# Discriminators
BUY_EXACT_IN_DISCRIMINATOR,
SELL_EXACT_IN_DISCRIMINATOR,
# PDA Functions
get_pool_pda as get_bonk_pool_pda,
get_vault_pda as get_bonk_vault_pda,
get_platform_associated_account,
get_creator_associated_account,
# Params
BonkParams,
# Calculation Functions
get_amount_in_net,
get_amount_out as get_bonk_amount_out,
get_buy_token_amount_from_sol_amount as get_bonk_buy_token_amount_from_sol_amount,
get_sell_sol_amount_from_token_amount as get_bonk_sell_sol_amount_from_token_amount,
# Instruction Builders
build_buy_instructions as build_bonk_buy_instructions,
build_sell_instructions as build_bonk_sell_instructions,
)
from .raydium_cpmm_builder import (
# Program IDs and Constants
RAYDIUM_CPMM_PROGRAM_ID,
AUTHORITY as RAYDIUM_CPMM_AUTHORITY,
FEE_RATE_DENOMINATOR_VALUE,
TRADE_FEE_RATE,
CREATOR_FEE_RATE,
PROTOCOL_FEE_RATE as RAYDIUM_CPMM_PROTOCOL_FEE_RATE,
FUND_FEE_RATE,
# Discriminators
SWAP_BASE_IN_DISCRIMINATOR as RAYDIUM_CPMM_SWAP_BASE_IN_DISCRIMINATOR,
SWAP_BASE_OUT_DISCRIMINATOR as RAYDIUM_CPMM_SWAP_BASE_OUT_DISCRIMINATOR,
# PDA Functions
get_pool_pda as get_raydium_cpmm_pool_pda,
get_vault_pda as get_raydium_cpmm_vault_pda,
get_observation_state_pda,
# Params
RaydiumCpmmParams,
# Calculation Functions
compute_swap_amount as compute_raydium_cpmm_swap_amount,
# Instruction Builders
build_buy_instructions as build_raydium_cpmm_buy_instructions,
build_sell_instructions as build_raydium_cpmm_sell_instructions,
)
from .raydium_amm_v4_builder import (
# Program IDs and Constants
RAYDIUM_AMM_V4_PROGRAM_ID,
AUTHORITY as RAYDIUM_AMM_V4_AUTHORITY,
TRADE_FEE_NUMERATOR,
TRADE_FEE_DENOMINATOR,
SWAP_FEE_NUMERATOR,
SWAP_FEE_DENOMINATOR,
# Discriminators
SWAP_BASE_IN_DISCRIMINATOR as RAYDIUM_AMM_V4_SWAP_BASE_IN_DISCRIMINATOR,
SWAP_BASE_OUT_DISCRIMINATOR as RAYDIUM_AMM_V4_SWAP_BASE_OUT_DISCRIMINATOR,
# Params
RaydiumAmmV4Params,
# Calculation Functions
compute_swap_amount as compute_raydium_amm_v4_swap_amount,
# Instruction Builders
build_buy_instructions as build_raydium_amm_v4_buy_instructions,
build_sell_instructions as build_raydium_amm_v4_sell_instructions,
)
from .meteora_damm_v2_builder import (
# Program IDs and Constants
METEORA_DAMM_V2_PROGRAM_ID,
AUTHORITY as METEORA_DAMM_V2_AUTHORITY,
# Discriminators
SWAP_DISCRIMINATOR as METEORA_DAMM_V2_SWAP_DISCRIMINATOR,
# PDA Functions
get_event_authority_pda,
# Params
MeteoraDammV2Params,
# Instruction Builders
build_buy_instructions as build_meteora_damm_v2_buy_instructions,
build_sell_instructions as build_meteora_damm_v2_sell_instructions,
)
# Version
__version__ = "1.0.0"
__all__ = [
# Common
"SYSTEM_PROGRAM",
"TOKEN_PROGRAM",
"TOKEN_PROGRAM_2022",
"ASSOCIATED_TOKEN_PROGRAM",
"SOL_TOKEN_ACCOUNT",
"WSOL_TOKEN_ACCOUNT",
"USDC_TOKEN_ACCOUNT",
"USD1_TOKEN_ACCOUNT",
"DEFAULT_SLIPPAGE",
"get_associated_token_address",
"create_associated_token_account_idempotent_instruction",
"create_wsol_account_instruction",
"close_wsol_account_instruction",
"close_token_account_instruction",
"handle_wsol",
"create_wsol_ata",
"close_wsol",
"calculate_with_slippage_buy",
"calculate_with_slippage_sell",
# PumpFun
"PUMPFUN_PROGRAM_ID",
"PUMPFUN_FEE_RECIPIENT",
"PUMPFUN_GLOBAL_ACCOUNT",
"PUMPFUN_EVENT_AUTHORITY",
"PUMPFUN_AUTHORITY",
"PUMPFUN_FEE_PROGRAM",
"PUMPFUN_GLOBAL_VOLUME_ACCUMULATOR",
"PUMPFUN_FEE_CONFIG",
"PUMPFUN_MAYHEM_FEE_RECIPIENTS",
"PUMPFUN_BUY_DISCRIMINATOR",
"PUMPFUN_BUY_EXACT_SOL_IN_DISCRIMINATOR",
"PUMPFUN_SELL_DISCRIMINATOR",
"PUMPFUN_CLAIM_CASHBACK_DISCRIMINATOR",
"get_bonding_curve_pda",
"get_bonding_curve_v2_pda",
"get_creator_vault_pda",
"get_pumpfun_user_volume_accumulator_pda",
"get_creator",
"get_mayhem_fee_recipient_random",
"PumpFunParams",
"get_buy_token_amount_from_sol_amount",
"get_sell_sol_amount_from_token_amount",
"build_pumpfun_buy_instructions",
"build_pumpfun_sell_instructions",
"claim_cashback_pumpfun_instruction",
# PumpSwap
"PUMPSWAP_PROGRAM_ID",
"PUMPSWAP_FEE_RECIPIENT",
"PUMPSWAP_GLOBAL_ACCOUNT",
"PUMPSWAP_EVENT_AUTHORITY",
"PUMPSWAP_ASSOCIATED_TOKEN_PROGRAM",
"PUMP_PROGRAM_ID",
"PUMPSWAP_FEE_PROGRAM",
"PUMPSWAP_GLOBAL_VOLUME_ACCUMULATOR",
"PUMPSWAP_FEE_CONFIG",
"DEFAULT_COIN_CREATOR_VAULT_AUTHORITY",
"PUMPSWAP_MAYHEM_FEE_RECIPIENTS",
"LP_FEE_BASIS_POINTS",
"PROTOCOL_FEE_BASIS_POINTS",
"COIN_CREATOR_FEE_BASIS_POINTS",
"PUMPSWAP_BUY_DISCRIMINATOR",
"BUY_EXACT_QUOTE_IN_DISCRIMINATOR",
"PUMPSWAP_SELL_DISCRIMINATOR",
"PUMPSWAP_CLAIM_CASHBACK_DISCRIMINATOR",
"get_pool_v2_pda",
"get_pump_pool_authority_pda",
"get_canonical_pool_pda",
"get_pumpswap_user_volume_accumulator_pda",
"get_user_volume_accumulator_wsol_ata",
"get_user_volume_accumulator_quote_ata",
"coin_creator_vault_authority",
"coin_creator_vault_ata",
"fee_recipient_ata",
"get_pumpswap_mayhem_fee_recipient_random",
"PumpSwapParams",
"buy_quote_input_internal",
"sell_base_input_internal",
"build_pumpswap_buy_instructions",
"build_pumpswap_sell_instructions",
"claim_cashback_pumpswap_instruction",
# Bonk
"BONK_PROGRAM_ID",
"BONK_AUTHORITY",
"GLOBAL_CONFIG",
"USD1_GLOBAL_CONFIG",
"BONK_EVENT_AUTHORITY",
"PLATFORM_FEE_RATE",
"BONK_PROTOCOL_FEE_RATE",
"SHARE_FEE_RATE",
"BUY_EXACT_IN_DISCRIMINATOR",
"SELL_EXACT_IN_DISCRIMINATOR",
"get_bonk_pool_pda",
"get_bonk_vault_pda",
"get_platform_associated_account",
"get_creator_associated_account",
"BonkParams",
"get_amount_in_net",
"get_bonk_amount_out",
"get_bonk_buy_token_amount_from_sol_amount",
"get_bonk_sell_sol_amount_from_token_amount",
"build_bonk_buy_instructions",
"build_bonk_sell_instructions",
# Raydium CPMM
"RAYDIUM_CPMM_PROGRAM_ID",
"RAYDIUM_CPMM_AUTHORITY",
"FEE_RATE_DENOMINATOR_VALUE",
"TRADE_FEE_RATE",
"CREATOR_FEE_RATE",
"RAYDIUM_CPMM_PROTOCOL_FEE_RATE",
"FUND_FEE_RATE",
"RAYDIUM_CPMM_SWAP_BASE_IN_DISCRIMINATOR",
"RAYDIUM_CPMM_SWAP_BASE_OUT_DISCRIMINATOR",
"get_raydium_cpmm_pool_pda",
"get_raydium_cpmm_vault_pda",
"get_observation_state_pda",
"RaydiumCpmmParams",
"compute_raydium_cpmm_swap_amount",
"build_raydium_cpmm_buy_instructions",
"build_raydium_cpmm_sell_instructions",
# Raydium AMM V4
"RAYDIUM_AMM_V4_PROGRAM_ID",
"RAYDIUM_AMM_V4_AUTHORITY",
"TRADE_FEE_NUMERATOR",
"TRADE_FEE_DENOMINATOR",
"SWAP_FEE_NUMERATOR",
"SWAP_FEE_DENOMINATOR",
"RAYDIUM_AMM_V4_SWAP_BASE_IN_DISCRIMINATOR",
"RAYDIUM_AMM_V4_SWAP_BASE_OUT_DISCRIMINATOR",
"RaydiumAmmV4Params",
"compute_raydium_amm_v4_swap_amount",
"build_raydium_amm_v4_buy_instructions",
"build_raydium_amm_v4_sell_instructions",
# Meteora DAMM V2
"METEORA_DAMM_V2_PROGRAM_ID",
"METEORA_DAMM_V2_AUTHORITY",
"METEORA_DAMM_V2_SWAP_DISCRIMINATOR",
"get_event_authority_pda",
"MeteoraDammV2Params",
"build_meteora_damm_v2_buy_instructions",
"build_meteora_damm_v2_sell_instructions",
]
@@ -1,533 +0,0 @@
"""
Bonk instruction builder for Solana trading SDK.
Production-grade implementation with all constants, discriminators, and PDA derivation functions.
"""
from typing import List, Optional, Tuple
from dataclasses import dataclass
from solders.pubkey import Pubkey
from solders.instruction import Instruction, AccountMeta
import struct
from .common import (
SYSTEM_PROGRAM,
TOKEN_PROGRAM,
WSOL_TOKEN_ACCOUNT,
USD1_TOKEN_ACCOUNT,
USDC_TOKEN_ACCOUNT,
DEFAULT_SLIPPAGE,
get_associated_token_address,
create_associated_token_account_idempotent_instruction,
handle_wsol,
close_wsol,
close_token_account_instruction,
calculate_with_slippage_sell,
)
# ============================================
# Bonk Program ID
# ============================================
BONK_PROGRAM_ID: Pubkey = Pubkey.from_string("LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj")
# ============================================
# Bonk Constants
# ============================================
# Authority
AUTHORITY: Pubkey = Pubkey.from_string("WLHv2UAZm6z4KyaaELi5pjdbJh6RESMva1Rnn8pJVVh")
# Global Config (SOL pools)
GLOBAL_CONFIG: Pubkey = Pubkey.from_string("6s1xP3hpbAfFoNtUNF8mfHsjr2Bd97JxFJRWLbL6aHuX")
# USD1 Global Config
USD1_GLOBAL_CONFIG: Pubkey = Pubkey.from_string("EPiZbnrThjyLnoQ6QQzkxeFqyL5uyg9RzNHHAudUPxBz")
# Event Authority
EVENT_AUTHORITY: Pubkey = Pubkey.from_string("2DPAtwB8L12vrMRExbLuyGnC7n2J5LNoZQSejeQGpwkr")
# Fee Rates
PLATFORM_FEE_RATE: int = 100 # 1%
PROTOCOL_FEE_RATE: int = 25 # 0.25%
SHARE_FEE_RATE: int = 0 # 0%
# ============================================
# Instruction Discriminators
# ============================================
BUY_EXACT_IN_DISCRIMINATOR: bytes = bytes([250, 234, 13, 123, 213, 156, 19, 236])
SELL_EXACT_IN_DISCRIMINATOR: bytes = bytes([149, 39, 222, 155, 211, 124, 152, 26])
# ============================================
# Seeds
# ============================================
POOL_SEED = b"pool"
POOL_VAULT_SEED = b"pool_vault"
# ============================================
# PDA Derivation Functions
# ============================================
def get_pool_pda(base_mint: Pubkey, quote_mint: Pubkey) -> Pubkey:
"""
Derive the pool PDA for a given base and quote mint.
Seeds: ["pool", base_mint, quote_mint]
"""
seeds = [POOL_SEED, bytes(base_mint), bytes(quote_mint)]
(pda, _) = Pubkey.find_program_address(seeds, BONK_PROGRAM_ID)
return pda
def get_vault_pda(pool_state: Pubkey, mint: Pubkey) -> Pubkey:
"""
Derive the vault PDA for a given pool and mint.
Seeds: ["pool_vault", pool_state, mint]
"""
seeds = [POOL_VAULT_SEED, bytes(pool_state), bytes(mint)]
(pda, _) = Pubkey.find_program_address(seeds, BONK_PROGRAM_ID)
return pda
def get_platform_associated_account(platform_config: Pubkey) -> Pubkey:
"""
Derive the platform associated account PDA.
Seeds: [platform_config, WSOL_TOKEN_ACCOUNT]
"""
seeds = [bytes(platform_config), bytes(WSOL_TOKEN_ACCOUNT)]
(pda, _) = Pubkey.find_program_address(seeds, BONK_PROGRAM_ID)
return pda
def get_creator_associated_account(creator: Pubkey) -> Pubkey:
"""
Derive the creator associated account PDA.
Seeds: [creator, WSOL_TOKEN_ACCOUNT]
"""
seeds = [bytes(creator), bytes(WSOL_TOKEN_ACCOUNT)]
(pda, _) = Pubkey.find_program_address(seeds, BONK_PROGRAM_ID)
return pda
# ============================================
# Bonk Parameters Dataclass
# ============================================
@dataclass
class BonkParams:
"""Parameters for Bonk protocol trading."""
virtual_base: int = 0
virtual_quote: int = 0
real_base: int = 0
real_quote: int = 0
pool_state: Optional[Pubkey] = None # If None, will derive from mints
base_vault: Optional[Pubkey] = None
quote_vault: Optional[Pubkey] = None
mint_token_program: Pubkey = TOKEN_PROGRAM
platform_config: Pubkey = Pubkey.from_string("11111111111111111111111111111111")
platform_associated_account: Pubkey = Pubkey.from_string("11111111111111111111111111111111")
creator_associated_account: Pubkey = Pubkey.from_string("11111111111111111111111111111111")
global_config: Pubkey = GLOBAL_CONFIG # Use USD1_GLOBAL_CONFIG for USD1 pools
@property
def is_usd1_pool(self) -> bool:
"""Check if this is a USD1 pool."""
return self.global_config == USD1_GLOBAL_CONFIG
# ============================================
# Bonk Calculation Functions
# ============================================
def get_amount_in_net(
amount_in: int,
protocol_fee_rate: int,
platform_fee_rate: int,
share_fee_rate: int,
) -> int:
"""Calculate net input amount after fees."""
protocol_fee = (amount_in * protocol_fee_rate) // 10000
platform_fee = (amount_in * platform_fee_rate) // 10000
share_fee = (amount_in * share_fee_rate) // 10000
return amount_in - protocol_fee - platform_fee - share_fee
def get_amount_out(
amount_in: int,
protocol_fee_rate: int,
platform_fee_rate: int,
share_fee_rate: int,
virtual_base: int,
virtual_quote: int,
real_base: int,
real_quote: int,
slippage_bps: int,
) -> int:
"""
Calculate output amount for a given input amount on Bonk.
"""
amount_in_net = get_amount_in_net(
amount_in, protocol_fee_rate, platform_fee_rate, share_fee_rate
)
input_reserve = virtual_quote + real_quote
output_reserve = virtual_base - real_base
numerator = amount_in_net * output_reserve
denominator = input_reserve + amount_in_net
amount_out = numerator // denominator
# Apply slippage
amount_out = amount_out - (amount_out * slippage_bps) // 10000
return amount_out
def get_buy_token_amount_from_sol_amount(
amount_in: int,
virtual_base: int,
virtual_quote: int,
real_base: int,
real_quote: int,
slippage_bps: int,
) -> int:
"""
Calculate the token amount received for a given SOL amount on Bonk.
"""
return get_amount_out(
amount_in,
PROTOCOL_FEE_RATE,
PLATFORM_FEE_RATE,
SHARE_FEE_RATE,
virtual_base,
virtual_quote,
real_base,
real_quote,
slippage_bps,
)
def get_sell_sol_amount_from_token_amount(
amount_in: int,
virtual_base: int,
virtual_quote: int,
real_base: int,
real_quote: int,
slippage_bps: int,
) -> int:
"""
Calculate the SOL amount received for a given token amount on Bonk.
For sell, we swap base -> quote.
"""
# For sell: base is input, quote is output
# So we swap virtual_base with virtual_quote roles
return get_amount_out(
amount_in,
PROTOCOL_FEE_RATE,
PLATFORM_FEE_RATE,
SHARE_FEE_RATE,
virtual_quote, # Swapped
virtual_base, # Swapped
real_quote, # Swapped
real_base, # Swapped
slippage_bps,
)
# ============================================
# Build Buy Instructions
# ============================================
def build_buy_instructions(
payer: Pubkey,
output_mint: Pubkey,
input_amount: int,
params: BonkParams,
slippage_bps: int = DEFAULT_SLIPPAGE,
create_input_ata: bool = True,
create_output_ata: bool = True,
close_input_ata: bool = False,
fixed_output_amount: Optional[int] = None,
) -> List[Instruction]:
"""
Build Bonk buy instructions.
Args:
payer: The wallet paying for the swap
output_mint: The token mint to buy
input_amount: Amount of SOL/quote to spend
params: Bonk protocol parameters
slippage_bps: Slippage tolerance in basis points
create_input_ata: Whether to create WSOL ATA if needed
create_output_ata: Whether to create output token ATA if needed
close_input_ata: Whether to close WSOL ATA after swap
fixed_output_amount: If set, use this as exact output amount
Returns:
List of instructions for the buy operation
"""
if input_amount == 0:
raise ValueError("Amount cannot be zero")
instructions = []
is_usd1_pool = params.is_usd1_pool
# Determine quote mint (WSOL or USD1)
quote_mint = USD1_TOKEN_ACCOUNT if is_usd1_pool else WSOL_TOKEN_ACCOUNT
quote_token_mint_meta = AccountMeta(quote_mint, False, False)
# Get pool state
pool_state = params.pool_state
if pool_state is None:
pool_state = get_pool_pda(output_mint, quote_mint)
# Get global config
global_config = USD1_GLOBAL_CONFIG if is_usd1_pool else GLOBAL_CONFIG
global_config_meta = AccountMeta(global_config, False, False)
# Calculate minimum output amount
if fixed_output_amount is not None:
minimum_amount_out = fixed_output_amount
else:
minimum_amount_out = get_buy_token_amount_from_sol_amount(
input_amount,
params.virtual_base,
params.virtual_quote,
params.real_base,
params.real_quote,
slippage_bps,
)
share_fee_rate = 0
# Get user token accounts
user_base_token_account = get_associated_token_address(
payer, output_mint, params.mint_token_program
)
user_quote_token_account = get_associated_token_address(
payer, quote_mint, TOKEN_PROGRAM
)
# Get vaults
base_vault = params.base_vault
if base_vault is None:
base_vault = get_vault_pda(pool_state, output_mint)
quote_vault = params.quote_vault
if quote_vault is None:
quote_vault = get_vault_pda(pool_state, quote_mint)
# Handle WSOL for non-USD1 pools
if create_input_ata and not is_usd1_pool:
instructions.extend(handle_wsol(payer, input_amount))
# Create output ATA if needed
if create_output_ata:
instructions.append(
create_associated_token_account_idempotent_instruction(
payer, payer, output_mint, params.mint_token_program
)
)
# Build instruction data
data = BUY_EXACT_IN_DISCRIMINATOR + struct.pack("<QQQ", input_amount, minimum_amount_out, share_fee_rate)
# Build accounts list
accounts = [
AccountMeta(payer, True, True), # payer (signer, writable)
AccountMeta(AUTHORITY, False, False), # authority (readonly)
global_config_meta, # global_config (readonly)
AccountMeta(params.platform_config, False, False), # platform_config (readonly)
AccountMeta(pool_state, False, True), # pool_state (writable)
AccountMeta(user_base_token_account, False, True), # user_base_token_account (writable)
AccountMeta(user_quote_token_account, False, True), # user_quote_token_account (writable)
AccountMeta(base_vault, False, True), # base_vault (writable)
AccountMeta(quote_vault, False, True), # quote_vault (writable)
AccountMeta(output_mint, False, False), # base_token_mint (readonly)
quote_token_mint_meta, # quote_token_mint (readonly)
AccountMeta(params.mint_token_program, False, False), # base_token_program (readonly)
AccountMeta(TOKEN_PROGRAM, False, False), # quote_token_program (readonly)
AccountMeta(EVENT_AUTHORITY, False, False), # event_authority (readonly)
AccountMeta(BONK_PROGRAM_ID, False, False), # program (readonly)
AccountMeta(SYSTEM_PROGRAM, False, False), # system_program (readonly)
AccountMeta(params.platform_associated_account, False, True), # platform_associated_account (writable)
AccountMeta(params.creator_associated_account, False, True), # creator_associated_account (writable)
]
instructions.append(Instruction(BONK_PROGRAM_ID, data, accounts))
# Close WSOL ATA if requested
if close_input_ata and not is_usd1_pool:
instructions.extend(close_wsol(payer))
return instructions
# ============================================
# Build Sell Instructions
# ============================================
def build_sell_instructions(
payer: Pubkey,
input_mint: Pubkey,
input_amount: int,
params: BonkParams,
slippage_bps: int = DEFAULT_SLIPPAGE,
create_output_ata: bool = True,
close_output_ata: bool = False,
close_input_ata: bool = False,
fixed_output_amount: Optional[int] = None,
) -> List[Instruction]:
"""
Build Bonk sell instructions.
Args:
payer: The wallet paying for the swap
input_mint: The token mint to sell
input_amount: Amount of tokens to sell
params: Bonk protocol parameters
slippage_bps: Slippage tolerance in basis points
create_output_ata: Whether to create WSOL ATA for receiving SOL
close_output_ata: Whether to close WSOL ATA after swap
close_input_ata: Whether to close token ATA after swap
fixed_output_amount: If set, use this as exact output amount
Returns:
List of instructions for the sell operation
"""
if input_amount == 0:
raise ValueError("Amount cannot be zero")
instructions = []
is_usd1_pool = params.is_usd1_pool
# Determine quote mint (WSOL or USD1)
quote_mint = USD1_TOKEN_ACCOUNT if is_usd1_pool else WSOL_TOKEN_ACCOUNT
quote_token_mint_meta = AccountMeta(quote_mint, False, False)
# Get pool state
pool_state = params.pool_state
if pool_state is None:
pool_state = get_pool_pda(input_mint, quote_mint)
# Get global config
global_config = USD1_GLOBAL_CONFIG if is_usd1_pool else GLOBAL_CONFIG
global_config_meta = AccountMeta(global_config, False, False)
# Calculate minimum output amount
share_fee_rate = 0
if fixed_output_amount is not None:
minimum_amount_out = fixed_output_amount
else:
minimum_amount_out = get_sell_sol_amount_from_token_amount(
input_amount,
params.virtual_base,
params.virtual_quote,
params.real_base,
params.real_quote,
slippage_bps,
)
# Get user token accounts
user_base_token_account = get_associated_token_address(
payer, input_mint, params.mint_token_program
)
user_quote_token_account = get_associated_token_address(
payer, quote_mint, TOKEN_PROGRAM
)
# Get vaults
base_vault = params.base_vault
if base_vault is None:
base_vault = get_vault_pda(pool_state, input_mint)
quote_vault = params.quote_vault
if quote_vault is None:
quote_vault = get_vault_pda(pool_state, quote_mint)
# Create WSOL ATA if needed for receiving SOL
if create_output_ata and not is_usd1_pool:
wsol_ata = get_associated_token_address(payer, WSOL_TOKEN_ACCOUNT, TOKEN_PROGRAM)
instructions.append(
create_associated_token_account_idempotent_instruction(
payer, payer, WSOL_TOKEN_ACCOUNT, TOKEN_PROGRAM
)
)
# Build instruction data
data = SELL_EXACT_IN_DISCRIMINATOR + struct.pack("<QQQ", input_amount, minimum_amount_out, share_fee_rate)
# Build accounts list
accounts = [
AccountMeta(payer, True, True), # payer (signer, writable)
AccountMeta(AUTHORITY, False, False), # authority (readonly)
global_config_meta, # global_config (readonly)
AccountMeta(params.platform_config, False, False), # platform_config (readonly)
AccountMeta(pool_state, False, True), # pool_state (writable)
AccountMeta(user_base_token_account, False, True), # user_base_token_account (writable)
AccountMeta(user_quote_token_account, False, True), # user_quote_token_account (writable)
AccountMeta(base_vault, False, True), # base_vault (writable)
AccountMeta(quote_vault, False, True), # quote_vault (writable)
AccountMeta(input_mint, False, False), # base_token_mint (readonly)
quote_token_mint_meta, # quote_token_mint (readonly)
AccountMeta(params.mint_token_program, False, False), # base_token_program (readonly)
AccountMeta(TOKEN_PROGRAM, False, False), # quote_token_program (readonly)
AccountMeta(EVENT_AUTHORITY, False, False), # event_authority (readonly)
AccountMeta(BONK_PROGRAM_ID, False, False), # program (readonly)
AccountMeta(SYSTEM_PROGRAM, False, False), # system_program (readonly)
AccountMeta(params.platform_associated_account, False, True), # platform_associated_account (writable)
AccountMeta(params.creator_associated_account, False, True), # creator_associated_account (writable)
]
instructions.append(Instruction(BONK_PROGRAM_ID, data, accounts))
# Close WSOL ATA if requested
if close_output_ata and not is_usd1_pool:
instructions.extend(close_wsol(payer))
# Close token ATA if requested
if close_input_ata:
instructions.append(
close_token_account_instruction(
params.mint_token_program,
user_base_token_account,
payer,
payer,
)
)
return instructions
# ============================================
# Exports
# ============================================
__all__ = [
# Program IDs and Constants
"BONK_PROGRAM_ID",
"AUTHORITY",
"GLOBAL_CONFIG",
"USD1_GLOBAL_CONFIG",
"EVENT_AUTHORITY",
"PLATFORM_FEE_RATE",
"PROTOCOL_FEE_RATE",
"SHARE_FEE_RATE",
# Discriminators
"BUY_EXACT_IN_DISCRIMINATOR",
"SELL_EXACT_IN_DISCRIMINATOR",
# PDA Functions
"get_pool_pda",
"get_vault_pda",
"get_platform_associated_account",
"get_creator_associated_account",
# Params
"BonkParams",
# Calculation Functions
"get_amount_in_net",
"get_amount_out",
"get_buy_token_amount_from_sol_amount",
"get_sell_sol_amount_from_token_amount",
# Instruction Builders
"build_buy_instructions",
"build_sell_instructions",
]
@@ -1,216 +0,0 @@
"""
Common constants and utilities for Solana trading SDK.
Contains program IDs, token addresses, and utility functions for ATA creation and WSOL handling.
"""
from typing import List, Optional, Tuple
from solders.pubkey import Pubkey
from solders.instruction import Instruction, AccountMeta
from solders.system_program import ID as SYSTEM_PROGRAM_ID
import struct
# ============================================
# Common Program IDs
# ============================================
SYSTEM_PROGRAM: Pubkey = Pubkey.from_string("11111111111111111111111111111111")
TOKEN_PROGRAM: Pubkey = Pubkey.from_string("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA")
TOKEN_PROGRAM_2022: Pubkey = Pubkey.from_string("TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb")
ASSOCIATED_TOKEN_PROGRAM: Pubkey = Pubkey.from_string("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL")
# ============================================
# Common Token Mints
# ============================================
SOL_TOKEN_ACCOUNT: Pubkey = Pubkey.from_string("So11111111111111111111111111111111111111111")
WSOL_TOKEN_ACCOUNT: Pubkey = Pubkey.from_string("So11111111111111111111111111111111111111112")
USDC_TOKEN_ACCOUNT: Pubkey = Pubkey.from_string("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v")
USD1_TOKEN_ACCOUNT: Pubkey = Pubkey.from_string("USD1ttGY1N17NEEHLmELoaybftRBUSErhqYiQzvEmuB")
# ============================================
# Default Values
# ============================================
DEFAULT_SLIPPAGE: int = 500 # 5% in basis points
# ============================================
# Utility Functions
# ============================================
def get_associated_token_address(
owner: Pubkey,
mint: Pubkey,
token_program: Pubkey = TOKEN_PROGRAM,
) -> Pubkey:
"""
Derive the associated token account address for a given owner and mint.
"""
from solders.pubkey import Pubkey as SolderPubkey
seeds = [
bytes(owner),
bytes(token_program),
bytes(mint),
]
(ata, _) = SolderPubkey.find_program_address(seeds, ASSOCIATED_TOKEN_PROGRAM)
return ata
def create_associated_token_account_idempotent_instruction(
payer: Pubkey,
owner: Pubkey,
mint: Pubkey,
token_program: Pubkey = TOKEN_PROGRAM,
) -> Instruction:
"""
Create an idempotent instruction to create an associated token account.
This instruction will succeed even if the account already exists.
"""
ata = get_associated_token_address(owner, mint, token_program)
# Discriminator for create_idempotent (Anchor IDL)
data = b"\x01" # create_idempotent discriminator (different from create)
accounts = [
AccountMeta(payer, True, True), # payer (signer, writable)
AccountMeta(ata, False, True), # ata (writable)
AccountMeta(owner, False, False), # owner (readonly)
AccountMeta(mint, False, False), # mint (readonly)
AccountMeta(SYSTEM_PROGRAM, False, False), # system_program (readonly)
AccountMeta(token_program, False, False), # token_program (readonly)
]
return Instruction(ASSOCIATED_TOKEN_PROGRAM, data, accounts)
def create_wsol_account_instruction(
payer: Pubkey,
amount: int,
) -> List[Instruction]:
"""
Create instructions to:
1. Create WSOL account if needed (via ATA idempotent)
2. Transfer SOL to the WSOL account
3. Sync the WSOL account (close will be done separately)
Returns a list of instructions for handling WSOL.
"""
instructions = []
wsol_ata = get_associated_token_address(payer, WSOL_TOKEN_ACCOUNT, TOKEN_PROGRAM)
# Create idempotent WSOL ATA
instructions.append(
create_associated_token_account_idempotent_instruction(
payer, payer, WSOL_TOKEN_ACCOUNT, TOKEN_PROGRAM
)
)
# Transfer SOL to WSOL ATA (system transfer)
# The amount is transferred to the WSOL account
from solders.system_program import transfer, TransferParams
transfer_ix = transfer(
TransferParams(
from_pubkey=payer,
to_pubkey=wsol_ata,
lamports=amount,
)
)
instructions.append(transfer_ix)
# Sync native token account (needed for wrapped SOL)
# Sync instruction: 17, 2, 218, 95, 237, 188, 186, 205 (sync_native discriminator)
sync_data = bytes([17, 2, 218, 95, 237, 188, 186, 205])
sync_accounts = [AccountMeta(wsol_ata, False, True)]
sync_ix = Instruction(TOKEN_PROGRAM, sync_data, sync_accounts)
instructions.append(sync_ix)
return instructions
def close_wsol_account_instruction(
payer: Pubkey,
) -> Instruction:
"""
Create instruction to close WSOL account and reclaim rent.
"""
wsol_ata = get_associated_token_address(payer, WSOL_TOKEN_ACCOUNT, TOKEN_PROGRAM)
# Close account discriminator
close_data = bytes([153, 228, 76, 56, 218, 79, 98, 4]) # close_account
accounts = [
AccountMeta(wsol_ata, False, True), # account to close (writable)
AccountMeta(payer, False, True), # destination (writable)
AccountMeta(payer, True, False), # owner (signer)
]
return Instruction(TOKEN_PROGRAM, close_data, accounts)
def close_token_account_instruction(
token_program: Pubkey,
account: Pubkey,
destination: Pubkey,
owner: Pubkey,
) -> Instruction:
"""
Create instruction to close a token account and reclaim rent.
"""
close_data = bytes([153, 228, 76, 56, 218, 79, 98, 4]) # close_account
accounts = [
AccountMeta(account, False, True), # account to close (writable)
AccountMeta(destination, False, True), # destination (writable)
AccountMeta(owner, True, False), # owner (signer)
]
return Instruction(token_program, close_data, accounts)
def handle_wsol(payer: Pubkey, amount: int) -> List[Instruction]:
"""
Handle WSOL operations: create ATA, transfer SOL, sync.
This is the Python equivalent of the Rust handle_wsol function.
"""
return create_wsol_account_instruction(payer, amount)
def create_wsol_ata(payer: Pubkey) -> List[Instruction]:
"""
Create WSOL ATA without funding (for receiving WSOL).
"""
return [
create_associated_token_account_idempotent_instruction(
payer, payer, WSOL_TOKEN_ACCOUNT, TOKEN_PROGRAM
)
]
def close_wsol(payer: Pubkey) -> List[Instruction]:
"""
Close WSOL ATA and reclaim rent.
"""
return [close_wsol_account_instruction(payer)]
# ============================================
# Calculation Functions
# ============================================
def calculate_with_slippage_buy(amount: int, slippage_bps: int) -> int:
"""
Calculate maximum input amount with slippage for buy operations.
Returns amount * (10000 + slippage_bps) / 10000
"""
return amount * (10000 + slippage_bps) // 10000
def calculate_with_slippage_sell(amount: int, slippage_bps: int) -> int:
"""
Calculate minimum output amount with slippage for sell operations.
Returns amount * (10000 - slippage_bps) / 10000
"""
return amount * (10000 - slippage_bps) // 10000
@@ -1,325 +0,0 @@
"""
Meteora DAMM V2 instruction builder for Solana trading SDK.
Production-grade implementation with all constants, discriminators, and PDA derivation functions.
"""
from typing import List, Optional
from dataclasses import dataclass
from solders.pubkey import Pubkey
from solders.instruction import Instruction, AccountMeta
import struct
from .common import (
TOKEN_PROGRAM,
WSOL_TOKEN_ACCOUNT,
USDC_TOKEN_ACCOUNT,
DEFAULT_SLIPPAGE,
get_associated_token_address,
create_associated_token_account_idempotent_instruction,
handle_wsol,
close_wsol,
close_token_account_instruction,
)
# ============================================
# Meteora DAMM V2 Program ID
# ============================================
METEORA_DAMM_V2_PROGRAM_ID: Pubkey = Pubkey.from_string("cpamdpZCGKUy5JxQXB4dcpGPiikHawvSWAd6mEn1sGG")
# ============================================
# Meteora DAMM V2 Constants
# ============================================
# Pool Authority
AUTHORITY: Pubkey = Pubkey.from_string("HLnpSz9h2S4hiLQ43rnSD9XkcUThA7B8hQMKmDaiTLcC")
# ============================================
# Instruction Discriminators
# ============================================
SWAP_DISCRIMINATOR: bytes = bytes([248, 198, 158, 145, 225, 117, 135, 200])
# ============================================
# Seeds
# ============================================
EVENT_AUTHORITY_SEED = b"__event_authority"
# ============================================
# PDA Derivation Functions
# ============================================
def get_event_authority_pda() -> Pubkey:
"""
Derive the event authority PDA.
Seeds: ["__event_authority"]
"""
seeds = [EVENT_AUTHORITY_SEED]
(pda, _) = Pubkey.find_program_address(seeds, METEORA_DAMM_V2_PROGRAM_ID)
return pda
# ============================================
# Meteora DAMM V2 Parameters Dataclass
# ============================================
@dataclass
class MeteoraDammV2Params:
"""Parameters for Meteora DAMM V2 protocol trading."""
pool: Pubkey = Pubkey.from_string("11111111111111111111111111111111")
token_a_vault: Pubkey = Pubkey.from_string("11111111111111111111111111111111")
token_b_vault: Pubkey = Pubkey.from_string("11111111111111111111111111111111")
token_a_mint: Pubkey = Pubkey.from_string("11111111111111111111111111111111")
token_b_mint: Pubkey = Pubkey.from_string("11111111111111111111111111111111")
token_a_program: Pubkey = TOKEN_PROGRAM
token_b_program: Pubkey = TOKEN_PROGRAM
@property
def is_wsol(self) -> bool:
"""Check if the pool contains WSOL."""
return self.token_a_mint == WSOL_TOKEN_ACCOUNT or self.token_b_mint == WSOL_TOKEN_ACCOUNT
@property
def is_usdc(self) -> bool:
"""Check if the pool contains USDC."""
return self.token_a_mint == USDC_TOKEN_ACCOUNT or self.token_b_mint == USDC_TOKEN_ACCOUNT
# ============================================
# Build Buy Instructions
# ============================================
def build_buy_instructions(
payer: Pubkey,
output_mint: Pubkey,
input_amount: int,
params: MeteoraDammV2Params,
slippage_bps: int = DEFAULT_SLIPPAGE,
create_input_ata: bool = True,
create_output_ata: bool = True,
close_input_ata: bool = False,
fixed_output_amount: Optional[int] = None,
) -> List[Instruction]:
"""
Build Meteora DAMM V2 buy instructions.
Args:
payer: The wallet paying for the swap
output_mint: The token mint to buy
input_amount: Amount of SOL/USDC to spend
params: Meteora DAMM V2 protocol parameters
slippage_bps: Slippage tolerance in basis points
create_input_ata: Whether to create WSOL ATA if needed
create_output_ata: Whether to create output token ATA if needed
close_input_ata: Whether to close WSOL ATA after swap
fixed_output_amount: MUST be set for Meteora DAMM V2 swaps
Returns:
List of instructions for the buy operation
"""
if input_amount == 0:
raise ValueError("Amount cannot be zero")
instructions = []
# Validate pool contains WSOL or USDC
if not params.is_wsol and not params.is_usdc:
raise ValueError("Pool must contain WSOL or USDC")
# Determine if token A is input (WSOL/USDC)
is_a_in = params.token_a_mint == WSOL_TOKEN_ACCOUNT or params.token_a_mint == USDC_TOKEN_ACCOUNT
# Meteora DAMM V2 requires fixed_output_amount
if fixed_output_amount is None:
raise ValueError("fixed_output_amount must be set for MeteoraDammV2 swap")
minimum_amount_out = fixed_output_amount
# Determine input/output mints and programs
input_mint = WSOL_TOKEN_ACCOUNT if params.is_wsol else USDC_TOKEN_ACCOUNT
input_token_program = params.token_a_program if is_a_in else params.token_b_program
output_token_program = params.token_b_program if is_a_in else params.token_a_program
# Get user token accounts
input_token_account = get_associated_token_address(payer, input_mint, TOKEN_PROGRAM)
output_token_account = get_associated_token_address(payer, output_mint, TOKEN_PROGRAM)
# Get event authority PDA
event_authority = get_event_authority_pda()
# Handle WSOL if needed
if create_input_ata and params.is_wsol:
instructions.extend(handle_wsol(payer, input_amount))
# Create output ATA if needed
if create_output_ata:
instructions.append(
create_associated_token_account_idempotent_instruction(
payer, payer, output_mint, TOKEN_PROGRAM
)
)
# Build instruction data
data = SWAP_DISCRIMINATOR + struct.pack("<QQ", input_amount, minimum_amount_out)
# Build accounts list (14 accounts)
accounts = [
AccountMeta(AUTHORITY, False, False), # pool_authority (readonly)
AccountMeta(params.pool, False, True), # pool (writable)
AccountMeta(input_token_account, False, True), # input_token_account (writable)
AccountMeta(output_token_account, False, True), # output_token_account (writable)
AccountMeta(params.token_a_vault, False, True), # token_a_vault (writable)
AccountMeta(params.token_b_vault, False, True), # token_b_vault (writable)
AccountMeta(params.token_a_mint, False, False), # token_a_mint (readonly)
AccountMeta(params.token_b_mint, False, False), # token_b_mint (readonly)
AccountMeta(payer, True, False), # user_transfer_authority (signer)
AccountMeta(params.token_a_program, False, False), # token_a_program (readonly)
AccountMeta(params.token_b_program, False, False), # token_b_program (readonly)
AccountMeta(METEORA_DAMM_V2_PROGRAM_ID, False, False), # referral_token_account (readonly, program)
AccountMeta(event_authority, False, False), # event_authority (readonly)
AccountMeta(METEORA_DAMM_V2_PROGRAM_ID, False, False), # program (readonly)
]
instructions.append(Instruction(METEORA_DAMM_V2_PROGRAM_ID, data, accounts))
# Close WSOL ATA if requested
if close_input_ata and params.is_wsol:
instructions.extend(close_wsol(payer))
return instructions
# ============================================
# Build Sell Instructions
# ============================================
def build_sell_instructions(
payer: Pubkey,
input_mint: Pubkey,
input_amount: int,
params: MeteoraDammV2Params,
slippage_bps: int = DEFAULT_SLIPPAGE,
create_output_ata: bool = True,
close_output_ata: bool = False,
close_input_ata: bool = False,
fixed_output_amount: Optional[int] = None,
) -> List[Instruction]:
"""
Build Meteora DAMM V2 sell instructions.
Args:
payer: The wallet paying for the swap
input_mint: The token mint to sell
input_amount: Amount of tokens to sell
params: Meteora DAMM V2 protocol parameters
slippage_bps: Slippage tolerance in basis points
create_output_ata: Whether to create WSOL ATA for receiving SOL
close_output_ata: Whether to close WSOL ATA after swap
close_input_ata: Whether to close token ATA after swap
fixed_output_amount: MUST be set for Meteora DAMM V2 swaps
Returns:
List of instructions for the sell operation
"""
if input_amount == 0:
raise ValueError("Amount cannot be zero")
instructions = []
# Validate pool contains WSOL or USDC
if not params.is_wsol and not params.is_usdc:
raise ValueError("Pool must contain WSOL or USDC")
# Determine if token B is output (WSOL/USDC)
is_a_in = params.token_b_mint == WSOL_TOKEN_ACCOUNT or params.token_b_mint == USDC_TOKEN_ACCOUNT
# Meteora DAMM V2 requires fixed_output_amount
if fixed_output_amount is None:
raise ValueError("fixed_output_amount must be set for MeteoraDammV2 swap")
minimum_amount_out = fixed_output_amount
# Determine output mint (WSOL or USDC)
output_mint = WSOL_TOKEN_ACCOUNT if params.is_wsol else USDC_TOKEN_ACCOUNT
# Get token programs based on direction
input_token_program = params.token_a_program if is_a_in else params.token_b_program
output_token_program = params.token_b_program if is_a_in else params.token_a_program
# Get user token accounts
input_token_account = get_associated_token_address(payer, input_mint, input_token_program)
output_token_account = get_associated_token_address(payer, output_mint, TOKEN_PROGRAM)
# Get event authority PDA
event_authority = get_event_authority_pda()
# Create WSOL ATA if needed for receiving SOL
if create_output_ata and params.is_wsol:
instructions.append(
create_associated_token_account_idempotent_instruction(
payer, payer, WSOL_TOKEN_ACCOUNT, TOKEN_PROGRAM
)
)
# Build instruction data
data = SWAP_DISCRIMINATOR + struct.pack("<QQ", input_amount, minimum_amount_out)
# Build accounts list (14 accounts)
accounts = [
AccountMeta(AUTHORITY, False, False), # pool_authority (readonly)
AccountMeta(params.pool, False, True), # pool (writable)
AccountMeta(input_token_account, False, True), # input_token_account (writable)
AccountMeta(output_token_account, False, True), # output_token_account (writable)
AccountMeta(params.token_a_vault, False, True), # token_a_vault (writable)
AccountMeta(params.token_b_vault, False, True), # token_b_vault (writable)
AccountMeta(params.token_a_mint, False, False), # token_a_mint (readonly)
AccountMeta(params.token_b_mint, False, False), # token_b_mint (readonly)
AccountMeta(payer, True, False), # user_transfer_authority (signer)
AccountMeta(params.token_a_program, False, False), # token_a_program (readonly)
AccountMeta(params.token_b_program, False, False), # token_b_program (readonly)
AccountMeta(METEORA_DAMM_V2_PROGRAM_ID, False, False), # referral_token_account (readonly, program)
AccountMeta(event_authority, False, False), # event_authority (readonly)
AccountMeta(METEORA_DAMM_V2_PROGRAM_ID, False, False), # program (readonly)
]
instructions.append(Instruction(METEORA_DAMM_V2_PROGRAM_ID, data, accounts))
# Close WSOL ATA if requested
if close_output_ata and params.is_wsol:
instructions.extend(close_wsol(payer))
# Close token ATA if requested
if close_input_ata:
instructions.append(
close_token_account_instruction(
input_token_program,
input_token_account,
payer,
payer,
)
)
return instructions
# ============================================
# Exports
# ============================================
__all__ = [
# Program IDs and Constants
"METEORA_DAMM_V2_PROGRAM_ID",
"AUTHORITY",
# Discriminators
"SWAP_DISCRIMINATOR",
# PDA Functions
"get_event_authority_pda",
# Params
"MeteoraDammV2Params",
# Instruction Builders
"build_buy_instructions",
"build_sell_instructions",
]
@@ -1,560 +0,0 @@
"""
PumpFun instruction builder for Solana trading SDK.
Production-grade implementation with all constants, discriminators, and PDA derivation functions.
"""
from typing import List, Optional, Tuple
from dataclasses import dataclass
from solders.pubkey import Pubkey
from solders.instruction import Instruction, AccountMeta
import struct
import random
from .common import (
SYSTEM_PROGRAM,
TOKEN_PROGRAM,
TOKEN_PROGRAM_2022,
WSOL_TOKEN_ACCOUNT,
DEFAULT_SLIPPAGE,
get_associated_token_address,
create_associated_token_account_idempotent_instruction,
handle_wsol,
close_wsol,
close_token_account_instruction,
calculate_with_slippage_buy,
calculate_with_slippage_sell,
)
# ============================================
# PumpFun Program ID
# ============================================
PUMPFUN_PROGRAM_ID: Pubkey = Pubkey.from_string("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P")
# ============================================
# PumpFun Constants
# ============================================
# Fee Recipient
FEE_RECIPIENT: Pubkey = Pubkey.from_string("62qc2CNXwrYqQScmEdiZFFAnJR262PxWEuNQtxfafNgV")
# Global Account
GLOBAL_ACCOUNT: Pubkey = Pubkey.from_string("4wTV1YmiEkRvAtNtsSGPtUrqRYQMe5SKy2uB4Jjaxnjf")
# Event Authority
EVENT_AUTHORITY: Pubkey = Pubkey.from_string("Ce6TQqeHC9p8KetsN6JsjHK7UTZk7nasjjnr7XxXp9F1")
# Authority
AUTHORITY: Pubkey = Pubkey.from_string("FFWtrEQ4B4PKQoVuHYzZq8FabGkVatYzDpEVHsK5rrhF")
# Fee Program
FEE_PROGRAM: Pubkey = Pubkey.from_string("pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ")
# Global Volume Accumulator
GLOBAL_VOLUME_ACCUMULATOR: Pubkey = Pubkey.from_string("Hq2wp8uJ9jCPsYgNHex8RtqdvMPfVGoYwjvF1ATiwn2Y")
# Fee Config
FEE_CONFIG: Pubkey = Pubkey.from_string("8Wf5TiAheLUqBrKXeYg2JtAFFMWtKdG2BSFgqUcPVwTt")
# Mayhem Fee Recipients (use any one randomly)
MAYHEM_FEE_RECIPIENTS: List[Pubkey] = [
Pubkey.from_string("GesfTA3X2arioaHp8bbKdjG9vJtskViWACZoYvxp4twS"),
Pubkey.from_string("4budycTjhs9fD6xw62VBducVTNgMgJJ5BgtKq7mAZwn6"),
Pubkey.from_string("8SBKzEQU4nLSzcwF4a74F2iaUDQyTfjGndn6qUWBnrpR"),
Pubkey.from_string("4UQeTP1T39KZ9Sfxzo3WR5skgsaP6NZa87BAkuazLEKH"),
Pubkey.from_string("8sNeir4QsLsJdYpc9RZacohhK1Y5FLU3nC5LXgYB4aa6"),
Pubkey.from_string("Fh9HmeLNUMVCvejxCtCL2DbYaRyBFVJ5xrWkLnMH6fdk"),
Pubkey.from_string("463MEnMeGyJekNZFQSTUABBEbLnvMTALbT6ZmsxAbAdq"),
Pubkey.from_string("6AUH3WEHucYZyC61hqpqYUWVto5qA5hjHuNQ32GNnNxA"),
]
# ============================================
# Instruction Discriminators
# ============================================
BUY_DISCRIMINATOR: bytes = bytes([102, 6, 61, 18, 1, 218, 235, 234])
BUY_EXACT_SOL_IN_DISCRIMINATOR: bytes = bytes([56, 252, 116, 8, 158, 223, 205, 95])
SELL_DISCRIMINATOR: bytes = bytes([51, 230, 133, 164, 1, 127, 131, 173])
CLAIM_CASHBACK_DISCRIMINATOR: bytes = bytes([37, 58, 35, 126, 190, 53, 228, 197])
# ============================================
# Seeds
# ============================================
BONDING_CURVE_SEED = b"bonding-curve"
BONDING_CURVE_V2_SEED = b"bonding-curve-v2"
CREATOR_VAULT_SEED = b"creator-vault"
USER_VOLUME_ACCUMULATOR_SEED = b"user_volume_accumulator"
GLOBAL_VOLUME_ACCUMULATOR_SEED = b"global_volume_accumulator"
FEE_CONFIG_SEED = b"fee_config"
# ============================================
# PDA Derivation Functions
# ============================================
def get_bonding_curve_pda(mint: Pubkey) -> Pubkey:
"""
Derive the bonding curve PDA for a given mint.
Seeds: ["bonding-curve", mint]
"""
seeds = [BONDING_CURVE_SEED, bytes(mint)]
(pda, _) = Pubkey.find_program_address(seeds, PUMPFUN_PROGRAM_ID)
return pda
def get_bonding_curve_v2_pda(mint: Pubkey) -> Pubkey:
"""
Derive the bonding curve v2 PDA for a given mint.
Seeds: ["bonding-curve-v2", mint]
"""
seeds = [BONDING_CURVE_V2_SEED, bytes(mint)]
(pda, _) = Pubkey.find_program_address(seeds, PUMPFUN_PROGRAM_ID)
return pda
def get_creator_vault_pda(creator: Pubkey) -> Pubkey:
"""
Derive the creator vault PDA for a given creator.
Seeds: ["creator-vault", creator]
"""
seeds = [CREATOR_VAULT_SEED, bytes(creator)]
(pda, _) = Pubkey.find_program_address(seeds, PUMPFUN_PROGRAM_ID)
return pda
def get_user_volume_accumulator_pda(user: Pubkey) -> Pubkey:
"""
Derive the user volume accumulator PDA for a given user.
Seeds: ["user_volume_accumulator", user]
"""
seeds = [USER_VOLUME_ACCUMULATOR_SEED, bytes(user)]
(pda, _) = Pubkey.find_program_address(seeds, PUMPFUN_PROGRAM_ID)
return pda
def get_creator(creator_vault_pda: Pubkey) -> Pubkey:
"""
Get the creator pubkey from the creator vault PDA.
Returns default pubkey if creator_vault_pda is default.
"""
default_pubkey = Pubkey.from_string("11111111111111111111111111111111")
if creator_vault_pda == default_pubkey:
return default_pubkey
return creator_vault_pda
def get_mayhem_fee_recipient_random() -> Pubkey:
"""
Get a random Mayhem fee recipient.
"""
return random.choice(MAYHEM_FEE_RECIPIENTS)
# ============================================
# PumpFun Parameters Dataclass
# ============================================
@dataclass
class PumpFunParams:
"""Parameters for PumpFun protocol trading."""
bonding_curve_account: Optional[Pubkey] = None # If None, will derive from mint
virtual_token_reserves: int = 0
virtual_sol_reserves: int = 0
real_token_reserves: int = 0
real_sol_reserves: int = 0
token_total_supply: int = 0
complete: bool = False
creator: Pubkey = Pubkey.from_string("11111111111111111111111111111111")
is_mayhem_mode: bool = False
is_cashback_coin: bool = False
associated_bonding_curve: Optional[Pubkey] = None
creator_vault: Pubkey = Pubkey.from_string("11111111111111111111111111111111")
token_program: Pubkey = TOKEN_PROGRAM
close_token_account_when_sell: bool = False
# ============================================
# PumpFun Calculation Functions
# ============================================
def get_buy_token_amount_from_sol_amount(
virtual_token_reserves: int,
virtual_sol_reserves: int,
real_token_reserves: int,
creator: Pubkey,
sol_amount: int,
) -> int:
"""
Calculate the token amount received for a given SOL amount on PumpFun.
Uses the bonding curve formula.
"""
if sol_amount == 0:
return 0
default_pubkey = Pubkey.from_string("11111111111111111111111111111111")
is_non_zero_creator = creator != default_pubkey
# Calculate using AMM formula
n = virtual_sol_reserves * virtual_token_reserves
i = virtual_sol_reserves + sol_amount
r = n // i + 1
s = virtual_token_reserves - r
# Apply creator fee if applicable
if is_non_zero_creator:
s = s - (s * 30) // 10000 # 0.30% creator fee
return min(s, real_token_reserves)
def get_sell_sol_amount_from_token_amount(
virtual_token_reserves: int,
virtual_sol_reserves: int,
creator: Pubkey,
token_amount: int,
) -> int:
"""
Calculate the SOL amount received for a given token amount on PumpFun.
"""
if token_amount == 0:
return 0
default_pubkey = Pubkey.from_string("11111111111111111111111111111111")
is_non_zero_creator = creator != default_pubkey
# Calculate using AMM formula
n = virtual_sol_reserves * virtual_token_reserves
i = virtual_token_reserves + token_amount
r = n // i + 1
sol_amount = virtual_sol_reserves - r
# Apply creator fee if applicable
if is_non_zero_creator:
sol_amount = sol_amount - (sol_amount * 30) // 10000 # 0.30% creator fee
return sol_amount
# ============================================
# Build Buy Instructions
# ============================================
def build_buy_instructions(
payer: Pubkey,
output_mint: Pubkey,
input_amount: int,
params: PumpFunParams,
slippage_bps: int = DEFAULT_SLIPPAGE,
create_output_ata: bool = True,
close_input_ata: bool = False,
fixed_output_amount: Optional[int] = None,
use_exact_sol_amount: bool = True,
) -> List[Instruction]:
"""
Build PumpFun buy instructions.
Args:
payer: The wallet paying for the swap
output_mint: The token mint to buy
input_amount: Amount of SOL to spend
params: PumpFun protocol parameters
slippage_bps: Slippage tolerance in basis points
create_output_ata: Whether to create output token ATA if needed
close_input_ata: Whether to close WSOL ATA after swap
fixed_output_amount: If set, use this as exact output amount
use_exact_sol_amount: If True, use buy_exact_sol_in instruction
Returns:
List of instructions for the buy operation
"""
if input_amount == 0:
raise ValueError("Amount cannot be zero")
instructions = []
# Get bonding curve address
bonding_curve_addr = params.bonding_curve_account
if bonding_curve_addr is None:
bonding_curve_addr = get_bonding_curve_pda(output_mint)
# Get creator from creator_vault
creator = get_creator(params.creator_vault)
# Calculate token amount
if fixed_output_amount is not None:
buy_token_amount = fixed_output_amount
else:
buy_token_amount = get_buy_token_amount_from_sol_amount(
params.virtual_token_reserves,
params.virtual_sol_reserves,
params.real_token_reserves,
creator,
input_amount,
)
# Calculate max SOL cost with slippage
max_sol_cost = calculate_with_slippage_buy(input_amount, slippage_bps)
# Get associated bonding curve
associated_bonding_curve = params.associated_bonding_curve
if associated_bonding_curve is None:
associated_bonding_curve = get_associated_token_address(
bonding_curve_addr, output_mint, params.token_program
)
# Get user token account
user_token_account = get_associated_token_address(
payer, output_mint, params.token_program
)
# Create ATA if needed
if create_output_ata:
instructions.append(
create_associated_token_account_idempotent_instruction(
payer, payer, output_mint, params.token_program
)
)
# Get user volume accumulator
user_volume_accumulator = get_user_volume_accumulator_pda(payer)
# Get bonding curve v2
bonding_curve_v2 = get_bonding_curve_v2_pda(output_mint)
# Determine fee recipient
if params.is_mayhem_mode:
fee_recipient = get_mayhem_fee_recipient_random()
else:
fee_recipient = FEE_RECIPIENT
# Build instruction data
track_volume = bytes([1, 1]) if params.is_cashback_coin else bytes([1, 0])
if use_exact_sol_amount:
# buy_exact_sol_in(spendable_sol_in: u64, min_tokens_out: u64, track_volume)
min_tokens_out = calculate_with_slippage_sell(buy_token_amount, slippage_bps)
data = BUY_EXACT_SOL_IN_DISCRIMINATOR + struct.pack("<QQ", input_amount, min_tokens_out) + track_volume
else:
# buy(token_amount: u64, max_sol_cost: u64, track_volume)
data = BUY_DISCRIMINATOR + struct.pack("<QQ", buy_token_amount, max_sol_cost) + track_volume
# Build accounts list
accounts = [
AccountMeta(GLOBAL_ACCOUNT, False, False), # global
AccountMeta(fee_recipient, False, True), # fee_recipient (writable)
AccountMeta(output_mint, False, False), # mint (readonly)
AccountMeta(bonding_curve_addr, False, True), # bonding_curve (writable)
AccountMeta(associated_bonding_curve, False, True), # associated_bonding_curve (writable)
AccountMeta(user_token_account, False, True), # user_token_account (writable)
AccountMeta(payer, True, True), # user (signer, writable)
AccountMeta(SYSTEM_PROGRAM, False, False), # system_program
AccountMeta(params.token_program, False, False), # token_program
AccountMeta(params.creator_vault, False, True), # creator_vault (writable)
AccountMeta(EVENT_AUTHORITY, False, False), # event_authority
AccountMeta(PUMPFUN_PROGRAM_ID, False, False), # program
AccountMeta(GLOBAL_VOLUME_ACCUMULATOR, False, True), # global_volume_accumulator (writable)
AccountMeta(user_volume_accumulator, False, True), # user_volume_accumulator (writable)
AccountMeta(FEE_CONFIG, False, False), # fee_config
AccountMeta(FEE_PROGRAM, False, False), # fee_program
AccountMeta(bonding_curve_v2, False, False), # bonding_curve_v2 (readonly, remaining account)
]
instructions.append(Instruction(PUMPFUN_PROGRAM_ID, data, accounts))
return instructions
# ============================================
# Build Sell Instructions
# ============================================
def build_sell_instructions(
payer: Pubkey,
input_mint: Pubkey,
input_amount: int,
params: PumpFunParams,
slippage_bps: int = DEFAULT_SLIPPAGE,
create_output_ata: bool = False,
close_output_ata: bool = False,
close_input_ata: bool = False,
fixed_output_amount: Optional[int] = None,
) -> List[Instruction]:
"""
Build PumpFun sell instructions.
Args:
payer: The wallet paying for the swap
input_mint: The token mint to sell
input_amount: Amount of tokens to sell
params: PumpFun protocol parameters
slippage_bps: Slippage tolerance in basis points
create_output_ata: Whether to create WSOL ATA for receiving SOL
close_output_ata: Whether to close WSOL ATA after swap
close_input_ata: Whether to close token ATA after swap
fixed_output_amount: If set, use this as exact output amount
Returns:
List of instructions for the sell operation
"""
if input_amount == 0:
raise ValueError("Amount cannot be zero")
instructions = []
# Get bonding curve address
bonding_curve_addr = params.bonding_curve_account
if bonding_curve_addr is None:
bonding_curve_addr = get_bonding_curve_pda(input_mint)
# Get creator from creator_vault
creator = get_creator(params.creator_vault)
# Calculate SOL amount
sol_amount = get_sell_sol_amount_from_token_amount(
params.virtual_token_reserves,
params.virtual_sol_reserves,
creator,
input_amount,
)
# Calculate min SOL output with slippage
if fixed_output_amount is not None:
min_sol_output = fixed_output_amount
else:
min_sol_output = calculate_with_slippage_sell(sol_amount, slippage_bps)
# Get associated bonding curve
associated_bonding_curve = params.associated_bonding_curve
if associated_bonding_curve is None:
associated_bonding_curve = get_associated_token_address(
bonding_curve_addr, input_mint, params.token_program
)
# Get user token account
user_token_account = get_associated_token_address(
payer, input_mint, params.token_program
)
# Create WSOL ATA if needed for receiving SOL
if create_output_ata or close_output_ata:
instructions.extend(
create_associated_token_account_idempotent_instruction(
payer, payer, WSOL_TOKEN_ACCOUNT, TOKEN_PROGRAM
)
)
# Determine fee recipient
if params.is_mayhem_mode:
fee_recipient = get_mayhem_fee_recipient_random()
else:
fee_recipient = FEE_RECIPIENT
# Build instruction data
data = SELL_DISCRIMINATOR + struct.pack("<QQ", input_amount, min_sol_output)
# Build accounts list
accounts = [
AccountMeta(GLOBAL_ACCOUNT, False, False), # global
AccountMeta(fee_recipient, False, True), # fee_recipient (writable)
AccountMeta(input_mint, False, False), # mint (readonly)
AccountMeta(bonding_curve_addr, False, True), # bonding_curve (writable)
AccountMeta(associated_bonding_curve, False, True), # associated_bonding_curve (writable)
AccountMeta(user_token_account, False, True), # user_token_account (writable)
AccountMeta(payer, True, True), # user (signer, writable)
AccountMeta(SYSTEM_PROGRAM, False, False), # system_program
AccountMeta(params.creator_vault, False, True), # creator_vault (writable)
AccountMeta(params.token_program, False, False), # token_program
AccountMeta(EVENT_AUTHORITY, False, False), # event_authority
AccountMeta(PUMPFUN_PROGRAM_ID, False, False), # program
AccountMeta(FEE_CONFIG, False, False), # fee_config
AccountMeta(FEE_PROGRAM, False, False), # fee_program
]
# Cashback: Add user_volume_accumulator if cashback coin
if params.is_cashback_coin:
user_volume_accumulator = get_user_volume_accumulator_pda(payer)
accounts.append(AccountMeta(user_volume_accumulator, False, True))
# Add bonding_curve_v2 at the end (remaining account)
bonding_curve_v2 = get_bonding_curve_v2_pda(input_mint)
accounts.append(AccountMeta(bonding_curve_v2, False, False))
instructions.append(Instruction(PUMPFUN_PROGRAM_ID, data, accounts))
# Close WSOL ATA if requested
if close_output_ata:
instructions.extend(close_wsol(payer))
# Close token ATA if requested
if close_input_ata or params.close_token_account_when_sell:
instructions.append(
close_token_account_instruction(
params.token_program,
user_token_account,
payer,
payer,
)
)
return instructions
# ============================================
# Claim Cashback Instruction
# ============================================
def claim_cashback_pumpfun_instruction(payer: Pubkey) -> Instruction:
"""
Build instruction to claim cashback for PumpFun bonding curve.
Transfers native lamports from UserVolumeAccumulator to user.
"""
user_volume_accumulator = get_user_volume_accumulator_pda(payer)
accounts = [
AccountMeta(payer, True, True), # user (signer, writable)
AccountMeta(user_volume_accumulator, False, True), # user_volume_accumulator (writable)
AccountMeta(SYSTEM_PROGRAM, False, False), # system_program
AccountMeta(EVENT_AUTHORITY, False, False), # event_authority
AccountMeta(PUMPFUN_PROGRAM_ID, False, False), # program
]
return Instruction(PUMPFUN_PROGRAM_ID, CLAIM_CASHBACK_DISCRIMINATOR, accounts)
# ============================================
# Exports
# ============================================
__all__ = [
# Program IDs and Constants
"PUMPFUN_PROGRAM_ID",
"FEE_RECIPIENT",
"GLOBAL_ACCOUNT",
"EVENT_AUTHORITY",
"AUTHORITY",
"FEE_PROGRAM",
"GLOBAL_VOLUME_ACCUMULATOR",
"FEE_CONFIG",
"MAYHEM_FEE_RECIPIENTS",
# Discriminators
"BUY_DISCRIMINATOR",
"BUY_EXACT_SOL_IN_DISCRIMINATOR",
"SELL_DISCRIMINATOR",
"CLAIM_CASHBACK_DISCRIMINATOR",
# PDA Functions
"get_bonding_curve_pda",
"get_bonding_curve_v2_pda",
"get_creator_vault_pda",
"get_user_volume_accumulator_pda",
"get_creator",
"get_mayhem_fee_recipient_random",
# Params
"PumpFunParams",
# Calculation Functions
"get_buy_token_amount_from_sol_amount",
"get_sell_sol_amount_from_token_amount",
# Instruction Builders
"build_buy_instructions",
"build_sell_instructions",
"claim_cashback_pumpfun_instruction",
]
@@ -1,760 +0,0 @@
"""
PumpSwap instruction builder for Solana trading SDK.
Production-grade implementation with all constants, discriminators, and PDA derivation functions.
"""
from typing import List, Optional, Tuple
from dataclasses import dataclass
from solders.pubkey import Pubkey
from solders.instruction import Instruction, AccountMeta
import struct
import random
from .common import (
SYSTEM_PROGRAM,
TOKEN_PROGRAM,
TOKEN_PROGRAM_2022,
WSOL_TOKEN_ACCOUNT,
USDC_TOKEN_ACCOUNT,
DEFAULT_SLIPPAGE,
get_associated_token_address,
create_associated_token_account_idempotent_instruction,
handle_wsol,
close_wsol,
close_token_account_instruction,
calculate_with_slippage_sell,
)
# ============================================
# PumpSwap Program ID
# ============================================
PUMPSWAP_PROGRAM_ID: Pubkey = Pubkey.from_string("pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA")
# ============================================
# PumpSwap Constants
# ============================================
# Fee Recipient
FEE_RECIPIENT: Pubkey = Pubkey.from_string("62qc2CNXwrYqQScmEdiZFFAnJR262PxWEuNQtxfafNgV")
# Global Account
GLOBAL_ACCOUNT: Pubkey = Pubkey.from_string("ADyA8hdefvWN2dbGGWFotbzWxrAvLW83WG6QCVXvJKqw")
# Event Authority
EVENT_AUTHORITY: Pubkey = Pubkey.from_string("GS4CU59F31iL7aR2Q8zVS8DRrcRnXX1yjQ66TqNVQnaR")
# Associated Token Program
ASSOCIATED_TOKEN_PROGRAM: Pubkey = Pubkey.from_string("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL")
# Protocol Fee Recipient
PROTOCOL_FEE_RECIPIENT: Pubkey = Pubkey.from_string("62qc2CNXwrYqQScmEdiZFFAnJR262PxWEuNQtxfafNgV")
# Pump Program ID (Bonding Curve)
PUMP_PROGRAM_ID: Pubkey = Pubkey.from_string("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P")
# Fee Program
FEE_PROGRAM: Pubkey = Pubkey.from_string("pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ")
# Global Volume Accumulator
GLOBAL_VOLUME_ACCUMULATOR: Pubkey = Pubkey.from_string("C2aFPdENg4A2HQsmrd5rTw5TaYBX5Ku887cWjbFKtZpw")
# Fee Config
FEE_CONFIG: Pubkey = Pubkey.from_string("5PHirr8joyTMp9JMm6N7hNDVyEYdkzDqazxPD7RaTjx")
# Default Coin Creator Vault Authority
DEFAULT_COIN_CREATOR_VAULT_AUTHORITY: Pubkey = Pubkey.from_string("8N3GDaZ2iwN65oxVatKTLPNooAVUJTbfiVJ1ahyqwjSk")
# Mayhem Fee Recipients
MAYHEM_FEE_RECIPIENTS: List[Pubkey] = [
Pubkey.from_string("GesfTA3X2arioaHp8bbKdjG9vJtskViWACZoYvxp4twS"),
Pubkey.from_string("4budycTjhs9fD6xw62VBducVTNgMgJJ5BgtKq7mAZwn6"),
Pubkey.from_string("8SBKzEQU4nLSzcwF4a74F2iaUDQyTfjGndn6qUWBnrpR"),
Pubkey.from_string("4UQeTP1T39KZ9Sfxzo3WR5skgsaP6NZa87BAkuazLEKH"),
Pubkey.from_string("8sNeir4QsLsJdYpc9RZacohhK1Y5FLU3nC5LXgYB4aa6"),
Pubkey.from_string("Fh9HmeLNUMVCvejxCtCL2DbYaRyBFVJ5xrWkLnMH6fdk"),
Pubkey.from_string("463MEnMeGyJekNZFQSTUABBEbLnvMTALbT6ZmsxAbAdq"),
Pubkey.from_string("6AUH3WEHucYZyC61hqpqYUWVto5qA5hjHuNQ32GNnNxA"),
]
# Fee Basis Points
LP_FEE_BASIS_POINTS: int = 25
PROTOCOL_FEE_BASIS_POINTS: int = 5
COIN_CREATOR_FEE_BASIS_POINTS: int = 5
# ============================================
# Instruction Discriminators
# ============================================
BUY_DISCRIMINATOR: bytes = bytes([102, 6, 61, 18, 1, 218, 235, 234])
BUY_EXACT_QUOTE_IN_DISCRIMINATOR: bytes = bytes([198, 46, 21, 82, 180, 217, 232, 112])
SELL_DISCRIMINATOR: bytes = bytes([51, 230, 133, 164, 1, 127, 131, 173])
CLAIM_CASHBACK_DISCRIMINATOR: bytes = bytes([37, 58, 35, 126, 190, 53, 228, 197])
# ============================================
# Seeds
# ============================================
GLOBAL_SEED = b"global"
MINT_AUTHORITY_SEED = b"mint-authority"
BONDING_CURVE_SEED = b"bonding-curve"
METADATA_SEED = b"metadata"
USER_VOLUME_ACCUMULATOR_SEED = b"user_volume_accumulator"
GLOBAL_VOLUME_ACCUMULATOR_SEED = b"global_volume_accumulator"
FEE_CONFIG_SEED = b"fee_config"
POOL_V2_SEED = b"pool-v2"
POOL_SEED = b"pool"
POOL_AUTHORITY_SEED = b"pool-authority"
# ============================================
# PDA Derivation Functions
# ============================================
def get_pool_v2_pda(base_mint: Pubkey) -> Pubkey:
"""
Derive the pool v2 PDA for a given base mint.
Seeds: ["pool-v2", base_mint]
"""
seeds = [POOL_V2_SEED, bytes(base_mint)]
(pda, _) = Pubkey.find_program_address(seeds, PUMPSWAP_PROGRAM_ID)
return pda
def get_pump_pool_authority_pda(mint: Pubkey) -> Pubkey:
"""
Derive the pump pool authority PDA for canonical pool.
Seeds: ["pool-authority", mint]
"""
seeds = [POOL_AUTHORITY_SEED, bytes(mint)]
(pda, _) = Pubkey.find_program_address(seeds, PUMP_PROGRAM_ID)
return pda
def get_canonical_pool_pda(mint: Pubkey) -> Pubkey:
"""
Derive the canonical pump pool PDA.
Seeds: ["pool", index=0, authority, mint, WSOL]
"""
index = 0
authority = get_pump_pool_authority_pda(mint)
seeds = [
POOL_SEED,
struct.pack("<H", index), # u16 index = 0
bytes(authority),
bytes(mint),
bytes(WSOL_TOKEN_ACCOUNT),
]
(pda, _) = Pubkey.find_program_address(seeds, PUMPSWAP_PROGRAM_ID)
return pda
def get_user_volume_accumulator_pda(user: Pubkey) -> Pubkey:
"""
Derive the user volume accumulator PDA for a given user.
Seeds: ["user_volume_accumulator", user]
"""
seeds = [USER_VOLUME_ACCUMULATOR_SEED, bytes(user)]
(pda, _) = Pubkey.find_program_address(seeds, PUMPSWAP_PROGRAM_ID)
return pda
def get_user_volume_accumulator_wsol_ata(user: Pubkey) -> Pubkey:
"""
Get the WSOL ATA of UserVolumeAccumulator for PumpSwap AMM.
"""
accumulator = get_user_volume_accumulator_pda(user)
return get_associated_token_address(accumulator, WSOL_TOKEN_ACCOUNT, TOKEN_PROGRAM)
def get_user_volume_accumulator_quote_ata(
user: Pubkey,
quote_mint: Pubkey,
quote_token_program: Pubkey,
) -> Pubkey:
"""
Get the quote-mint ATA of UserVolumeAccumulator (used for sell cashback).
"""
accumulator = get_user_volume_accumulator_pda(user)
return get_associated_token_address(accumulator, quote_mint, quote_token_program)
def coin_creator_vault_authority(coin_creator: Pubkey) -> Pubkey:
"""
Derive the coin creator vault authority PDA.
Seeds: ["creator_vault", coin_creator]
"""
seeds = [b"creator_vault", bytes(coin_creator)]
(pda, _) = Pubkey.find_program_address(seeds, PUMPSWAP_PROGRAM_ID)
return pda
def coin_creator_vault_ata(coin_creator: Pubkey, quote_mint: Pubkey) -> Pubkey:
"""
Get the coin creator vault ATA.
"""
authority = coin_creator_vault_authority(coin_creator)
return get_associated_token_address(authority, quote_mint, TOKEN_PROGRAM)
def fee_recipient_ata(fee_recipient: Pubkey, quote_mint: Pubkey) -> Pubkey:
"""
Get the fee recipient ATA.
"""
return get_associated_token_address(fee_recipient, quote_mint, TOKEN_PROGRAM)
def get_mayhem_fee_recipient_random() -> Tuple[Pubkey, AccountMeta]:
"""
Get a random Mayhem fee recipient and its AccountMeta.
"""
recipient = random.choice(MAYHEM_FEE_RECIPIENTS)
meta = AccountMeta(recipient, False, False)
return (recipient, meta)
# ============================================
# PumpSwap Parameters Dataclass
# ============================================
@dataclass
class PumpSwapParams:
"""Parameters for PumpSwap protocol trading."""
pool: Pubkey = Pubkey.from_string("11111111111111111111111111111111")
base_mint: Pubkey = Pubkey.from_string("11111111111111111111111111111111")
quote_mint: Pubkey = Pubkey.from_string("11111111111111111111111111111111")
pool_base_token_account: Pubkey = Pubkey.from_string("11111111111111111111111111111111")
pool_quote_token_account: Pubkey = Pubkey.from_string("11111111111111111111111111111111")
pool_base_token_reserves: int = 0
pool_quote_token_reserves: int = 0
coin_creator_vault_ata: Pubkey = Pubkey.from_string("11111111111111111111111111111111")
coin_creator_vault_authority: Pubkey = Pubkey.from_string("11111111111111111111111111111111")
base_token_program: Pubkey = TOKEN_PROGRAM
quote_token_program: Pubkey = TOKEN_PROGRAM
is_mayhem_mode: bool = False
is_cashback_coin: bool = False
@property
def is_wsol(self) -> bool:
"""Check if the pool contains WSOL."""
return (
(self.base_mint == WSOL_TOKEN_ACCOUNT and self.quote_mint != USDC_TOKEN_ACCOUNT)
or (self.quote_mint == WSOL_TOKEN_ACCOUNT and self.base_mint != USDC_TOKEN_ACCOUNT)
)
@property
def is_usdc(self) -> bool:
"""Check if the pool contains USDC."""
return (
(self.base_mint == USDC_TOKEN_ACCOUNT and self.quote_mint != WSOL_TOKEN_ACCOUNT)
or (self.quote_mint == USDC_TOKEN_ACCOUNT and self.base_mint != WSOL_TOKEN_ACCOUNT)
)
# ============================================
# PumpSwap Calculation Functions
# ============================================
def buy_quote_input_internal(
quote_amount: int,
slippage_bps: int,
pool_base_reserves: int,
pool_quote_reserves: int,
creator: Pubkey,
) -> Tuple[int, int]:
"""
Calculate buy amounts for PumpSwap when quote is input.
Returns (base_amount_out, max_quote_amount_in).
"""
default_pubkey = Pubkey.from_string("11111111111111111111111111111111")
is_non_zero_creator = creator != default_pubkey
# Apply LP fee
quote_amount_after_fee = quote_amount - (quote_amount * LP_FEE_BASIS_POINTS) // 10000
# Apply protocol fee
quote_amount_after_fee = quote_amount_after_fee - (quote_amount * PROTOCOL_FEE_BASIS_POINTS) // 10000
# Apply creator fee if applicable
if is_non_zero_creator:
quote_amount_after_fee = quote_amount_after_fee - (quote_amount * COIN_CREATOR_FEE_BASIS_POINTS) // 10000
# Calculate output using AMM formula
# out = (quote_in * base_reserves) / (quote_reserves + quote_in)
numerator = quote_amount_after_fee * pool_base_reserves
denominator = pool_quote_reserves + quote_amount_after_fee
base_amount_out = numerator // denominator
# Calculate max quote with slippage
max_quote = int(quote_amount * (10000 + slippage_bps) / 10000)
return (base_amount_out, max_quote)
def sell_base_input_internal(
base_amount: int,
slippage_bps: int,
pool_base_reserves: int,
pool_quote_reserves: int,
creator: Pubkey,
) -> Tuple[int, int]:
"""
Calculate sell amounts for PumpSwap when base is input.
Returns (min_quote_amount_out, base_amount_in).
"""
default_pubkey = Pubkey.from_string("11111111111111111111111111111111")
is_non_zero_creator = creator != default_pubkey
# Apply LP fee
base_amount_after_fee = base_amount - (base_amount * LP_FEE_BASIS_POINTS) // 10000
# Apply protocol fee
base_amount_after_fee = base_amount_after_fee - (base_amount * PROTOCOL_FEE_BASIS_POINTS) // 10000
# Apply creator fee if applicable
if is_non_zero_creator:
base_amount_after_fee = base_amount_after_fee - (base_amount * COIN_CREATOR_FEE_BASIS_POINTS) // 10000
# Calculate output using AMM formula
# out = (base_in * quote_reserves) / (base_reserves + base_in)
numerator = base_amount_after_fee * pool_quote_reserves
denominator = pool_base_reserves + base_amount_after_fee
quote_amount_out = numerator // denominator
# Apply slippage
min_quote = calculate_with_slippage_sell(quote_amount_out, slippage_bps)
return (min_quote, base_amount)
# ============================================
# Build Buy Instructions
# ============================================
def build_buy_instructions(
payer: Pubkey,
output_mint: Pubkey,
input_amount: int,
params: PumpSwapParams,
slippage_bps: int = DEFAULT_SLIPPAGE,
create_input_ata: bool = True,
create_output_ata: bool = True,
close_input_ata: bool = False,
fixed_output_amount: Optional[int] = None,
use_exact_quote_amount: bool = True,
) -> List[Instruction]:
"""
Build PumpSwap buy instructions.
Args:
payer: The wallet paying for the swap
output_mint: The token mint to buy (base_mint)
input_amount: Amount of SOL/USDC to spend
params: PumpSwap protocol parameters
slippage_bps: Slippage tolerance in basis points
create_input_ata: Whether to create WSOL ATA if needed
create_output_ata: Whether to create output token ATA if needed
close_input_ata: Whether to close WSOL ATA after swap
fixed_output_amount: If set, use this as exact output amount
use_exact_quote_amount: If True, use buy_exact_quote_in instruction
Returns:
List of instructions for the buy operation
"""
if input_amount == 0:
raise ValueError("Amount cannot be zero")
instructions = []
# Validate pool contains WSOL or USDC
if not params.is_wsol and not params.is_usdc:
raise ValueError("Pool must contain WSOL or USDC")
# Determine if quote is WSOL/USDC
quote_is_wsol_or_usdc = (
params.quote_mint == WSOL_TOKEN_ACCOUNT or params.quote_mint == USDC_TOKEN_ACCOUNT
)
# Get creator
creator = Pubkey.from_string("11111111111111111111111111111111")
if params.coin_creator_vault_authority != DEFAULT_COIN_CREATOR_VAULT_AUTHORITY:
creator = params.coin_creator_vault_authority
# Calculate amounts
if quote_is_wsol_or_usdc:
result = buy_quote_input_internal(
input_amount,
slippage_bps,
params.pool_base_token_reserves,
params.pool_quote_token_reserves,
creator,
)
token_amount, sol_amount = result[0], result[1]
else:
result = sell_base_input_internal(
input_amount,
slippage_bps,
params.pool_base_token_reserves,
params.pool_quote_token_reserves,
creator,
)
token_amount, sol_amount = result[1], result[0]
if fixed_output_amount is not None:
token_amount = fixed_output_amount
# Get user token accounts
user_base_token_account = get_associated_token_address(
payer, params.base_mint, params.base_token_program
)
user_quote_token_account = get_associated_token_address(
payer, params.quote_mint, params.quote_token_program
)
# Determine fee recipient
if params.is_mayhem_mode:
fee_recipient, fee_recipient_meta = get_mayhem_fee_recipient_random()
else:
fee_recipient = FEE_RECIPIENT
fee_recipient_meta = AccountMeta(FEE_RECIPIENT, False, False)
# Get fee recipient ATA
if params.is_mayhem_mode:
fee_recipient_ata_addr = fee_recipient_ata(fee_recipient, WSOL_TOKEN_ACCOUNT)
else:
fee_recipient_ata_addr = fee_recipient_ata(fee_recipient, params.quote_mint)
# Handle WSOL if needed
if create_input_ata:
wrap_amount = input_amount if quote_is_wsol_or_usdc and use_exact_quote_amount else sol_amount
if params.is_wsol:
instructions.extend(handle_wsol(payer, wrap_amount))
# Create output ATA if needed
if create_output_ata:
output_mint_to_create = params.base_mint if quote_is_wsol_or_usdc else params.quote_mint
output_program = params.base_token_program if quote_is_wsol_or_usdc else params.quote_token_program
instructions.append(
create_associated_token_account_idempotent_instruction(
payer, payer, output_mint_to_create, output_program
)
)
# Build accounts list
accounts = [
AccountMeta(params.pool, False, True), # pool_id (writable)
AccountMeta(payer, True, True), # user (signer, writable)
AccountMeta(GLOBAL_ACCOUNT, False, False), # global (readonly)
AccountMeta(params.base_mint, False, False), # base_mint (readonly)
AccountMeta(params.quote_mint, False, False), # quote_mint (readonly)
AccountMeta(user_base_token_account, False, True), # user_base_token_account (writable)
AccountMeta(user_quote_token_account, False, True), # user_quote_token_account (writable)
AccountMeta(params.pool_base_token_account, False, True), # pool_base_token_account (writable)
AccountMeta(params.pool_quote_token_account, False, True), # pool_quote_token_account (writable)
fee_recipient_meta, # fee_recipient (readonly)
AccountMeta(fee_recipient_ata_addr, False, True), # fee_recipient_ata (writable)
AccountMeta(params.base_token_program, False, False), # base_token_program (readonly)
AccountMeta(params.quote_token_program, False, False), # quote_token_program (readonly)
AccountMeta(SYSTEM_PROGRAM, False, False), # system_program (readonly)
AccountMeta(ASSOCIATED_TOKEN_PROGRAM, False, False), # associated_token_program (readonly)
AccountMeta(EVENT_AUTHORITY, False, False), # event_authority (readonly)
AccountMeta(PUMPSWAP_PROGRAM_ID, False, False), # program (readonly)
AccountMeta(params.coin_creator_vault_ata, False, True), # coin_creator_vault_ata (writable)
AccountMeta(params.coin_creator_vault_authority, False, False), # coin_creator_vault_authority (readonly)
]
# Add volume accumulator accounts for WSOL/USDC pools
if quote_is_wsol_or_usdc:
accounts.append(AccountMeta(GLOBAL_VOLUME_ACCUMULATOR, False, True))
uva = get_user_volume_accumulator_pda(payer)
accounts.append(AccountMeta(uva, False, True))
# Add fee accounts
accounts.append(AccountMeta(FEE_CONFIG, False, False))
accounts.append(AccountMeta(FEE_PROGRAM, False, False))
# Add cashback WSOL ATA
if params.is_cashback_coin:
wsol_ata = get_user_volume_accumulator_wsol_ata(payer)
accounts.append(AccountMeta(wsol_ata, False, True))
# Add pool v2 PDA
pool_v2 = get_pool_v2_pda(params.base_mint)
accounts.append(AccountMeta(pool_v2, False, False))
# Build instruction data
track_volume = bytes([1, 1]) if params.is_cashback_coin else bytes([1, 0])
if quote_is_wsol_or_usdc:
if use_exact_quote_amount:
min_base_out = calculate_with_slippage_sell(token_amount, slippage_bps)
data = BUY_EXACT_QUOTE_IN_DISCRIMINATOR + struct.pack("<QQ", input_amount, min_base_out) + track_volume
else:
data = BUY_DISCRIMINATOR + struct.pack("<QQ", token_amount, sol_amount) + track_volume
else:
data = SELL_DISCRIMINATOR + struct.pack("<QQ", sol_amount, token_amount)
instructions.append(Instruction(PUMPSWAP_PROGRAM_ID, data, accounts))
# Close WSOL ATA if requested
if close_input_ata and params.is_wsol:
instructions.extend(close_wsol(payer))
return instructions
# ============================================
# Build Sell Instructions
# ============================================
def build_sell_instructions(
payer: Pubkey,
input_mint: Pubkey,
input_amount: int,
params: PumpSwapParams,
slippage_bps: int = DEFAULT_SLIPPAGE,
create_output_ata: bool = True,
close_output_ata: bool = False,
close_input_ata: bool = False,
fixed_output_amount: Optional[int] = None,
) -> List[Instruction]:
"""
Build PumpSwap sell instructions.
Args:
payer: The wallet paying for the swap
input_mint: The token mint to sell (base_mint)
input_amount: Amount of tokens to sell
params: PumpSwap protocol parameters
slippage_bps: Slippage tolerance in basis points
create_output_ata: Whether to create WSOL ATA for receiving SOL
close_output_ata: Whether to close WSOL ATA after swap
close_input_ata: Whether to close token ATA after swap
fixed_output_amount: If set, use this as exact output amount
Returns:
List of instructions for the sell operation
"""
if input_amount == 0:
raise ValueError("Amount cannot be zero")
instructions = []
# Validate pool contains WSOL or USDC
if not params.is_wsol and not params.is_usdc:
raise ValueError("Pool must contain WSOL or USDC")
# Determine if quote is WSOL/USDC
quote_is_wsol_or_usdc = (
params.quote_mint == WSOL_TOKEN_ACCOUNT or params.quote_mint == USDC_TOKEN_ACCOUNT
)
# Get creator
creator = Pubkey.from_string("11111111111111111111111111111111")
if params.coin_creator_vault_authority != DEFAULT_COIN_CREATOR_VAULT_AUTHORITY:
creator = params.coin_creator_vault_authority
# Calculate amounts
if quote_is_wsol_or_usdc:
result = sell_base_input_internal(
input_amount,
slippage_bps,
params.pool_base_token_reserves,
params.pool_quote_token_reserves,
creator,
)
token_amount, sol_amount = input_amount, result[0]
else:
result = buy_quote_input_internal(
input_amount,
slippage_bps,
params.pool_base_token_reserves,
params.pool_quote_token_reserves,
creator,
)
token_amount, sol_amount = result[1], result[0]
if fixed_output_amount is not None:
sol_amount = fixed_output_amount
# Get user token accounts
user_base_token_account = get_associated_token_address(
payer, params.base_mint, params.base_token_program
)
user_quote_token_account = get_associated_token_address(
payer, params.quote_mint, params.quote_token_program
)
# Determine fee recipient
if params.is_mayhem_mode:
fee_recipient, fee_recipient_meta = get_mayhem_fee_recipient_random()
else:
fee_recipient = FEE_RECIPIENT
fee_recipient_meta = AccountMeta(FEE_RECIPIENT, False, False)
# Get fee recipient ATA
if params.is_mayhem_mode:
fee_recipient_ata_addr = fee_recipient_ata(fee_recipient, WSOL_TOKEN_ACCOUNT)
else:
fee_recipient_ata_addr = fee_recipient_ata(fee_recipient, params.quote_mint)
# Create WSOL ATA if needed
if create_output_ata and params.is_wsol:
instructions.append(
create_associated_token_account_idempotent_instruction(
payer, payer, WSOL_TOKEN_ACCOUNT, TOKEN_PROGRAM
)
)
# Build accounts list
accounts = [
AccountMeta(params.pool, False, True), # pool_id (writable)
AccountMeta(payer, True, True), # user (signer, writable)
AccountMeta(GLOBAL_ACCOUNT, False, False), # global (readonly)
AccountMeta(params.base_mint, False, False), # base_mint (readonly)
AccountMeta(params.quote_mint, False, False), # quote_mint (readonly)
AccountMeta(user_base_token_account, False, True), # user_base_token_account (writable)
AccountMeta(user_quote_token_account, False, True), # user_quote_token_account (writable)
AccountMeta(params.pool_base_token_account, False, True), # pool_base_token_account (writable)
AccountMeta(params.pool_quote_token_account, False, True), # pool_quote_token_account (writable)
fee_recipient_meta, # fee_recipient (readonly)
AccountMeta(fee_recipient_ata_addr, False, True), # fee_recipient_ata (writable)
AccountMeta(params.base_token_program, False, False), # base_token_program (readonly)
AccountMeta(params.quote_token_program, False, False), # quote_token_program (readonly)
AccountMeta(SYSTEM_PROGRAM, False, False), # system_program (readonly)
AccountMeta(ASSOCIATED_TOKEN_PROGRAM, False, False), # associated_token_program (readonly)
AccountMeta(EVENT_AUTHORITY, False, False), # event_authority (readonly)
AccountMeta(PUMPSWAP_PROGRAM_ID, False, False), # program (readonly)
AccountMeta(params.coin_creator_vault_ata, False, True), # coin_creator_vault_ata (writable)
AccountMeta(params.coin_creator_vault_authority, False, False), # coin_creator_vault_authority (readonly)
]
# Add volume accumulator accounts for non-WSOL/USDC pools
if not quote_is_wsol_or_usdc:
accounts.append(AccountMeta(GLOBAL_VOLUME_ACCUMULATOR, False, True))
uva = get_user_volume_accumulator_pda(payer)
accounts.append(AccountMeta(uva, False, True))
# Add fee accounts
accounts.append(AccountMeta(FEE_CONFIG, False, False))
accounts.append(AccountMeta(FEE_PROGRAM, False, False))
# Add cashback accounts for sell
if params.is_cashback_coin:
quote_ata = get_user_volume_accumulator_quote_ata(
payer, params.quote_mint, params.quote_token_program
)
accumulator = get_user_volume_accumulator_pda(payer)
accounts.append(AccountMeta(quote_ata, False, True))
accounts.append(AccountMeta(accumulator, False, True))
# Add pool v2 PDA
pool_v2 = get_pool_v2_pda(params.base_mint)
accounts.append(AccountMeta(pool_v2, False, False))
# Build instruction data
if quote_is_wsol_or_usdc:
data = SELL_DISCRIMINATOR + struct.pack("<QQ", token_amount, sol_amount)
else:
data = BUY_DISCRIMINATOR + struct.pack("<QQ", sol_amount, token_amount)
instructions.append(Instruction(PUMPSWAP_PROGRAM_ID, data, accounts))
# Close WSOL ATA if requested
if close_output_ata and params.is_wsol:
instructions.extend(close_wsol(payer))
# Close token ATA if requested
if close_input_ata:
token_program = params.base_token_program if quote_is_wsol_or_usdc else params.quote_token_program
token_account = user_base_token_account if quote_is_wsol_or_usdc else user_quote_token_account
instructions.append(
close_token_account_instruction(
token_program,
token_account,
payer,
payer,
)
)
return instructions
# ============================================
# Claim Cashback Instruction
# ============================================
def claim_cashback_pumpswap_instruction(
payer: Pubkey,
quote_mint: Pubkey,
quote_token_program: Pubkey,
) -> Instruction:
"""
Build instruction to claim cashback for PumpSwap AMM.
Transfers WSOL from UserVolumeAccumulator's WSOL ATA to user's WSOL ATA.
"""
user_volume_accumulator = get_user_volume_accumulator_pda(payer)
user_volume_accumulator_wsol_ata = get_user_volume_accumulator_wsol_ata(payer)
user_wsol_ata = get_associated_token_address(payer, quote_mint, quote_token_program)
accounts = [
AccountMeta(payer, True, True), # user (signer, writable)
AccountMeta(user_volume_accumulator, False, True), # user_volume_accumulator (writable)
AccountMeta(quote_mint, False, False), # quote_mint (readonly)
AccountMeta(quote_token_program, False, False), # quote_token_program (readonly)
AccountMeta(user_volume_accumulator_wsol_ata, False, True), # user_volume_accumulator_wsol_token_account (writable)
AccountMeta(user_wsol_ata, False, True), # user_wsol_token_account (writable)
AccountMeta(SYSTEM_PROGRAM, False, False), # system_program
AccountMeta(EVENT_AUTHORITY, False, False), # event_authority
AccountMeta(PUMPSWAP_PROGRAM_ID, False, False), # program
]
return Instruction(PUMPSWAP_PROGRAM_ID, CLAIM_CASHBACK_DISCRIMINATOR, accounts)
# ============================================
# Exports
# ============================================
__all__ = [
# Program IDs and Constants
"PUMPSWAP_PROGRAM_ID",
"FEE_RECIPIENT",
"GLOBAL_ACCOUNT",
"EVENT_AUTHORITY",
"ASSOCIATED_TOKEN_PROGRAM",
"PROTOCOL_FEE_RECIPIENT",
"PUMP_PROGRAM_ID",
"FEE_PROGRAM",
"GLOBAL_VOLUME_ACCUMULATOR",
"FEE_CONFIG",
"DEFAULT_COIN_CREATOR_VAULT_AUTHORITY",
"MAYHEM_FEE_RECIPIENTS",
"LP_FEE_BASIS_POINTS",
"PROTOCOL_FEE_BASIS_POINTS",
"COIN_CREATOR_FEE_BASIS_POINTS",
# Discriminators
"BUY_DISCRIMINATOR",
"BUY_EXACT_QUOTE_IN_DISCRIMINATOR",
"SELL_DISCRIMINATOR",
"CLAIM_CASHBACK_DISCRIMINATOR",
# PDA Functions
"get_pool_v2_pda",
"get_pump_pool_authority_pda",
"get_canonical_pool_pda",
"get_user_volume_accumulator_pda",
"get_user_volume_accumulator_wsol_ata",
"get_user_volume_accumulator_quote_ata",
"coin_creator_vault_authority",
"coin_creator_vault_ata",
"fee_recipient_ata",
"get_mayhem_fee_recipient_random",
# Params
"PumpSwapParams",
# Calculation Functions
"buy_quote_input_internal",
"sell_base_input_internal",
# Instruction Builders
"build_buy_instructions",
"build_sell_instructions",
"claim_cashback_pumpswap_instruction",
]
@@ -1,374 +0,0 @@
"""
Raydium AMM V4 instruction builder for Solana trading SDK.
Production-grade implementation with all constants, discriminators, and PDA derivation functions.
"""
from typing import List, Optional
from dataclasses import dataclass
from solders.pubkey import Pubkey
from solders.instruction import Instruction, AccountMeta
import struct
from .common import (
TOKEN_PROGRAM,
WSOL_TOKEN_ACCOUNT,
USDC_TOKEN_ACCOUNT,
DEFAULT_SLIPPAGE,
get_associated_token_address,
create_associated_token_account_idempotent_instruction,
handle_wsol,
close_wsol,
close_token_account_instruction,
calculate_with_slippage_sell,
)
# ============================================
# Raydium AMM V4 Program ID
# ============================================
RAYDIUM_AMM_V4_PROGRAM_ID: Pubkey = Pubkey.from_string("675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8")
# ============================================
# Raydium AMM V4 Constants
# ============================================
# Authority
AUTHORITY: Pubkey = Pubkey.from_string("5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1")
# Fee Rates
TRADE_FEE_NUMERATOR: int = 25
TRADE_FEE_DENOMINATOR: int = 10000
SWAP_FEE_NUMERATOR: int = 25
SWAP_FEE_DENOMINATOR: int = 10000
# ============================================
# Instruction Discriminators
# ============================================
# Note: Raydium AMM V4 uses single-byte discriminators
SWAP_BASE_IN_DISCRIMINATOR: bytes = bytes([9])
SWAP_BASE_OUT_DISCRIMINATOR: bytes = bytes([11])
# ============================================
# Seeds
# ============================================
POOL_SEED = b"pool"
# ============================================
# Raydium AMM V4 Parameters Dataclass
# ============================================
@dataclass
class RaydiumAmmV4Params:
"""Parameters for Raydium AMM V4 protocol trading."""
amm: Pubkey = Pubkey.from_string("11111111111111111111111111111111")
coin_mint: Pubkey = Pubkey.from_string("11111111111111111111111111111111")
pc_mint: Pubkey = Pubkey.from_string("11111111111111111111111111111111")
token_coin: Pubkey = Pubkey.from_string("11111111111111111111111111111111")
token_pc: Pubkey = Pubkey.from_string("11111111111111111111111111111111")
coin_reserve: int = 0
pc_reserve: int = 0
@property
def is_wsol(self) -> bool:
"""Check if the pool contains WSOL."""
return self.coin_mint == WSOL_TOKEN_ACCOUNT or self.pc_mint == WSOL_TOKEN_ACCOUNT
@property
def is_usdc(self) -> bool:
"""Check if the pool contains USDC."""
return self.coin_mint == USDC_TOKEN_ACCOUNT or self.pc_mint == USDC_TOKEN_ACCOUNT
# ============================================
# Raydium AMM V4 Calculation Functions
# ============================================
def compute_swap_amount(
coin_reserve: int,
pc_reserve: int,
is_coin_in: bool,
amount_in: int,
slippage_bps: int,
) -> tuple:
"""
Compute swap output amount for Raydium AMM V4.
Returns (amount_out, min_amount_out).
"""
# Apply trade fee (0.25%)
amount_in_after_fee = amount_in - (amount_in * TRADE_FEE_NUMERATOR) // TRADE_FEE_DENOMINATOR
if is_coin_in:
# Coin -> PC
# out = (amount_in * pc_reserve) / (coin_reserve + amount_in)
numerator = amount_in_after_fee * pc_reserve
denominator = coin_reserve + amount_in_after_fee
amount_out = numerator // denominator
else:
# PC -> Coin
# out = (amount_in * coin_reserve) / (pc_reserve + amount_in)
numerator = amount_in_after_fee * coin_reserve
denominator = pc_reserve + amount_in_after_fee
amount_out = numerator // denominator
# Apply slippage
min_amount_out = calculate_with_slippage_sell(amount_out, slippage_bps)
return (amount_out, min_amount_out)
# ============================================
# Build Buy Instructions
# ============================================
def build_buy_instructions(
payer: Pubkey,
output_mint: Pubkey,
input_amount: int,
params: RaydiumAmmV4Params,
slippage_bps: int = DEFAULT_SLIPPAGE,
create_input_ata: bool = True,
create_output_ata: bool = True,
close_input_ata: bool = False,
fixed_output_amount: Optional[int] = None,
) -> List[Instruction]:
"""
Build Raydium AMM V4 buy instructions.
Args:
payer: The wallet paying for the swap
output_mint: The token mint to buy
input_amount: Amount of SOL/USDC to spend
params: Raydium AMM V4 protocol parameters
slippage_bps: Slippage tolerance in basis points
create_input_ata: Whether to create WSOL ATA if needed
create_output_ata: Whether to create output token ATA if needed
close_input_ata: Whether to close WSOL ATA after swap
fixed_output_amount: If set, use this as exact output amount
Returns:
List of instructions for the buy operation
"""
if input_amount == 0:
raise ValueError("Amount cannot be zero")
instructions = []
# Validate pool contains WSOL or USDC
if not params.is_wsol and not params.is_usdc:
raise ValueError("Pool must contain WSOL or USDC")
# Determine if coin is input (WSOL/USDC)
is_coin_in = params.coin_mint == WSOL_TOKEN_ACCOUNT or params.coin_mint == USDC_TOKEN_ACCOUNT
# Calculate swap amount
_, min_amount_out = compute_swap_amount(
params.coin_reserve,
params.pc_reserve,
is_coin_in,
input_amount,
slippage_bps,
)
if fixed_output_amount is not None:
minimum_amount_out = fixed_output_amount
else:
minimum_amount_out = min_amount_out
# Determine input mint (WSOL or USDC)
input_mint = WSOL_TOKEN_ACCOUNT if params.is_wsol else USDC_TOKEN_ACCOUNT
# Get user token accounts
user_source_token_account = get_associated_token_address(payer, input_mint, TOKEN_PROGRAM)
user_destination_token_account = get_associated_token_address(payer, output_mint, TOKEN_PROGRAM)
# Handle WSOL if needed
if create_input_ata and params.is_wsol:
instructions.extend(handle_wsol(payer, input_amount))
# Create output ATA if needed
if create_output_ata:
instructions.append(
create_associated_token_account_idempotent_instruction(
payer, payer, output_mint, TOKEN_PROGRAM
)
)
# Build instruction data (1 byte discriminator + 8 bytes amount_in + 8 bytes min_out)
data = SWAP_BASE_IN_DISCRIMINATOR + struct.pack("<QQ", input_amount, minimum_amount_out)
# Build accounts list (17 accounts)
# Note: Raydium AMM V4 has specific account ordering
accounts = [
AccountMeta(TOKEN_PROGRAM, False, False), # token_program (readonly)
AccountMeta(params.amm, False, True), # amm (writable)
AccountMeta(AUTHORITY, False, False), # authority (readonly)
AccountMeta(params.amm, False, False), # amm_open_orders (uses amm address)
AccountMeta(params.token_coin, False, True), # pool_coin_token_account (writable)
AccountMeta(params.token_pc, False, True), # pool_pc_token_account (writable)
AccountMeta(params.amm, False, False), # serum_program (placeholder)
AccountMeta(params.amm, False, False), # serum_market (placeholder)
AccountMeta(params.amm, False, False), # serum_bids (placeholder)
AccountMeta(params.amm, False, False), # serum_asks (placeholder)
AccountMeta(params.amm, False, False), # serum_event_queue (placeholder)
AccountMeta(params.amm, False, False), # serum_coin_vault_account (placeholder)
AccountMeta(params.amm, False, False), # serum_pc_vault_account (placeholder)
AccountMeta(params.amm, False, False), # serum_vault_signer (placeholder)
AccountMeta(user_source_token_account, False, True), # user_source_token_account (writable)
AccountMeta(user_destination_token_account, False, True), # user_destination_token_account (writable)
AccountMeta(payer, True, False), # user_source_owner (signer)
]
instructions.append(Instruction(RAYDIUM_AMM_V4_PROGRAM_ID, data, accounts))
# Close WSOL ATA if requested
if close_input_ata and params.is_wsol:
instructions.extend(close_wsol(payer))
return instructions
# ============================================
# Build Sell Instructions
# ============================================
def build_sell_instructions(
payer: Pubkey,
input_mint: Pubkey,
input_amount: int,
params: RaydiumAmmV4Params,
slippage_bps: int = DEFAULT_SLIPPAGE,
create_output_ata: bool = True,
close_output_ata: bool = False,
close_input_ata: bool = False,
fixed_output_amount: Optional[int] = None,
) -> List[Instruction]:
"""
Build Raydium AMM V4 sell instructions.
Args:
payer: The wallet paying for the swap
input_mint: The token mint to sell
input_amount: Amount of tokens to sell
params: Raydium AMM V4 protocol parameters
slippage_bps: Slippage tolerance in basis points
create_output_ata: Whether to create WSOL ATA for receiving SOL
close_output_ata: Whether to close WSOL ATA after swap
close_input_ata: Whether to close token ATA after swap
fixed_output_amount: If set, use this as exact output amount
Returns:
List of instructions for the sell operation
"""
if input_amount == 0:
raise ValueError("Amount cannot be zero")
instructions = []
# Validate pool contains WSOL or USDC
if not params.is_wsol and not params.is_usdc:
raise ValueError("Pool must contain WSOL or USDC")
# Determine if pc is output (WSOL/USDC)
# For sell: is_base_in = True means we're selling PC to get Coin
# is_base_in = False means we're selling Coin to get PC
is_pc_out = params.pc_mint == WSOL_TOKEN_ACCOUNT or params.pc_mint == USDC_TOKEN_ACCOUNT
# Calculate swap amount (is_pc_out reversed because we're selling to get WSOL/USDC)
_, min_amount_out = compute_swap_amount(
params.coin_reserve,
params.pc_reserve,
not is_pc_out, # Reversed for sell
input_amount,
slippage_bps,
)
if fixed_output_amount is not None:
minimum_amount_out = fixed_output_amount
else:
minimum_amount_out = min_amount_out
# Determine output mint (WSOL or USDC)
output_mint = WSOL_TOKEN_ACCOUNT if params.is_wsol else USDC_TOKEN_ACCOUNT
# Get user token accounts
user_source_token_account = get_associated_token_address(payer, input_mint, TOKEN_PROGRAM)
user_destination_token_account = get_associated_token_address(payer, output_mint, TOKEN_PROGRAM)
# Create WSOL ATA if needed for receiving SOL
if create_output_ata and params.is_wsol:
instructions.append(
create_associated_token_account_idempotent_instruction(
payer, payer, WSOL_TOKEN_ACCOUNT, TOKEN_PROGRAM
)
)
# Build instruction data (1 byte discriminator + 8 bytes amount_in + 8 bytes min_out)
data = SWAP_BASE_IN_DISCRIMINATOR + struct.pack("<QQ", input_amount, minimum_amount_out)
# Build accounts list (17 accounts)
accounts = [
AccountMeta(TOKEN_PROGRAM, False, False), # token_program (readonly)
AccountMeta(params.amm, False, True), # amm (writable)
AccountMeta(AUTHORITY, False, False), # authority (readonly)
AccountMeta(params.amm, False, False), # amm_open_orders (uses amm address)
AccountMeta(params.token_coin, False, True), # pool_coin_token_account (writable)
AccountMeta(params.token_pc, False, True), # pool_pc_token_account (writable)
AccountMeta(params.amm, False, False), # serum_program (placeholder)
AccountMeta(params.amm, False, False), # serum_market (placeholder)
AccountMeta(params.amm, False, False), # serum_bids (placeholder)
AccountMeta(params.amm, False, False), # serum_asks (placeholder)
AccountMeta(params.amm, False, False), # serum_event_queue (placeholder)
AccountMeta(params.amm, False, False), # serum_coin_vault_account (placeholder)
AccountMeta(params.amm, False, False), # serum_pc_vault_account (placeholder)
AccountMeta(params.amm, False, False), # serum_vault_signer (placeholder)
AccountMeta(user_source_token_account, False, True), # user_source_token_account (writable)
AccountMeta(user_destination_token_account, False, True), # user_destination_token_account (writable)
AccountMeta(payer, True, False), # user_source_owner (signer)
]
instructions.append(Instruction(RAYDIUM_AMM_V4_PROGRAM_ID, data, accounts))
# Close WSOL ATA if requested
if close_output_ata and params.is_wsol:
instructions.extend(close_wsol(payer))
# Close token ATA if requested
if close_input_ata:
instructions.append(
close_token_account_instruction(
TOKEN_PROGRAM,
user_source_token_account,
payer,
payer,
)
)
return instructions
# ============================================
# Exports
# ============================================
__all__ = [
# Program IDs and Constants
"RAYDIUM_AMM_V4_PROGRAM_ID",
"AUTHORITY",
"TRADE_FEE_NUMERATOR",
"TRADE_FEE_DENOMINATOR",
"SWAP_FEE_NUMERATOR",
"SWAP_FEE_DENOMINATOR",
# Discriminators
"SWAP_BASE_IN_DISCRIMINATOR",
"SWAP_BASE_OUT_DISCRIMINATOR",
# Params
"RaydiumAmmV4Params",
# Calculation Functions
"compute_swap_amount",
# Instruction Builders
"build_buy_instructions",
"build_sell_instructions",
]
@@ -1,454 +0,0 @@
"""
Raydium CPMM instruction builder for Solana trading SDK.
Production-grade implementation with all constants, discriminators, and PDA derivation functions.
"""
from typing import List, Optional
from dataclasses import dataclass
from solders.pubkey import Pubkey
from solders.instruction import Instruction, AccountMeta
import struct
from .common import (
SYSTEM_PROGRAM,
TOKEN_PROGRAM,
TOKEN_PROGRAM_2022,
WSOL_TOKEN_ACCOUNT,
USDC_TOKEN_ACCOUNT,
DEFAULT_SLIPPAGE,
get_associated_token_address,
create_associated_token_account_idempotent_instruction,
handle_wsol,
close_wsol,
close_token_account_instruction,
calculate_with_slippage_sell,
)
# ============================================
# Raydium CPMM Program ID
# ============================================
RAYDIUM_CPMM_PROGRAM_ID: Pubkey = Pubkey.from_string("CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C")
# ============================================
# Raydium CPMM Constants
# ============================================
# Authority
AUTHORITY: Pubkey = Pubkey.from_string("GpMZbSM2GgvTKHJirzeGfMFoaZ8UR2X7F4v8vHTvxFbL")
# Fee Rates
FEE_RATE_DENOMINATOR_VALUE: int = 1_000_000
TRADE_FEE_RATE: int = 2500
CREATOR_FEE_RATE: int = 0
PROTOCOL_FEE_RATE: int = 120000
FUND_FEE_RATE: int = 40000
# ============================================
# Instruction Discriminators
# ============================================
SWAP_BASE_IN_DISCRIMINATOR: bytes = bytes([143, 190, 90, 218, 196, 30, 51, 222])
SWAP_BASE_OUT_DISCRIMINATOR: bytes = bytes([55, 217, 98, 86, 163, 74, 180, 173])
# ============================================
# Seeds
# ============================================
POOL_SEED = b"pool"
POOL_VAULT_SEED = b"pool_vault"
OBSERVATION_STATE_SEED = b"observation"
# ============================================
# PDA Derivation Functions
# ============================================
def get_pool_pda(amm_config: Pubkey, mint1: Pubkey, mint2: Pubkey) -> Pubkey:
"""
Derive the pool PDA for a given amm_config and two mints.
Seeds: ["pool", amm_config, mint1, mint2]
"""
seeds = [POOL_SEED, bytes(amm_config), bytes(mint1), bytes(mint2)]
(pda, _) = Pubkey.find_program_address(seeds, RAYDIUM_CPMM_PROGRAM_ID)
return pda
def get_vault_pda(pool_state: Pubkey, mint: Pubkey) -> Pubkey:
"""
Derive the vault PDA for a given pool and mint.
Seeds: ["pool_vault", pool_state, mint]
"""
seeds = [POOL_VAULT_SEED, bytes(pool_state), bytes(mint)]
(pda, _) = Pubkey.find_program_address(seeds, RAYDIUM_CPMM_PROGRAM_ID)
return pda
def get_observation_state_pda(pool_state: Pubkey) -> Pubkey:
"""
Derive the observation state PDA for a given pool.
Seeds: ["observation", pool_state]
"""
seeds = [OBSERVATION_STATE_SEED, bytes(pool_state)]
(pda, _) = Pubkey.find_program_address(seeds, RAYDIUM_CPMM_PROGRAM_ID)
return pda
# ============================================
# Raydium CPMM Parameters Dataclass
# ============================================
@dataclass
class RaydiumCpmmParams:
"""Parameters for Raydium CPMM protocol trading."""
pool_state: Optional[Pubkey] = None # If None, will derive from mints and amm_config
amm_config: Pubkey = Pubkey.from_string("11111111111111111111111111111111")
base_mint: Pubkey = Pubkey.from_string("11111111111111111111111111111111")
quote_mint: Pubkey = Pubkey.from_string("11111111111111111111111111111111")
base_reserve: int = 0
quote_reserve: int = 0
base_vault: Optional[Pubkey] = None
quote_vault: Optional[Pubkey] = None
base_token_program: Pubkey = TOKEN_PROGRAM
quote_token_program: Pubkey = TOKEN_PROGRAM
observation_state: Optional[Pubkey] = None
@property
def is_wsol(self) -> bool:
"""Check if the pool contains WSOL."""
return self.base_mint == WSOL_TOKEN_ACCOUNT or self.quote_mint == WSOL_TOKEN_ACCOUNT
@property
def is_usdc(self) -> bool:
"""Check if the pool contains USDC."""
return self.base_mint == USDC_TOKEN_ACCOUNT or self.quote_mint == USDC_TOKEN_ACCOUNT
# ============================================
# Raydium CPMM Calculation Functions
# ============================================
def compute_swap_amount(
base_reserve: int,
quote_reserve: int,
is_base_in: bool,
amount_in: int,
slippage_bps: int,
) -> tuple:
"""
Compute swap output amount for Raydium CPMM.
Returns (amount_out, min_amount_out).
"""
# Apply trade fee (0.25%)
fee_rate = TRADE_FEE_RATE / FEE_RATE_DENOMINATOR_VALUE
amount_in_after_fee = int(amount_in * (1 - fee_rate))
if is_base_in:
# Base -> Quote
# out = (amount_in * quote_reserve) / (base_reserve + amount_in)
numerator = amount_in_after_fee * quote_reserve
denominator = base_reserve + amount_in_after_fee
amount_out = numerator // denominator
else:
# Quote -> Base
# out = (amount_in * base_reserve) / (quote_reserve + amount_in)
numerator = amount_in_after_fee * base_reserve
denominator = quote_reserve + amount_in_after_fee
amount_out = numerator // denominator
# Apply slippage
min_amount_out = calculate_with_slippage_sell(amount_out, slippage_bps)
return (amount_out, min_amount_out)
# ============================================
# Build Buy Instructions
# ============================================
def build_buy_instructions(
payer: Pubkey,
output_mint: Pubkey,
input_amount: int,
params: RaydiumCpmmParams,
slippage_bps: int = DEFAULT_SLIPPAGE,
create_input_ata: bool = True,
create_output_ata: bool = True,
close_input_ata: bool = False,
fixed_output_amount: Optional[int] = None,
) -> List[Instruction]:
"""
Build Raydium CPMM buy instructions.
Args:
payer: The wallet paying for the swap
output_mint: The token mint to buy
input_amount: Amount of SOL/USDC to spend
params: Raydium CPMM protocol parameters
slippage_bps: Slippage tolerance in basis points
create_input_ata: Whether to create WSOL ATA if needed
create_output_ata: Whether to create output token ATA if needed
close_input_ata: Whether to close WSOL ATA after swap
fixed_output_amount: If set, use this as exact output amount
Returns:
List of instructions for the buy operation
"""
if input_amount == 0:
raise ValueError("Amount cannot be zero")
instructions = []
# Validate pool contains WSOL or USDC
if not params.is_wsol and not params.is_usdc:
raise ValueError("Pool must contain WSOL or USDC")
# Get pool state
pool_state = params.pool_state
if pool_state is None:
pool_state = get_pool_pda(params.amm_config, params.base_mint, params.quote_mint)
# Determine if base is input (WSOL/USDC)
is_base_in = params.base_mint == WSOL_TOKEN_ACCOUNT or params.base_mint == USDC_TOKEN_ACCOUNT
# Get output token program
mint_token_program = params.quote_token_program if is_base_in else params.base_token_program
# Calculate swap amount
_, min_amount_out = compute_swap_amount(
params.base_reserve,
params.quote_reserve,
is_base_in,
input_amount,
slippage_bps,
)
if fixed_output_amount is not None:
minimum_amount_out = fixed_output_amount
else:
minimum_amount_out = min_amount_out
# Determine input mint (WSOL or USDC)
input_mint = WSOL_TOKEN_ACCOUNT if params.is_wsol else USDC_TOKEN_ACCOUNT
# Get user token accounts
input_token_account = get_associated_token_address(payer, input_mint, TOKEN_PROGRAM)
output_token_account = get_associated_token_address(payer, output_mint, mint_token_program)
# Get vaults
input_vault = params.base_vault if is_base_in else params.quote_vault
if input_vault is None:
input_vault = get_vault_pda(pool_state, input_mint)
output_vault = params.quote_vault if is_base_in else params.base_vault
if output_vault is None:
output_vault = get_vault_pda(pool_state, output_mint)
# Get observation state
observation_state = params.observation_state
if observation_state is None:
observation_state = get_observation_state_pda(pool_state)
# Handle WSOL if needed
if create_input_ata and params.is_wsol:
instructions.extend(handle_wsol(payer, input_amount))
# Create output ATA if needed
if create_output_ata:
instructions.append(
create_associated_token_account_idempotent_instruction(
payer, payer, output_mint, mint_token_program
)
)
# Build instruction data
data = SWAP_BASE_IN_DISCRIMINATOR + struct.pack("<QQ", input_amount, minimum_amount_out)
# Build accounts list
accounts = [
AccountMeta(payer, True, True), # payer (signer, writable)
AccountMeta(AUTHORITY, False, False), # authority (readonly)
AccountMeta(params.amm_config, False, False), # amm_config (readonly)
AccountMeta(pool_state, False, True), # pool_state (writable)
AccountMeta(input_token_account, False, True), # input_token_account (writable)
AccountMeta(output_token_account, False, True), # output_token_account (writable)
AccountMeta(input_vault, False, True), # input_vault (writable)
AccountMeta(output_vault, False, True), # output_vault (writable)
AccountMeta(TOKEN_PROGRAM, False, False), # input_token_program (readonly)
AccountMeta(mint_token_program, False, False), # output_token_program (readonly)
AccountMeta(input_mint, False, False), # input_token_mint (readonly)
AccountMeta(output_mint, False, False), # output_token_mint (readonly)
AccountMeta(observation_state, False, True), # observation_state (writable)
]
instructions.append(Instruction(RAYDIUM_CPMM_PROGRAM_ID, data, accounts))
# Close WSOL ATA if requested
if close_input_ata and params.is_wsol:
instructions.extend(close_wsol(payer))
return instructions
# ============================================
# Build Sell Instructions
# ============================================
def build_sell_instructions(
payer: Pubkey,
input_mint: Pubkey,
input_amount: int,
params: RaydiumCpmmParams,
slippage_bps: int = DEFAULT_SLIPPAGE,
create_output_ata: bool = True,
close_output_ata: bool = False,
close_input_ata: bool = False,
fixed_output_amount: Optional[int] = None,
) -> List[Instruction]:
"""
Build Raydium CPMM sell instructions.
Args:
payer: The wallet paying for the swap
input_mint: The token mint to sell
input_amount: Amount of tokens to sell
params: Raydium CPMM protocol parameters
slippage_bps: Slippage tolerance in basis points
create_output_ata: Whether to create WSOL ATA for receiving SOL
close_output_ata: Whether to close WSOL ATA after swap
close_input_ata: Whether to close token ATA after swap
fixed_output_amount: If set, use this as exact output amount
Returns:
List of instructions for the sell operation
"""
if input_amount == 0:
raise ValueError("Amount cannot be zero")
instructions = []
# Validate pool contains WSOL or USDC
if not params.is_wsol and not params.is_usdc:
raise ValueError("Pool must contain WSOL or USDC")
# Get pool state
pool_state = params.pool_state
if pool_state is None:
pool_state = get_pool_pda(params.amm_config, params.base_mint, params.quote_mint)
# Determine if quote is output (WSOL/USDC)
is_quote_out = params.quote_mint == WSOL_TOKEN_ACCOUNT or params.quote_mint == USDC_TOKEN_ACCOUNT
# Get input token program
mint_token_program = params.base_token_program if is_quote_out else params.quote_token_program
# Calculate swap amount
_, min_amount_out = compute_swap_amount(
params.base_reserve,
params.quote_reserve,
is_quote_out, # Swap direction is reversed for sell
input_amount,
slippage_bps,
)
if fixed_output_amount is not None:
minimum_amount_out = fixed_output_amount
else:
minimum_amount_out = min_amount_out
# Determine output mint (WSOL or USDC)
output_mint = WSOL_TOKEN_ACCOUNT if params.is_wsol else USDC_TOKEN_ACCOUNT
# Get user token accounts
output_token_account = get_associated_token_address(payer, output_mint, TOKEN_PROGRAM)
input_token_account = get_associated_token_address(payer, input_mint, mint_token_program)
# Get vaults
output_vault = params.quote_vault if is_quote_out else params.base_vault
if output_vault is None:
output_vault = get_vault_pda(pool_state, output_mint)
input_vault = params.base_vault if is_quote_out else params.quote_vault
if input_vault is None:
input_vault = get_vault_pda(pool_state, input_mint)
# Get observation state
observation_state = params.observation_state
if observation_state is None:
observation_state = get_observation_state_pda(pool_state)
# Create WSOL ATA if needed for receiving SOL
if create_output_ata and params.is_wsol:
instructions.append(
create_associated_token_account_idempotent_instruction(
payer, payer, WSOL_TOKEN_ACCOUNT, TOKEN_PROGRAM
)
)
# Build instruction data
data = SWAP_BASE_IN_DISCRIMINATOR + struct.pack("<QQ", input_amount, minimum_amount_out)
# Build accounts list
accounts = [
AccountMeta(payer, True, True), # payer (signer, writable)
AccountMeta(AUTHORITY, False, False), # authority (readonly)
AccountMeta(params.amm_config, False, False), # amm_config (readonly)
AccountMeta(pool_state, False, True), # pool_state (writable)
AccountMeta(input_token_account, False, True), # input_token_account (writable)
AccountMeta(output_token_account, False, True), # output_token_account (writable)
AccountMeta(input_vault, False, True), # input_vault (writable)
AccountMeta(output_vault, False, True), # output_vault (writable)
AccountMeta(mint_token_program, False, False), # input_token_program (readonly)
AccountMeta(TOKEN_PROGRAM, False, False), # output_token_program (readonly)
AccountMeta(input_mint, False, False), # input_token_mint (readonly)
AccountMeta(output_mint, False, False), # output_token_mint (readonly)
AccountMeta(observation_state, False, True), # observation_state (writable)
]
instructions.append(Instruction(RAYDIUM_CPMM_PROGRAM_ID, data, accounts))
# Close WSOL ATA if requested
if close_output_ata and params.is_wsol:
instructions.extend(close_wsol(payer))
# Close token ATA if requested
if close_input_ata:
instructions.append(
close_token_account_instruction(
mint_token_program,
input_token_account,
payer,
payer,
)
)
return instructions
# ============================================
# Exports
# ============================================
__all__ = [
# Program IDs and Constants
"RAYDIUM_CPMM_PROGRAM_ID",
"AUTHORITY",
"FEE_RATE_DENOMINATOR_VALUE",
"TRADE_FEE_RATE",
"CREATOR_FEE_RATE",
"PROTOCOL_FEE_RATE",
"FUND_FEE_RATE",
# Discriminators
"SWAP_BASE_IN_DISCRIMINATOR",
"SWAP_BASE_OUT_DISCRIMINATOR",
# PDA Functions
"get_pool_pda",
"get_vault_pda",
"get_observation_state_pda",
# Params
"RaydiumCpmmParams",
# Calculation Functions
"compute_swap_amount",
# Instruction Builders
"build_buy_instructions",
"build_sell_instructions",
]