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