solana_exec/pumpswap/
push_pumpswap_swap_instruction.rs

1use crate::constants::COMPUTE_UNITS;
2use crate::types::swap_config::SwapConfig;
3use crate::utilities::get_min_amount_out::get_min_amount_out;
4use solana_central::PoolTrait;
5use solana_central::PumpswapPool;
6use solana_central::SwapDirection;
7use solana_central::constants::{PUMP_CONSTANTS, SOLANA_PROGRAMS};
8use solana_sdk::instruction::{AccountMeta, Instruction};
9use solana_sdk::pubkey::Pubkey;
10use spl_associated_token_account::get_associated_token_address;
11
12/// Create and add a Pumpswap AMM swap instruction. If `use_manual_instruction_args` is true, uses
13/// exact amounts instead of slippage-based calculations.
14pub fn push_pumpswap_swap_instruction(
15  swap_config: &SwapConfig,
16  // If true, use exact amounts (manual override) instead of slippage calculations
17  use_manual_instruction_args: bool,
18  mut index_zero_arg: u64,
19  mut index_one_arg: u64,
20  instructions: &mut Vec<Instruction>,
21  compute_unit_budget: &mut u32,
22  pool: &PumpswapPool,
23) {
24  if swap_config.slippage_lp == u64::MAX && swap_config.direction == SwapDirection::BToA {
25    println!(
26      "pumpswap_innerinstructions: Infinite slippage is not allowed for pumpfun buy \
27    direction swaps (B to A, quote to base) because it authorizes the protocol to take all of the \
28    base tokens available in the wallet. Base token address: {}",
29      pool.token_a_address()
30    );
31  }
32  /*
33  This instruction data will contain the 8 byte discriminator, and then the following arguments:
34  base_amount_in, min_quote_amount_out for sells
35  base_amount_out, max_quote_amount_in for buys.
36  8 bytes + 2 u64s = 24 bytes total.
37  */
38  let mut data: Vec<u8> = Vec::with_capacity(25);
39
40  /*
41  Pumpswap reminders:
42  We store the base token as token A and the quote token as token B.
43  A token swap from A to B is considered a sell by the Pumpswap protocol
44  A token swap from B to A is considered a buy by the Pumpswap protocol
45  The Pumpswap protocol uses a fixed fee of 0.25% (250 basis points) for all swaps charged in the quote token B.
46
47  Slippage calculations are done without price impacts
48  */
49  if !use_manual_instruction_args {
50    if swap_config.direction == SwapDirection::AToB {
51      let a_amount_in = swap_config.amount_in;
52      let min_b_amount_out = get_min_amount_out(swap_config, pool);
53      index_zero_arg = a_amount_in;
54      index_one_arg = min_b_amount_out;
55      data.extend_from_slice(&PUMP_CONSTANTS.sell_instruction_discriminator);
56    } else {
57      index_zero_arg = swap_config.amount_in;
58      index_one_arg = get_min_amount_out(swap_config, pool);
59      data.extend_from_slice(&PUMP_CONSTANTS.pumpswap_buy_exact_quote_in_instruction_discriminator);
60    }
61  }
62  data.extend_from_slice(&index_zero_arg.to_le_bytes());
63  data.extend_from_slice(&index_one_arg.to_le_bytes());
64  // The buy quote exact in instruction takes one more byte of 0
65  if swap_config.direction == SwapDirection::BToA {
66    data.push(1);
67  }
68
69  /*
70  Pumpswap fees are always collected in the quote token on both buys and sells. If the quote token
71  is not wrapped sol, we should open the token account for the fee vault for the quote token
72  otherwise the tx will fail. For WSOL the token accounts will always be open. We always store the
73  quote token as token b. However it looks like the protocol takes care of this for you in the
74  inner instructions. It also looks like my attempt at doing this was wrong account.
75  */
76  // if pool.info.token_b_address != TOKENS.wsol {
77  //   instructions.push(create_associated_token_account_idempotent(
78  //     &swap_config.wallet,
79  //     &pool.fee_vault_token_account,
80  //     &pool.info.token_b_address,
81  //     &SOLANA_PROGRAMS.token_program,
82  //   ));
83  //   *compute_unit_budget += COMPUTE_UNITS.open_token_account;
84  // }
85
86  let mut accounts: Vec<AccountMeta> = vec![
87    AccountMeta::new(pool.info.pool_address, false),
88    AccountMeta::new(swap_config.wallet, true),
89    AccountMeta::new_readonly(PUMP_CONSTANTS.pump_swap_global_config, false),
90    AccountMeta::new_readonly(pool.info.token_a_address, false),
91    AccountMeta::new_readonly(pool.info.token_b_address, false),
92    AccountMeta::new(
93      get_associated_token_address(&swap_config.wallet, &pool.info.token_a_address),
94      false,
95    ),
96    AccountMeta::new(
97      get_associated_token_address(&swap_config.wallet, &pool.info.token_b_address),
98      false,
99    ),
100    AccountMeta::new(pool.info.token_a_vault_address, false),
101    AccountMeta::new(pool.info.token_b_vault_address, false),
102    AccountMeta::new_readonly(pool.fee_vault, false),
103    AccountMeta::new(pool.fee_vault_token_account, false),
104    AccountMeta::new_readonly(SOLANA_PROGRAMS.token_program, false),
105    AccountMeta::new_readonly(SOLANA_PROGRAMS.token_program, false),
106    AccountMeta::new_readonly(SOLANA_PROGRAMS.system_program, false),
107    AccountMeta::new_readonly(SOLANA_PROGRAMS.associated_token_program, false),
108    AccountMeta::new_readonly(PUMP_CONSTANTS.pump_swap_event_authority, false),
109    AccountMeta::new_readonly(PUMP_CONSTANTS.pump_swap_program, false),
110    AccountMeta::new(pool.coin_creator_vault_authority_token_account, false),
111    AccountMeta::new_readonly(pool.coin_creator_vault_authority, false),
112  ];
113
114  // Buys only use accumulators
115  if swap_config.direction == SwapDirection::BToA {
116    accounts.push(AccountMeta::new(
117      PUMP_CONSTANTS.pumpswap_global_volume_accumulator,
118      false,
119    ));
120    // Derive PDA for user volume accumulator
121    let user_volume_accumulator = Pubkey::find_program_address(
122      &[b"user_volume_accumulator", swap_config.wallet.as_ref()],
123      &PUMP_CONSTANTS.pump_swap_program,
124    )
125    .0;
126    accounts.push(AccountMeta::new(user_volume_accumulator, false));
127  }
128
129  // Fee configs everywhere
130  accounts.push(AccountMeta::new_readonly(
131    PUMP_CONSTANTS.pumpswap_fee_config,
132    false,
133  ));
134  accounts.push(AccountMeta::new_readonly(PUMP_CONSTANTS.fee_program, false));
135
136  instructions.push(Instruction {
137    program_id: PUMP_CONSTANTS.pump_swap_program,
138    accounts,
139    data,
140  });
141  *compute_unit_budget += COMPUTE_UNITS.pumpswap_swap;
142}