Add buy_exact_sol_in and buy_exact_quote_in instruction support

- Add BUY_EXACT_SOL_IN_DISCRIMINATOR for PumpFun
- Add BUY_EXACT_QUOTE_IN_DISCRIMINATOR for PumpSwap
- Add use_exact_in_instruction field to SwapParams
- When enabled, uses exact input instructions that spend exactly the
  specified SOL/quote amount with slippage applied to output tokens
- Defaults to false for backwards compatibility

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
vibes
2026-01-06 10:32:56 +00:00
co-authored by Claude Opus 4.5
parent e12c45cc4c
commit 16996b24d4
6 changed files with 52 additions and 10 deletions
+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_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(&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_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(&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
+2
View File
@@ -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
+4
View File
@@ -67,6 +67,10 @@ pub struct SwapParams {
pub fixed_output_amount: Option<u64>,
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 {