Skip to main content

pump_rust_client/sdk/
pump_amm_ix.rs

1use anchor_lang::{InstructionData, ToAccountMetas};
2use solana_program::{
3    instruction::{AccountMeta, Instruction},
4    pubkey::Pubkey,
5    system_program,
6};
7
8use crate::pump_amm::{
9    client as amm_client, types::OptionBool as AmmOptionBool, ID as PUMP_AMM_PROGRAM_ID,
10};
11use crate::state::pump_amm::{GlobalConfig, Pool};
12use crate::token::create_associated_token_account_idempotent;
13use crate::{constants, pda};
14
15use super::PumpSdk;
16
17/// Pubkeys derived once and threaded into both `Buy` and `Sell` AMM
18/// instructions (which share the same account set apart from buy's extra
19/// volume-accumulator slots).
20struct AmmTradeAccounts {
21    coin_creator_vault_authority: Pubkey,
22    user_volume_accumulator: Pubkey,
23    user_base_token_account: Pubkey,
24    user_quote_token_account: Pubkey,
25    pool_base_token_account: Pubkey,
26    pool_quote_token_account: Pubkey,
27    protocol_fee_recipient_token_account: Pubkey,
28    coin_creator_vault_ata: Pubkey,
29}
30
31impl AmmTradeAccounts {
32    fn derive(
33        pool: Pubkey,
34        base_mint: Pubkey,
35        quote_mint: Pubkey,
36        base_token_program: Pubkey,
37        quote_token_program: Pubkey,
38        user: Pubkey,
39        coin_creator: Pubkey,
40        protocol_fee_recipient: Pubkey,
41    ) -> Self {
42        let coin_creator_vault_authority =
43            pda::pump_amm::coin_creator_vault_authority(&coin_creator).0;
44        let user_volume_accumulator = pda::pump_amm::user_volume_accumulator(&user).0;
45        let ata = |owner: &Pubkey, token_program: &Pubkey, mint: &Pubkey| {
46            pda::associated_token(owner, token_program, mint).0
47        };
48        Self {
49            coin_creator_vault_authority,
50            user_volume_accumulator,
51            user_base_token_account: ata(&user, &base_token_program, &base_mint),
52            user_quote_token_account: ata(&user, &quote_token_program, &quote_mint),
53            pool_base_token_account: ata(&pool, &base_token_program, &base_mint),
54            pool_quote_token_account: ata(&pool, &quote_token_program, &quote_mint),
55            protocol_fee_recipient_token_account: ata(
56                &protocol_fee_recipient,
57                &quote_token_program,
58                &quote_mint,
59            ),
60            coin_creator_vault_ata: ata(
61                &coin_creator_vault_authority,
62                &quote_token_program,
63                &quote_mint,
64            ),
65        }
66    }
67}
68
69impl PumpSdk {
70    /// `pump_amm` buy. Fees from [`GlobalConfig`], pool layout from [`Pool`]. Use default `coin_creator` to omit `pool_v2`.
71    pub fn buy_amm_instruction(
72        &self,
73        pool: Pubkey,
74        amm_global: &GlobalConfig,
75        pool_state: &Pool,
76        base_token_program: Pubkey,
77        quote_token_program: Pubkey,
78        user: Pubkey,
79        base_amount_out: u64,
80        max_quote_amount_in: u64,
81    ) -> Option<Instruction> {
82        let (protocol_fee_recipient, buyback_fee_recipient) =
83            Self::amm_fee_recipients_pair(amm_global)?;
84        Some(self.buy_amm_instruction_with_recipients(
85            pool,
86            pool_state.base_mint,
87            pool_state.quote_mint,
88            base_token_program,
89            quote_token_program,
90            user,
91            pool_state.coin_creator,
92            protocol_fee_recipient,
93            buyback_fee_recipient,
94            pool_state.is_cashback_coin,
95            base_amount_out,
96            max_quote_amount_in,
97        ))
98    }
99
100    pub(crate) fn buy_amm_instruction_with_recipients(
101        &self,
102        pool: Pubkey,
103        base_mint: Pubkey,
104        quote_mint: Pubkey,
105        base_token_program: Pubkey,
106        quote_token_program: Pubkey,
107        user: Pubkey,
108        coin_creator: Pubkey,
109        protocol_fee_recipient: Pubkey,
110        buyback_fee_recipient: Pubkey,
111        is_cashback_coin: bool,
112        base_amount_out: u64,
113        max_quote_amount_in: u64,
114    ) -> Instruction {
115        let a = AmmTradeAccounts::derive(
116            pool,
117            base_mint,
118            quote_mint,
119            base_token_program,
120            quote_token_program,
121            user,
122            coin_creator,
123            protocol_fee_recipient,
124        );
125        let accounts = amm_client::accounts::Buy {
126            pool,
127            user,
128            global_config: pda::pump_amm::global_config().0,
129            base_mint,
130            quote_mint,
131            user_base_token_account: a.user_base_token_account,
132            user_quote_token_account: a.user_quote_token_account,
133            pool_base_token_account: a.pool_base_token_account,
134            pool_quote_token_account: a.pool_quote_token_account,
135            protocol_fee_recipient,
136            protocol_fee_recipient_token_account: a.protocol_fee_recipient_token_account,
137            base_token_program,
138            quote_token_program,
139            system_program: system_program::ID,
140            associated_token_program: constants::SPL_ATA_PROGRAM_ID,
141            event_authority: pda::pump_amm::event_authority().0,
142            program: PUMP_AMM_PROGRAM_ID,
143            coin_creator_vault_ata: a.coin_creator_vault_ata,
144            coin_creator_vault_authority: a.coin_creator_vault_authority,
145            global_volume_accumulator: pda::pump_amm::global_volume_accumulator().0,
146            user_volume_accumulator: a.user_volume_accumulator,
147            fee_config: pda::pump_amm::fee_config().0,
148            fee_program: constants::FEE_PROGRAM_ID,
149        };
150        let args = amm_client::args::Buy {
151            base_amount_out,
152            max_quote_amount_in,
153            track_volume: AmmOptionBool(true),
154        };
155        let mut metas = accounts.to_account_metas(None);
156        if is_cashback_coin {
157            metas.push(AccountMeta::new(
158                pda::associated_token(
159                    &a.user_volume_accumulator,
160                    &quote_token_program,
161                    &constants::NATIVE_MINT,
162                )
163                .0,
164                false,
165            ));
166        }
167        if coin_creator != Pubkey::default() {
168            metas.push(AccountMeta::new_readonly(
169                pda::pump_amm::pool_v2(&base_mint).0,
170                false,
171            ));
172        }
173        metas.push(AccountMeta::new(buyback_fee_recipient, false));
174        metas.push(AccountMeta::new(
175            pda::associated_token(&buyback_fee_recipient, &quote_token_program, &quote_mint).0,
176            false,
177        ));
178        Instruction {
179            program_id: PUMP_AMM_PROGRAM_ID,
180            accounts: metas,
181            data: args.data(),
182        }
183    }
184
185    /// [`Self::buy_amm_instruction`] plus idempotent user base ATA create.
186    pub fn buy_amm_instructions(
187        &self,
188        pool: Pubkey,
189        amm_global: &GlobalConfig,
190        pool_state: &Pool,
191        base_token_program: Pubkey,
192        quote_token_program: Pubkey,
193        user: Pubkey,
194        base_amount_out: u64,
195        max_quote_amount_in: u64,
196    ) -> Option<Vec<Instruction>> {
197        let buy = self.buy_amm_instruction(
198            pool,
199            amm_global,
200            pool_state,
201            base_token_program,
202            quote_token_program,
203            user,
204            base_amount_out,
205            max_quote_amount_in,
206        )?;
207        Some(vec![
208            create_associated_token_account_idempotent(
209                &user,
210                &user,
211                &pool_state.base_mint,
212                &base_token_program,
213            ),
214            buy,
215        ])
216    }
217
218    /// `pump_amm` sell (remaining accounts differ from buy for cashback / buyback).
219    pub fn sell_amm_instruction(
220        &self,
221        pool: Pubkey,
222        amm_global: &GlobalConfig,
223        pool_state: &Pool,
224        base_token_program: Pubkey,
225        quote_token_program: Pubkey,
226        user: Pubkey,
227        base_amount_in: u64,
228        min_quote_amount_out: u64,
229    ) -> Option<Instruction> {
230        let (protocol_fee_recipient, buyback_fee_recipient) =
231            Self::amm_fee_recipients_pair(amm_global)?;
232        Some(self.sell_amm_instruction_with_recipients(
233            pool,
234            pool_state.base_mint,
235            pool_state.quote_mint,
236            base_token_program,
237            quote_token_program,
238            user,
239            pool_state.coin_creator,
240            protocol_fee_recipient,
241            buyback_fee_recipient,
242            pool_state.is_cashback_coin,
243            base_amount_in,
244            min_quote_amount_out,
245        ))
246    }
247
248    pub(crate) fn sell_amm_instruction_with_recipients(
249        &self,
250        pool: Pubkey,
251        base_mint: Pubkey,
252        quote_mint: Pubkey,
253        base_token_program: Pubkey,
254        quote_token_program: Pubkey,
255        user: Pubkey,
256        coin_creator: Pubkey,
257        protocol_fee_recipient: Pubkey,
258        buyback_fee_recipient: Pubkey,
259        is_cashback_coin: bool,
260        base_amount_in: u64,
261        min_quote_amount_out: u64,
262    ) -> Instruction {
263        let a = AmmTradeAccounts::derive(
264            pool,
265            base_mint,
266            quote_mint,
267            base_token_program,
268            quote_token_program,
269            user,
270            coin_creator,
271            protocol_fee_recipient,
272        );
273        let accounts = amm_client::accounts::Sell {
274            pool,
275            user,
276            global_config: pda::pump_amm::global_config().0,
277            base_mint,
278            quote_mint,
279            user_base_token_account: a.user_base_token_account,
280            user_quote_token_account: a.user_quote_token_account,
281            pool_base_token_account: a.pool_base_token_account,
282            pool_quote_token_account: a.pool_quote_token_account,
283            protocol_fee_recipient,
284            protocol_fee_recipient_token_account: a.protocol_fee_recipient_token_account,
285            base_token_program,
286            quote_token_program,
287            system_program: system_program::ID,
288            associated_token_program: constants::SPL_ATA_PROGRAM_ID,
289            event_authority: pda::pump_amm::event_authority().0,
290            program: PUMP_AMM_PROGRAM_ID,
291            coin_creator_vault_ata: a.coin_creator_vault_ata,
292            coin_creator_vault_authority: a.coin_creator_vault_authority,
293            fee_config: pda::pump_amm::fee_config().0,
294            fee_program: constants::FEE_PROGRAM_ID,
295        };
296        let args = amm_client::args::Sell {
297            base_amount_in,
298            min_quote_amount_out,
299        };
300        let mut metas = accounts.to_account_metas(None);
301        if is_cashback_coin {
302            metas.push(AccountMeta::new(
303                pda::associated_token(
304                    &a.user_volume_accumulator,
305                    &quote_token_program,
306                    &quote_mint,
307                )
308                .0,
309                false,
310            ));
311            metas.push(AccountMeta::new(a.user_volume_accumulator, false));
312        }
313        if coin_creator != Pubkey::default() {
314            metas.push(AccountMeta::new_readonly(
315                pda::pump_amm::pool_v2(&base_mint).0,
316                false,
317            ));
318        }
319        metas.push(AccountMeta::new_readonly(buyback_fee_recipient, false));
320        metas.push(AccountMeta::new(
321            pda::associated_token(&buyback_fee_recipient, &quote_token_program, &quote_mint).0,
322            false,
323        ));
324        Instruction {
325            program_id: PUMP_AMM_PROGRAM_ID,
326            accounts: metas,
327            data: args.data(),
328        }
329    }
330
331    /// [`Self::sell_amm_instruction`] plus idempotent user quote ATA create.
332    pub fn sell_amm_instructions(
333        &self,
334        pool: Pubkey,
335        amm_global: &GlobalConfig,
336        pool_state: &Pool,
337        base_token_program: Pubkey,
338        quote_token_program: Pubkey,
339        user: Pubkey,
340        base_amount_in: u64,
341        min_quote_amount_out: u64,
342    ) -> Option<Vec<Instruction>> {
343        let sell = self.sell_amm_instruction(
344            pool,
345            amm_global,
346            pool_state,
347            base_token_program,
348            quote_token_program,
349            user,
350            base_amount_in,
351            min_quote_amount_out,
352        )?;
353        Some(vec![
354            create_associated_token_account_idempotent(
355                &user,
356                &user,
357                &pool_state.quote_mint,
358                &quote_token_program,
359            ),
360            sell,
361        ])
362    }
363}