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:
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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"]
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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",
|
||||
]
|
||||
Reference in New Issue
Block a user