diff --git a/README.md b/README.md index 8b18c72..d8ace02 100644 --- a/README.md +++ b/README.md @@ -178,6 +178,7 @@ let buy_params = sol_trade_sdk::TradeBuyParams { fixed_output_token_amount: None, // Optional: specify exact output amount gas_fee_strategy: gas_fee_strategy.clone(), // Gas fee strategy configuration simulate: false, // Set to true for simulation only + use_exact_sol_amount: None, // Use exact SOL input for PumpFun/PumpSwap (defaults to true) }; ``` diff --git a/README_CN.md b/README_CN.md index b844e2c..6b30db1 100755 --- a/README_CN.md +++ b/README_CN.md @@ -174,6 +174,7 @@ let buy_params = sol_trade_sdk::TradeBuyParams { fixed_output_token_amount: None, // 可选:指定精确输出数量 gas_fee_strategy: gas_fee_strategy.clone(), // Gas 费用策略配置 simulate: false, // 设为 true 仅进行模拟 + use_exact_sol_amount: None, // 对 PumpFun/PumpSwap 使用精确 SOL 输入(默认为 true) }; ``` diff --git a/examples/address_lookup/src/main.rs b/examples/address_lookup/src/main.rs index e40a5f9..14ff199 100644 --- a/examples/address_lookup/src/main.rs +++ b/examples/address_lookup/src/main.rs @@ -160,6 +160,7 @@ async fn pumpfun_copy_trade_with_grpc(trade_info: PumpFunTradeEvent) -> AnyResul fixed_output_token_amount: None, gas_fee_strategy: gas_fee_strategy, simulate: false, + use_exact_sol_amount: None, }; client.buy(buy_params).await?; diff --git a/examples/bonk_copy_trading/src/main.rs b/examples/bonk_copy_trading/src/main.rs index e9ba2a4..a58837a 100644 --- a/examples/bonk_copy_trading/src/main.rs +++ b/examples/bonk_copy_trading/src/main.rs @@ -175,6 +175,7 @@ async fn bonk_copy_trade_with_grpc(trade_info: BonkTradeEvent) -> AnyResult<()> fixed_output_token_amount: None, gas_fee_strategy: gas_fee_strategy.clone(), simulate: false, + use_exact_sol_amount: None, }; client.buy(buy_params).await?; diff --git a/examples/bonk_sniper_trading/src/main.rs b/examples/bonk_sniper_trading/src/main.rs index 49f7964..6df794d 100644 --- a/examples/bonk_sniper_trading/src/main.rs +++ b/examples/bonk_sniper_trading/src/main.rs @@ -143,6 +143,7 @@ async fn bonk_sniper_trade_with_shreds(trade_info: BonkTradeEvent) -> AnyResult< fixed_output_token_amount: None, gas_fee_strategy: gas_fee_strategy.clone(), simulate: false, + use_exact_sol_amount: None, }; client.buy(buy_params).await?; diff --git a/examples/cli_trading/src/main.rs b/examples/cli_trading/src/main.rs index 31802ba..c60333d 100644 --- a/examples/cli_trading/src/main.rs +++ b/examples/cli_trading/src/main.rs @@ -634,6 +634,7 @@ async fn handle_buy_pumpfun( fixed_output_token_amount: None, gas_fee_strategy: gas_fee_strategy, simulate: false, + use_exact_sol_amount: None, }; match client.buy(buy_params).await { Ok((_, signature, _)) => { @@ -688,6 +689,7 @@ async fn handle_buy_pumpswap( fixed_output_token_amount: None, gas_fee_strategy: gas_fee_strategy, simulate: false, + use_exact_sol_amount: None, }; match client.buy(buy_params).await { Ok((_, signature, _)) => { @@ -741,6 +743,7 @@ async fn handle_buy_bonk( fixed_output_token_amount: None, gas_fee_strategy: gas_fee_strategy, simulate: false, + use_exact_sol_amount: None, }; match client.buy(buy_params).await { Ok((_, signature, _)) => { @@ -798,6 +801,7 @@ async fn handle_buy_raydium_v4( fixed_output_token_amount: None, gas_fee_strategy: gas_fee_strategy, simulate: false, + use_exact_sol_amount: None, }; match client.buy(buy_params).await { Ok((_, signature, _)) => { @@ -855,6 +859,7 @@ async fn handle_buy_raydium_cpmm( fixed_output_token_amount: None, gas_fee_strategy: gas_fee_strategy, simulate: false, + use_exact_sol_amount: None, }; match client.buy(buy_params).await { Ok((_, signature, _)) => { diff --git a/examples/meteora_damm_v2_direct_trading/src/main.rs b/examples/meteora_damm_v2_direct_trading/src/main.rs index 08ebd73..6297fdd 100644 --- a/examples/meteora_damm_v2_direct_trading/src/main.rs +++ b/examples/meteora_damm_v2_direct_trading/src/main.rs @@ -43,6 +43,7 @@ async fn main() -> Result<(), Box> { fixed_output_token_amount: Some(1), gas_fee_strategy: gas_fee_strategy.clone(), simulate: false, + use_exact_sol_amount: None, }; client.buy(buy_params).await?; diff --git a/examples/middleware_system/src/main.rs b/examples/middleware_system/src/main.rs index b7ae8a8..ee6c82e 100644 --- a/examples/middleware_system/src/main.rs +++ b/examples/middleware_system/src/main.rs @@ -102,6 +102,7 @@ async fn test_middleware() -> AnyResult<()> { fixed_output_token_amount: None, gas_fee_strategy: gas_fee_strategy, simulate: false, + use_exact_sol_amount: None, }; client.buy(buy_params).await?; println!("tip: This transaction will not succeed because we're using a test account. You can modify the code to initialize the payer with your own private key"); diff --git a/examples/nonce_cache/src/main.rs b/examples/nonce_cache/src/main.rs index 725aa32..852e122 100644 --- a/examples/nonce_cache/src/main.rs +++ b/examples/nonce_cache/src/main.rs @@ -160,6 +160,7 @@ async fn pumpfun_copy_trade_with_grpc(trade_info: PumpFunTradeEvent) -> AnyResul fixed_output_token_amount: None, gas_fee_strategy: gas_fee_strategy, simulate: false, + use_exact_sol_amount: None, }; client.buy(buy_params).await?; diff --git a/examples/pumpfun_copy_trading/src/main.rs b/examples/pumpfun_copy_trading/src/main.rs index fd4ad6d..0bbfab8 100644 --- a/examples/pumpfun_copy_trading/src/main.rs +++ b/examples/pumpfun_copy_trading/src/main.rs @@ -163,6 +163,7 @@ async fn pumpfun_copy_trade_with_grpc(trade_info: PumpFunTradeEvent) -> AnyResul fixed_output_token_amount: None, gas_fee_strategy: gas_fee_strategy.clone(), simulate: false, + use_exact_sol_amount: None, }; client.buy(buy_params).await?; diff --git a/examples/pumpfun_sniper_trading/src/main.rs b/examples/pumpfun_sniper_trading/src/main.rs index 6402853..36edaec 100644 --- a/examples/pumpfun_sniper_trading/src/main.rs +++ b/examples/pumpfun_sniper_trading/src/main.rs @@ -122,6 +122,7 @@ async fn pumpfun_sniper_trade_with_shreds(trade_info: PumpFunTradeEvent) -> AnyR fixed_output_token_amount: None, gas_fee_strategy: gas_fee_strategy.clone(), simulate: false, + use_exact_sol_amount: None, }; client.buy(buy_params).await?; diff --git a/examples/pumpswap_direct_trading/src/main.rs b/examples/pumpswap_direct_trading/src/main.rs index 11034ec..3ce7e18 100644 --- a/examples/pumpswap_direct_trading/src/main.rs +++ b/examples/pumpswap_direct_trading/src/main.rs @@ -43,6 +43,7 @@ async fn main() -> Result<(), Box> { fixed_output_token_amount: None, gas_fee_strategy: gas_fee_strategy.clone(), simulate: false, + use_exact_sol_amount: None, }; client.buy(buy_params).await?; diff --git a/examples/pumpswap_trading/src/main.rs b/examples/pumpswap_trading/src/main.rs index aef10c8..b259ac4 100644 --- a/examples/pumpswap_trading/src/main.rs +++ b/examples/pumpswap_trading/src/main.rs @@ -220,6 +220,7 @@ async fn pumpswap_trade_with_grpc(mint_pubkey: Pubkey, params: PumpSwapParams) - fixed_output_token_amount: None, gas_fee_strategy: gas_fee_strategy.clone(), simulate: false, + use_exact_sol_amount: None, }; client.buy(buy_params).await?; diff --git a/examples/raydium_amm_v4_trading/src/main.rs b/examples/raydium_amm_v4_trading/src/main.rs index f744704..77cbb74 100644 --- a/examples/raydium_amm_v4_trading/src/main.rs +++ b/examples/raydium_amm_v4_trading/src/main.rs @@ -173,6 +173,7 @@ async fn raydium_amm_v4_copy_trade_with_grpc(trade_info: RaydiumAmmV4SwapEvent) fixed_output_token_amount: None, gas_fee_strategy: gas_fee_strategy.clone(), simulate: false, + use_exact_sol_amount: None, }; client.buy(buy_params).await?; diff --git a/examples/raydium_cpmm_trading/src/main.rs b/examples/raydium_cpmm_trading/src/main.rs index 6d27296..a033079 100644 --- a/examples/raydium_cpmm_trading/src/main.rs +++ b/examples/raydium_cpmm_trading/src/main.rs @@ -158,6 +158,7 @@ async fn raydium_cpmm_copy_trade_with_grpc(trade_info: RaydiumCpmmSwapEvent) -> fixed_output_token_amount: None, gas_fee_strategy: gas_fee_strategy.clone(), simulate: false, + use_exact_sol_amount: None, }; client.buy(buy_params).await?; diff --git a/examples/seed_trading/src/main.rs b/examples/seed_trading/src/main.rs index b824b6d..69cb186 100644 --- a/examples/seed_trading/src/main.rs +++ b/examples/seed_trading/src/main.rs @@ -46,6 +46,7 @@ async fn main() -> Result<(), Box> { fixed_output_token_amount: None, gas_fee_strategy: gas_fee_strategy.clone(), simulate: false, + use_exact_sol_amount: None, }; client.buy(buy_params).await?; diff --git a/src/instruction/pumpfun.rs b/src/instruction/pumpfun.rs index 9994a8e..043951b 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_sol_amount.unwrap_or(true) { + // 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..959bdb5 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_sol_amount.unwrap_or(true) { + // 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..d3d65dd 100755 --- a/src/lib.rs +++ b/src/lib.rs @@ -187,6 +187,11 @@ pub struct TradeBuyParams { pub gas_fee_strategy: GasFeeStrategy, /// Whether to simulate the transaction instead of executing it pub simulate: bool, + /// Use exact SOL amount instructions (buy_exact_sol_in for PumpFun, buy_exact_quote_in for PumpSwap). + /// When Some(true) or None (default), the exact SOL/quote amount is spent and slippage is applied to output tokens. + /// When Some(false), uses regular buy instruction where slippage is applied to SOL/quote input. + /// This option only applies to PumpFun and PumpSwap DEXes; it is ignored for other DEXes. + pub use_exact_sol_amount: Option, } /// Parameters for executing sell orders across different DEX protocols @@ -508,6 +513,7 @@ impl TradingClient { fixed_output_amount: params.fixed_output_token_amount, gas_fee_strategy: params.gas_fee_strategy, simulate: params.simulate, + use_exact_sol_amount: params.use_exact_sol_amount, }; // Validate protocol params @@ -617,6 +623,7 @@ impl TradingClient { fixed_output_amount: params.fixed_output_token_amount, gas_fee_strategy: params.gas_fee_strategy, simulate: params.simulate, + use_exact_sol_amount: None, }; // Validate protocol params diff --git a/src/trading/core/params.rs b/src/trading/core/params.rs index 9e275db..404808a 100755 --- a/src/trading/core/params.rs +++ b/src/trading/core/params.rs @@ -67,6 +67,11 @@ pub struct SwapParams { pub fixed_output_amount: Option, pub gas_fee_strategy: GasFeeStrategy, pub simulate: bool, + /// Use exact SOL amount instructions (buy_exact_sol_in for PumpFun, buy_exact_quote_in for PumpSwap). + /// When Some(true) or None (default), the exact SOL/quote amount is spent and slippage is applied to output tokens. + /// When Some(false), uses regular buy instruction where slippage is applied to SOL/quote input. + /// This option only applies to PumpFun and PumpSwap DEXes; it is ignored for other DEXes. + pub use_exact_sol_amount: Option, } impl std::fmt::Debug for SwapParams {