solana_exec/pumpfun/
push_pumpfun_swap_instruction.rs

1use crate::constants::COMPUTE_UNITS;
2use crate::pumpfun::get_pf_a_amount_out_no_price_impact::get_pf_a_amount_out_no_price_impact;
3use crate::pumpfun::get_pf_max_b_amount_in_no_price_impact::get_pf_max_b_amount_in_no_price_impact;
4use crate::types::swap_config::SwapConfig;
5use crate::utilities::get_min_amount_out::get_min_amount_out;
6use solana_central::CentralContext;
7use solana_central::PfBondingCurve;
8use solana_central::PoolTrait;
9use solana_central::SwapDirection;
10use solana_central::constants::PUMP_SWAP_FEE_VAULTS;
11use solana_central::constants::{PUMP_CONSTANTS, SOLANA_PROGRAMS};
12use solana_sdk::instruction::{AccountMeta, Instruction};
13use solana_sdk::pubkey::Pubkey;
14use spl_associated_token_account::get_associated_token_address;
15use std::sync::Arc;
16
17/// Create and add a Pumpfun bonding curve swap instruction. For Pumpfun, token A is the bonding
18/// curve token and token B is SOL. BtoA is a buy, AtoB is a sell (consistent with Pumpswap
19/// protocol when tokens migrate).
20pub fn push_pumpfun_swap_instruction(
21  swap_config: &SwapConfig,
22  central_context: &Arc<CentralContext>,
23  instructions: &mut Vec<Instruction>,
24  compute_unit_budget: &mut u32,
25  pool: &PfBondingCurve,
26) {
27  if swap_config.slippage_lp == u64::MAX && swap_config.direction == SwapDirection::BToA {
28    println!(
29      "pumpfun_innerinstructions: Infinite slippage is not allowed for pumpfun buy \
30    direction swaps (B to A, quote to base) because it authorizes the protocol to take all of the \
31    base tokens available in the wallet. Base token address: {}",
32      pool.token_a_address()
33    );
34  }
35  /*
36  This instruction data will contain the 8 byte discriminator, and then the following arguments:
37  amount, max_sol_cost for buys
38  amount, min_sol_output for sells.
39  8 bytes + 2 u64s = 24 bytes total.
40  */
41  let mut data: Vec<u8> = Vec::with_capacity(24);
42  /*
43  Pumpfun reminders:
44  We store the bonding curve token as token A and SOL as token B.
45  A token swap from A to B is considered a sell by Pumpfun (selling token for SOL)
46  A token swap from B to A is considered a buy by Pumpfun (buying token with SOL)
47  The Pumpfun protocol uses a fixed fee of 1% for all swaps charged in SOL
48  Slippage calculations are done without price impacts
49  */
50  let index_zero_arg: u64;
51  let index_one_arg: u64;
52  if swap_config.direction == SwapDirection::AToB {
53    let a_amount_in = swap_config.amount_in;
54    let min_b_amount_out = get_min_amount_out(swap_config, pool);
55    index_zero_arg = a_amount_in;
56    index_one_arg = min_b_amount_out;
57    data.extend_from_slice(&PUMP_CONSTANTS.sell_instruction_discriminator);
58    *compute_unit_budget += COMPUTE_UNITS.pf_bonding_curve_sell;
59  }
60  // swap from B to A is sol buy of the token
61  // TODO remove fees into account from these calculaitons and support buy exact in
62  else {
63    let b_amount_in = swap_config.amount_in;
64    let a_amount_out = get_pf_a_amount_out_no_price_impact(
65      b_amount_in,
66      pool.total_swap_fee_lp(&central_context),
67      pool.virtual_token_reserves,
68      pool.virtual_sol_reserves,
69    );
70    let max_b_amount_in = get_pf_max_b_amount_in_no_price_impact(
71      a_amount_out,
72      pool.virtual_token_reserves,
73      pool.virtual_sol_reserves,
74      swap_config.slippage_lp,
75    );
76    index_zero_arg = a_amount_out;
77    index_one_arg = max_b_amount_in;
78    data.extend_from_slice(&PUMP_CONSTANTS.buy_instruction_discriminator);
79    *compute_unit_budget += COMPUTE_UNITS.pf_bonding_curve_buy;
80  }
81  data.extend_from_slice(&index_zero_arg.to_le_bytes());
82  data.extend_from_slice(&index_one_arg.to_le_bytes());
83
84  let mut accounts: Vec<AccountMeta> = vec![
85    AccountMeta::new_readonly(PUMP_CONSTANTS.bonding_curve_global_config, false),
86    AccountMeta::new(PUMP_SWAP_FEE_VAULTS[0], false),
87    AccountMeta::new_readonly(pool.token_address, false),
88    AccountMeta::new(pool.bonding_curve_address, false),
89    AccountMeta::new(pool.bonding_curve_associated_token_account_address, false),
90    AccountMeta::new(
91      get_associated_token_address(&swap_config.wallet, &pool.token_address),
92      false,
93    ),
94    AccountMeta::new(swap_config.wallet, true),
95    AccountMeta::new_readonly(SOLANA_PROGRAMS.system_program, false),
96  ];
97  // Sell ordering
98  if swap_config.direction == SwapDirection::AToB {
99    accounts.push(AccountMeta::new(pool.creator_vault_address, false));
100    accounts.push(AccountMeta::new_readonly(
101      SOLANA_PROGRAMS.token_program,
102      false,
103    ));
104  }
105  // Buy ordering
106  else {
107    accounts.push(AccountMeta::new_readonly(
108      SOLANA_PROGRAMS.token_program,
109      false,
110    ));
111    accounts.push(AccountMeta::new(pool.creator_vault_address, false));
112  }
113  // Rest of accounts are the same across both buys and sells
114  accounts.push(AccountMeta::new_readonly(
115    PUMP_CONSTANTS.bonding_curve_event_authority,
116    false,
117  ));
118  accounts.push(AccountMeta::new_readonly(
119    PUMP_CONSTANTS.bonding_curve_program,
120    false,
121  ));
122
123  // For buys push the volume accumulators for the fee update. Not used in sells
124  if swap_config.direction == SwapDirection::BToA {
125    accounts.push(AccountMeta::new(
126      PUMP_CONSTANTS.bonding_curve_global_volume_accumulator,
127      false,
128    ));
129
130    // Derive PDA for user volume accumulator
131    let user_volume_accumulator = Pubkey::find_program_address(
132      &[b"user_volume_accumulator", swap_config.wallet.as_ref()],
133      &PUMP_CONSTANTS.bonding_curve_program,
134    )
135    .0;
136    accounts.push(AccountMeta::new(user_volume_accumulator, false));
137  }
138
139  accounts.push(AccountMeta::new_readonly(
140    PUMP_CONSTANTS.bonding_curve_fee_config,
141    false,
142  ));
143  accounts.push(AccountMeta::new_readonly(PUMP_CONSTANTS.fee_program, false));
144
145  instructions.push(Instruction {
146    program_id: PUMP_CONSTANTS.bonding_curve_program,
147    accounts,
148    data,
149  });
150}