Merge pull request #68 from HelvetiCrypt/feat/add-exact-buy-instructions

Add buy_exact_sol_in and buy_exact_quote_in instruction support
This commit is contained in:
Wood
2026-01-06 22:35:40 +08:00
committed by GitHub
22 changed files with 78 additions and 10 deletions
+1
View File
@@ -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)
};
```
+1
View File
@@ -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)
};
```
+1
View File
@@ -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?;
+1
View File
@@ -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?;
+1
View File
@@ -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?;
+5
View File
@@ -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, _)) => {
@@ -43,6 +43,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
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?;
+1
View File
@@ -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");
+1
View File
@@ -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?;
@@ -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?;
@@ -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?;
@@ -43,6 +43,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
fixed_output_token_amount: None,
gas_fee_strategy: gas_fee_strategy.clone(),
simulate: false,
use_exact_sol_amount: None,
};
client.buy(buy_params).await?;
+1
View File
@@ -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?;
@@ -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?;
@@ -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?;
+1
View File
@@ -46,6 +46,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
fixed_output_token_amount: None,
gas_fee_strategy: gas_fee_strategy.clone(),
simulate: false,
use_exact_sol_amount: None,
};
client.buy(buy_params).await?;
+18 -4
View File
@@ -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(&params.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 {
+22 -6
View File
@@ -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(&params.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
+5
View File
@@ -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 {
+1
View File
@@ -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
+7
View File
@@ -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<bool>,
}
/// 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
+5
View File
@@ -67,6 +67,11 @@ pub struct SwapParams {
pub fixed_output_amount: Option<u64>,
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<bool>,
}
impl std::fmt::Debug for SwapParams {