diff --git a/src/instruction/pumpfun.rs b/src/instruction/pumpfun.rs index 9994a8e..4b02c08 100755 --- a/src/instruction/pumpfun.rs +++ b/src/instruction/pumpfun.rs @@ -9,7 +9,7 @@ use crate::{ use crate::{ instruction::utils::pumpfun::{ accounts, get_bonding_curve_pda, get_creator, get_user_volume_accumulator_pda, - global_constants::{self}, + global_constants::{self}, BUY_DISCRIMINATOR, BUY_EXACT_SOL_IN_DISCRIMINATOR, }, utils::calc::{ common::{calculate_with_slippage_buy, calculate_with_slippage_sell}, @@ -118,9 +118,23 @@ impl InstructionBuilder for PumpFunInstructionBuilder { } let mut buy_data = [0u8; 24]; - buy_data[..8].copy_from_slice(&[102, 6, 61, 18, 1, 218, 235, 234]); // Method ID - buy_data[8..16].copy_from_slice(&buy_token_amount.to_le_bytes()); - buy_data[16..24].copy_from_slice(&max_sol_cost.to_le_bytes()); + if params.use_exact_in_instruction { + // buy_exact_sol_in(spendable_sol_in: u64, min_tokens_out: u64) + // Spend exactly the input SOL amount, get at least min_tokens_out + let min_tokens_out = calculate_with_slippage_sell( + buy_token_amount, + params.slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE), + ); + buy_data[..8].copy_from_slice(&BUY_EXACT_SOL_IN_DISCRIMINATOR); + buy_data[8..16].copy_from_slice(¶ms.input_amount.unwrap_or(0).to_le_bytes()); + buy_data[16..24].copy_from_slice(&min_tokens_out.to_le_bytes()); + } else { + // buy(token_amount: u64, max_sol_cost: u64) + // Buy exactly token_amount tokens, pay up to max_sol_cost + buy_data[..8].copy_from_slice(&BUY_DISCRIMINATOR); + buy_data[8..16].copy_from_slice(&buy_token_amount.to_le_bytes()); + buy_data[16..24].copy_from_slice(&max_sol_cost.to_le_bytes()); + } // Determine fee recipient based on mayhem mode let fee_recipient_meta = if is_mayhem_mode { diff --git a/src/instruction/pumpswap.rs b/src/instruction/pumpswap.rs index eaa87cc..a25aa28 100755 --- a/src/instruction/pumpswap.rs +++ b/src/instruction/pumpswap.rs @@ -2,7 +2,7 @@ use crate::{ constants::trade::trade::DEFAULT_SLIPPAGE, instruction::utils::pumpswap::{ accounts, fee_recipient_ata, get_user_volume_accumulator_pda, BUY_DISCRIMINATOR, - SELL_DISCRIMINATOR, + BUY_EXACT_QUOTE_IN_DISCRIMINATOR, SELL_DISCRIMINATOR, }, trading::{ common::wsol_manager, @@ -187,11 +187,27 @@ impl InstructionBuilder for PumpSwapInstructionBuilder { // Create instruction data let mut data = [0u8; 24]; if quote_is_wsol_or_usdc { - data[..8].copy_from_slice(&BUY_DISCRIMINATOR); - // base_amount_out - data[8..16].copy_from_slice(&token_amount.to_le_bytes()); - // max_quote_amount_in - data[16..24].copy_from_slice(&sol_amount.to_le_bytes()); + if params.use_exact_in_instruction { + // buy_exact_quote_in(spendable_quote_in: u64, min_base_amount_out: u64) + // Spend exactly the input SOL/quote amount, get at least min_base_amount_out + let min_base_amount_out = crate::utils::calc::common::calculate_with_slippage_sell( + token_amount, + params.slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE), + ); + data[..8].copy_from_slice(&BUY_EXACT_QUOTE_IN_DISCRIMINATOR); + // spendable_quote_in (exact SOL amount to spend) + data[8..16].copy_from_slice(¶ms.input_amount.unwrap_or(0).to_le_bytes()); + // min_base_amount_out (minimum tokens to receive) + data[16..24].copy_from_slice(&min_base_amount_out.to_le_bytes()); + } else { + // buy(base_amount_out: u64, max_quote_amount_in: u64) + // Buy exactly base_amount_out tokens, pay up to max_quote_amount_in + data[..8].copy_from_slice(&BUY_DISCRIMINATOR); + // base_amount_out + data[8..16].copy_from_slice(&token_amount.to_le_bytes()); + // max_quote_amount_in + data[16..24].copy_from_slice(&sol_amount.to_le_bytes()); + } } else { data[..8].copy_from_slice(&SELL_DISCRIMINATOR); // base_amount_in diff --git a/src/instruction/utils/pumpfun.rs b/src/instruction/utils/pumpfun.rs index ff445b6..55017b2 100644 --- a/src/instruction/utils/pumpfun.rs +++ b/src/instruction/utils/pumpfun.rs @@ -154,6 +154,11 @@ pub mod accounts { }; } +/// Instruction discriminators for PumpFun program +pub const BUY_DISCRIMINATOR: [u8; 8] = [102, 6, 61, 18, 1, 218, 235, 234]; +pub const BUY_EXACT_SOL_IN_DISCRIMINATOR: [u8; 8] = [56, 252, 116, 8, 158, 223, 205, 95]; +pub const SELL_DISCRIMINATOR: [u8; 8] = [51, 230, 133, 164, 1, 127, 131, 173]; + pub struct Symbol; impl Symbol { diff --git a/src/instruction/utils/pumpswap.rs b/src/instruction/utils/pumpswap.rs index 5c466b7..bbaf4e9 100644 --- a/src/instruction/utils/pumpswap.rs +++ b/src/instruction/utils/pumpswap.rs @@ -136,6 +136,7 @@ pub mod accounts { } pub const BUY_DISCRIMINATOR: [u8; 8] = [102, 6, 61, 18, 1, 218, 235, 234]; +pub const BUY_EXACT_QUOTE_IN_DISCRIMINATOR: [u8; 8] = [198, 46, 21, 82, 180, 217, 232, 112]; pub const SELL_DISCRIMINATOR: [u8; 8] = [51, 230, 133, 164, 1, 127, 131, 173]; // Find a pool for a specific mint diff --git a/src/lib.rs b/src/lib.rs index 47c1ab7..46f24eb 100755 --- a/src/lib.rs +++ b/src/lib.rs @@ -508,6 +508,7 @@ impl TradingClient { fixed_output_amount: params.fixed_output_token_amount, gas_fee_strategy: params.gas_fee_strategy, simulate: params.simulate, + use_exact_in_instruction: false, }; // Validate protocol params @@ -617,6 +618,7 @@ impl TradingClient { fixed_output_amount: params.fixed_output_token_amount, gas_fee_strategy: params.gas_fee_strategy, simulate: params.simulate, + use_exact_in_instruction: false, }; // Validate protocol params diff --git a/src/trading/core/params.rs b/src/trading/core/params.rs index 9e275db..1c0d097 100755 --- a/src/trading/core/params.rs +++ b/src/trading/core/params.rs @@ -67,6 +67,10 @@ pub struct SwapParams { pub fixed_output_amount: Option, pub gas_fee_strategy: GasFeeStrategy, pub simulate: bool, + /// Use exact input instructions (buy_exact_sol_in for PumpFun, buy_exact_quote_in for PumpSwap). + /// When true, the exact SOL/quote amount is spent and slippage is applied to output tokens. + /// When false (default), uses regular buy instruction where slippage is applied to SOL/quote input. + pub use_exact_in_instruction: bool, } impl std::fmt::Debug for SwapParams {