Skip to main content

pump_rust_client/sdk/
pump_v2.rs

1use anchor_lang::{InstructionData, ToAccountMetas};
2use solana_program::{
3    instruction::{AccountMeta, Instruction},
4    pubkey::Pubkey,
5    system_program,
6};
7
8use crate::state::{BondingCurve, Global};
9use crate::token::create_associated_token_account_idempotent;
10use crate::{
11    constants, pda, pump::client, pump::types::OptionBool,
12    pump_agent_payments::client as agent_client,
13};
14
15use super::{CreateCoinParams, PumpSdk};
16
17/// Pubkeys derived once and threaded into both `BuyV2` and `SellV2` (the two
18/// instructions share the same account set apart from `BuyV2`'s extra
19/// `global_volume_accumulator`). `quote_mint` is the resolved value
20/// (default → wSOL); `base_token_program` is fixed for v2 (Token-2022).
21struct V2TradeAccounts {
22    quote_mint: Pubkey,
23    base_token_program: Pubkey,
24    bonding_curve: Pubkey,
25    creator_vault: Pubkey,
26    user_volume_accumulator: Pubkey,
27    associated_quote_fee_recipient: Pubkey,
28    associated_quote_buyback_fee_recipient: Pubkey,
29    associated_base_bonding_curve: Pubkey,
30    associated_quote_bonding_curve: Pubkey,
31    associated_base_user: Pubkey,
32    associated_quote_user: Pubkey,
33    associated_creator_vault: Pubkey,
34    associated_user_volume_accumulator: Pubkey,
35}
36
37impl V2TradeAccounts {
38    fn derive(
39        base_mint: Pubkey,
40        quote_mint: Pubkey,
41        quote_token_program: Pubkey,
42        user: Pubkey,
43        creator: Pubkey,
44        fee_recipient: Pubkey,
45        buyback_fee_recipient: Pubkey,
46    ) -> Self {
47        let base_token_program = constants::SPL_TOKEN_2022_PROGRAM_ID;
48        let quote_mint = if quote_mint == Pubkey::default() {
49            constants::NATIVE_MINT
50        } else {
51            quote_mint
52        };
53        let bonding_curve = pda::pump::bonding_curve(&base_mint).0;
54        let creator_vault = pda::pump::creator_vault(&creator).0;
55        let user_volume_accumulator = pda::pump::user_volume_accumulator(&user).0;
56        let ata = |owner: &Pubkey, token_program: &Pubkey, mint: &Pubkey| {
57            pda::associated_token(owner, token_program, mint).0
58        };
59        Self {
60            quote_mint,
61            base_token_program,
62            bonding_curve,
63            creator_vault,
64            user_volume_accumulator,
65            associated_quote_fee_recipient: ata(&fee_recipient, &quote_token_program, &quote_mint),
66            associated_quote_buyback_fee_recipient: ata(
67                &buyback_fee_recipient,
68                &quote_token_program,
69                &quote_mint,
70            ),
71            associated_base_bonding_curve: ata(&bonding_curve, &base_token_program, &base_mint),
72            associated_quote_bonding_curve: ata(&bonding_curve, &quote_token_program, &quote_mint),
73            associated_base_user: ata(&user, &base_token_program, &base_mint),
74            associated_quote_user: ata(&user, &quote_token_program, &quote_mint),
75            associated_creator_vault: ata(&creator_vault, &quote_token_program, &quote_mint),
76            associated_user_volume_accumulator: ata(
77                &user_volume_accumulator,
78                &quote_token_program,
79                &quote_mint,
80            ),
81        }
82    }
83}
84
85impl PumpSdk {
86    /// `create_v2` (Token-2022, Mayhem PDAs).
87    ///
88    /// `quote_mint` selects the curve's quote asset. Pass `Pubkey::default()`
89    /// or [`constants::NATIVE_MINT`] for the standard wSOL curve. Any other
90    /// mint (e.g. USDC) is treated as a non-SOL quote and three remaining
91    /// accounts are appended (quote mint, the legacy-SPL ATA owned by the
92    /// bonding curve, and the legacy SPL Token program).
93    pub fn create_v2_instruction(
94        &self,
95        mint: Pubkey,
96        user: Pubkey,
97        name: impl Into<String>,
98        symbol: impl Into<String>,
99        uri: impl Into<String>,
100        creator: Pubkey,
101        quote_mint: Pubkey,
102        mayhem_mode: bool,
103        cashback: bool,
104    ) -> Instruction {
105        let token_program = constants::SPL_TOKEN_2022_PROGRAM_ID;
106        let bonding_curve = pda::pump::bonding_curve(&mint).0;
107        let accounts = client::accounts::CreateV2 {
108            mint,
109            mint_authority: pda::pump::mint_authority().0,
110            bonding_curve,
111            associated_bonding_curve: pda::associated_token(&bonding_curve, &token_program, &mint)
112                .0,
113            global: pda::pump::global().0,
114            user,
115            system_program: system_program::ID,
116            token_program,
117            associated_token_program: constants::SPL_ATA_PROGRAM_ID,
118            mayhem_program_id: constants::MAYHEM_PROGRAM_ID,
119            global_params: pda::mayhem::global_params().0,
120            sol_vault: pda::mayhem::sol_vault().0,
121            mayhem_state: pda::mayhem::mayhem_state(&mint).0,
122            mayhem_token_vault: pda::mayhem::mayhem_token_vault(&mint).0,
123            event_authority: pda::pump::event_authority().0,
124            program: crate::pump::ID,
125        };
126        let args = client::args::CreateV2 {
127            name: name.into(),
128            symbol: symbol.into(),
129            uri: uri.into(),
130            creator,
131            is_mayhem_mode: mayhem_mode,
132            is_cashback_enabled: OptionBool(cashback),
133        };
134        let mut metas = accounts.to_account_metas(None);
135        let resolved_quote_mint = if quote_mint == Pubkey::default() {
136            constants::NATIVE_MINT
137        } else {
138            quote_mint
139        };
140        if resolved_quote_mint != constants::NATIVE_MINT {
141            let quote_token_program = constants::SPL_TOKEN_PROGRAM_ID;
142            let associated_quote_bonding_curve =
143                pda::associated_token(&bonding_curve, &quote_token_program, &resolved_quote_mint).0;
144            metas.push(AccountMeta::new_readonly(resolved_quote_mint, false));
145            metas.push(AccountMeta::new(associated_quote_bonding_curve, false));
146            metas.push(AccountMeta::new_readonly(quote_token_program, false));
147        }
148        Instruction {
149            program_id: crate::pump::ID,
150            accounts: metas,
151            data: args.data(),
152        }
153    }
154
155    /// `buy_v2` with fee recipients from [`Global`] and quote layout from [`BondingCurve`].
156    pub fn buy_v2_instruction(
157        &self,
158        global: &Global,
159        bonding_curve: &BondingCurve,
160        base_mint: Pubkey,
161        quote_token_program: Pubkey,
162        user: Pubkey,
163        amount: u64,
164        max_sol_cost: u64,
165    ) -> Option<Instruction> {
166        let (fee_recipient, buyback_fee_recipient) =
167            Self::pump_fee_recipients_pair(global, bonding_curve.is_mayhem_mode)?;
168        Some(self.buy_v2_instruction_with_recipients(
169            base_mint,
170            bonding_curve.quote_mint,
171            quote_token_program,
172            user,
173            bonding_curve.creator,
174            fee_recipient,
175            buyback_fee_recipient,
176            amount,
177            max_sol_cost,
178        ))
179    }
180
181    pub(crate) fn buy_v2_instruction_with_recipients(
182        &self,
183        base_mint: Pubkey,
184        quote_mint: Pubkey,
185        quote_token_program: Pubkey,
186        user: Pubkey,
187        creator: Pubkey,
188        fee_recipient: Pubkey,
189        buyback_fee_recipient: Pubkey,
190        amount: u64,
191        max_sol_cost: u64,
192    ) -> Instruction {
193        let a = V2TradeAccounts::derive(
194            base_mint,
195            quote_mint,
196            quote_token_program,
197            user,
198            creator,
199            fee_recipient,
200            buyback_fee_recipient,
201        );
202        let accounts = client::accounts::BuyV2 {
203            global: pda::pump::global().0,
204            base_mint,
205            quote_mint: a.quote_mint,
206            base_token_program: a.base_token_program,
207            quote_token_program,
208            associated_token_program: constants::SPL_ATA_PROGRAM_ID,
209            fee_recipient,
210            associated_quote_fee_recipient: a.associated_quote_fee_recipient,
211            buyback_fee_recipient,
212            associated_quote_buyback_fee_recipient: a.associated_quote_buyback_fee_recipient,
213            bonding_curve: a.bonding_curve,
214            associated_base_bonding_curve: a.associated_base_bonding_curve,
215            associated_quote_bonding_curve: a.associated_quote_bonding_curve,
216            user,
217            associated_base_user: a.associated_base_user,
218            associated_quote_user: a.associated_quote_user,
219            creator_vault: a.creator_vault,
220            associated_creator_vault: a.associated_creator_vault,
221            sharing_config: pda::pump::sharing_config(&base_mint).0,
222            global_volume_accumulator: pda::pump::global_volume_accumulator().0,
223            user_volume_accumulator: a.user_volume_accumulator,
224            associated_user_volume_accumulator: a.associated_user_volume_accumulator,
225            fee_config: pda::pump::fee_config().0,
226            fee_program: constants::FEE_PROGRAM_ID,
227            system_program: system_program::ID,
228            event_authority: pda::pump::event_authority().0,
229            program: crate::pump::ID,
230        };
231        let args = client::args::BuyV2 {
232            amount,
233            max_sol_cost,
234        };
235        Instruction {
236            program_id: crate::pump::ID,
237            accounts: accounts.to_account_metas(None),
238            data: args.data(),
239        }
240    }
241
242    /// `buy_v2` with idempotent ATA creates for base/quote user accounts (skipped when `quote_mint` is the default).
243    pub fn buy_v2_instructions(
244        &self,
245        global: &Global,
246        bonding_curve: &BondingCurve,
247        base_mint: Pubkey,
248        quote_token_program: Pubkey,
249        user: Pubkey,
250        amount: u64,
251        max_sol_cost: u64,
252    ) -> Option<Vec<Instruction>> {
253        let base_token_program = constants::SPL_TOKEN_2022_PROGRAM_ID;
254        let (fee_recipient, buyback_fee_recipient) =
255            Self::pump_fee_recipients_pair(global, bonding_curve.is_mayhem_mode)?;
256        let quote_mint = bonding_curve.quote_mint;
257        let creator = bonding_curve.creator;
258        let mut instructions = Self::user_trade_atas(
259            user,
260            base_mint,
261            bonding_curve,
262            base_token_program,
263            quote_token_program,
264            true,
265        );
266        instructions.push(self.buy_v2_instruction_with_recipients(
267            base_mint,
268            quote_mint,
269            quote_token_program,
270            user,
271            creator,
272            fee_recipient,
273            buyback_fee_recipient,
274            amount,
275            max_sol_cost,
276        ));
277        Some(instructions)
278    }
279
280    /// `buy_exact_quote_in_v2`: spend exactly `spendable_quote_in`, requiring at least `min_tokens_out` base tokens out.
281    pub fn buy_exact_quote_in_v2_instruction(
282        &self,
283        global: &Global,
284        bonding_curve: &BondingCurve,
285        base_mint: Pubkey,
286        quote_token_program: Pubkey,
287        user: Pubkey,
288        spendable_quote_in: u64,
289        min_tokens_out: u64,
290    ) -> Option<Instruction> {
291        let (fee_recipient, buyback_fee_recipient) =
292            Self::pump_fee_recipients_pair(global, bonding_curve.is_mayhem_mode)?;
293        Some(self.buy_exact_quote_in_v2_instruction_with_recipients(
294            base_mint,
295            bonding_curve.quote_mint,
296            quote_token_program,
297            user,
298            bonding_curve.creator,
299            fee_recipient,
300            buyback_fee_recipient,
301            spendable_quote_in,
302            min_tokens_out,
303        ))
304    }
305
306    pub(crate) fn buy_exact_quote_in_v2_instruction_with_recipients(
307        &self,
308        base_mint: Pubkey,
309        quote_mint: Pubkey,
310        quote_token_program: Pubkey,
311        user: Pubkey,
312        creator: Pubkey,
313        fee_recipient: Pubkey,
314        buyback_fee_recipient: Pubkey,
315        spendable_quote_in: u64,
316        min_tokens_out: u64,
317    ) -> Instruction {
318        let a = V2TradeAccounts::derive(
319            base_mint,
320            quote_mint,
321            quote_token_program,
322            user,
323            creator,
324            fee_recipient,
325            buyback_fee_recipient,
326        );
327        let accounts = client::accounts::BuyExactQuoteInV2 {
328            global: pda::pump::global().0,
329            base_mint,
330            quote_mint: a.quote_mint,
331            base_token_program: a.base_token_program,
332            quote_token_program,
333            associated_token_program: constants::SPL_ATA_PROGRAM_ID,
334            fee_recipient,
335            associated_quote_fee_recipient: a.associated_quote_fee_recipient,
336            buyback_fee_recipient,
337            associated_quote_buyback_fee_recipient: a.associated_quote_buyback_fee_recipient,
338            bonding_curve: a.bonding_curve,
339            associated_base_bonding_curve: a.associated_base_bonding_curve,
340            associated_quote_bonding_curve: a.associated_quote_bonding_curve,
341            user,
342            associated_base_user: a.associated_base_user,
343            associated_quote_user: a.associated_quote_user,
344            creator_vault: a.creator_vault,
345            associated_creator_vault: a.associated_creator_vault,
346            sharing_config: pda::pump::sharing_config(&base_mint).0,
347            global_volume_accumulator: pda::pump::global_volume_accumulator().0,
348            user_volume_accumulator: a.user_volume_accumulator,
349            associated_user_volume_accumulator: a.associated_user_volume_accumulator,
350            fee_config: pda::pump::fee_config().0,
351            fee_program: constants::FEE_PROGRAM_ID,
352            system_program: system_program::ID,
353            event_authority: pda::pump::event_authority().0,
354            program: crate::pump::ID,
355        };
356        let args = client::args::BuyExactQuoteInV2 {
357            spendable_quote_in,
358            min_tokens_out,
359        };
360        Instruction {
361            program_id: crate::pump::ID,
362            accounts: accounts.to_account_metas(None),
363            data: args.data(),
364        }
365    }
366
367    /// `buy_exact_quote_in_v2` with idempotent ATA creates for base/quote user accounts (skipped when `quote_mint` is the default).
368    pub fn buy_exact_quote_in_v2_instructions(
369        &self,
370        global: &Global,
371        bonding_curve: &BondingCurve,
372        base_mint: Pubkey,
373        quote_token_program: Pubkey,
374        user: Pubkey,
375        spendable_quote_in: u64,
376        min_tokens_out: u64,
377    ) -> Option<Vec<Instruction>> {
378        let base_token_program = constants::SPL_TOKEN_2022_PROGRAM_ID;
379        let (fee_recipient, buyback_fee_recipient) =
380            Self::pump_fee_recipients_pair(global, bonding_curve.is_mayhem_mode)?;
381        let quote_mint = bonding_curve.quote_mint;
382        let creator = bonding_curve.creator;
383        let mut instructions = Self::user_trade_atas(
384            user,
385            base_mint,
386            bonding_curve,
387            base_token_program,
388            quote_token_program,
389            true,
390        );
391        instructions.push(self.buy_exact_quote_in_v2_instruction_with_recipients(
392            base_mint,
393            quote_mint,
394            quote_token_program,
395            user,
396            creator,
397            fee_recipient,
398            buyback_fee_recipient,
399            spendable_quote_in,
400            min_tokens_out,
401        ));
402        Some(instructions)
403    }
404
405    /// `sell_v2`; fee recipients from [`Global`], quote accounts from [`BondingCurve`].
406    pub fn sell_v2_instruction(
407        &self,
408        global: &Global,
409        bonding_curve: &BondingCurve,
410        base_mint: Pubkey,
411        quote_token_program: Pubkey,
412        user: Pubkey,
413        amount: u64,
414        min_sol_output: u64,
415    ) -> Option<Instruction> {
416        let (fee_recipient, buyback_fee_recipient) =
417            Self::pump_fee_recipients_pair(global, bonding_curve.is_mayhem_mode)?;
418        Some(self.sell_v2_instruction_with_recipients(
419            base_mint,
420            bonding_curve.quote_mint,
421            quote_token_program,
422            user,
423            bonding_curve.creator,
424            fee_recipient,
425            buyback_fee_recipient,
426            amount,
427            min_sol_output,
428        ))
429    }
430
431    pub(crate) fn sell_v2_instruction_with_recipients(
432        &self,
433        base_mint: Pubkey,
434        quote_mint: Pubkey,
435        quote_token_program: Pubkey,
436        user: Pubkey,
437        creator: Pubkey,
438        fee_recipient: Pubkey,
439        buyback_fee_recipient: Pubkey,
440        amount: u64,
441        min_sol_output: u64,
442    ) -> Instruction {
443        let a = V2TradeAccounts::derive(
444            base_mint,
445            quote_mint,
446            quote_token_program,
447            user,
448            creator,
449            fee_recipient,
450            buyback_fee_recipient,
451        );
452        let accounts = client::accounts::SellV2 {
453            global: pda::pump::global().0,
454            base_mint,
455            quote_mint: a.quote_mint,
456            base_token_program: a.base_token_program,
457            quote_token_program,
458            associated_token_program: constants::SPL_ATA_PROGRAM_ID,
459            fee_recipient,
460            associated_quote_fee_recipient: a.associated_quote_fee_recipient,
461            buyback_fee_recipient,
462            associated_quote_buyback_fee_recipient: a.associated_quote_buyback_fee_recipient,
463            bonding_curve: a.bonding_curve,
464            associated_base_bonding_curve: a.associated_base_bonding_curve,
465            associated_quote_bonding_curve: a.associated_quote_bonding_curve,
466            user,
467            associated_base_user: a.associated_base_user,
468            associated_quote_user: a.associated_quote_user,
469            creator_vault: a.creator_vault,
470            associated_creator_vault: a.associated_creator_vault,
471            sharing_config: pda::pump::sharing_config(&base_mint).0,
472            user_volume_accumulator: a.user_volume_accumulator,
473            associated_user_volume_accumulator: a.associated_user_volume_accumulator,
474            fee_config: pda::pump::fee_config().0,
475            fee_program: constants::FEE_PROGRAM_ID,
476            system_program: system_program::ID,
477            event_authority: pda::pump::event_authority().0,
478            program: crate::pump::ID,
479        };
480        let args = client::args::SellV2 {
481            amount,
482            min_sol_output,
483        };
484        Instruction {
485            program_id: crate::pump::ID,
486            accounts: accounts.to_account_metas(None),
487            data: args.data(),
488        }
489    }
490
491    /// `sell_v2` with idempotent ATA creates for base/quote user accounts (skipped when `quote_mint` is the default).
492    pub fn sell_v2_instructions(
493        &self,
494        global: &Global,
495        bonding_curve: &BondingCurve,
496        base_mint: Pubkey,
497        quote_token_program: Pubkey,
498        user: Pubkey,
499        amount: u64,
500        min_sol_output: u64,
501    ) -> Option<Vec<Instruction>> {
502        let (fee_recipient, buyback_fee_recipient) =
503            Self::pump_fee_recipients_pair(global, bonding_curve.is_mayhem_mode)?;
504        let quote_mint = bonding_curve.quote_mint;
505        let creator = bonding_curve.creator;
506        let mut instructions = Self::user_trade_atas(
507            user,
508            base_mint,
509            bonding_curve,
510            constants::SPL_TOKEN_2022_PROGRAM_ID,
511            quote_token_program,
512            false,
513        );
514        instructions.push(self.sell_v2_instruction_with_recipients(
515            base_mint,
516            quote_mint,
517            quote_token_program,
518            user,
519            creator,
520            fee_recipient,
521            buyback_fee_recipient,
522            amount,
523            min_sol_output,
524        ));
525        Some(instructions)
526    }
527
528    /// `create_v2` then [`Self::buy_v2_instructions`] (wSOL quote at create).
529    /// Pass `tokenized_agent_buyback_bps = Some(bps)` to also append
530    /// [`Self::agent_initialize_instruction`] in the same tx (mirrors the
531    /// frontend's tokenized-agent flow).
532    pub fn create_v2_and_buy_instruction(
533        &self,
534        mint: Pubkey,
535        user: Pubkey,
536        name: impl Into<String>,
537        symbol: impl Into<String>,
538        uri: impl Into<String>,
539        creator: Pubkey,
540        mayhem_mode: bool,
541        cashback: bool,
542        tokenized_agent_buyback_bps: Option<u16>,
543        global: &Global,
544        amount: u64,
545        max_sol_cost: u64,
546    ) -> Option<Vec<Instruction>> {
547        let quote_token_program = constants::SPL_TOKEN_PROGRAM_ID;
548        let bonding_curve_preview = BondingCurve {
549            creator,
550            is_cashback_coin: cashback,
551            is_mayhem_mode: mayhem_mode,
552            quote_mint: constants::NATIVE_MINT,
553            ..Default::default()
554        };
555        let buy_v2_instructions = self.buy_v2_instructions(
556            global,
557            &bonding_curve_preview,
558            mint,
559            quote_token_program,
560            user,
561            amount,
562            max_sol_cost,
563        )?;
564        let mut instructions = vec![self.create_v2_instruction(
565            mint,
566            user,
567            name,
568            symbol,
569            uri,
570            creator,
571            Pubkey::default(),
572            mayhem_mode,
573            cashback,
574        )];
575
576        instructions.extend(buy_v2_instructions);
577
578        if let Some(buyback_bps) = tokenized_agent_buyback_bps {
579            instructions.push(self.agent_initialize_instruction(mint, user, creator, buyback_bps));
580        }
581
582        Some(instructions)
583    }
584
585    /// `agent_initialize` (pump_agent_payments). Initializes the
586    /// `TokenAgentPayments` PDA for `mint` with `creator` as the agent
587    /// payment authority. `user` signs and pays for the new account.
588    pub fn agent_initialize_instruction(
589        &self,
590        mint: Pubkey,
591        user: Pubkey,
592        creator: Pubkey,
593        buyback_bps: u16,
594    ) -> Instruction {
595        let accounts = agent_client::accounts::AgentInitialize {
596            authority: user,
597            bonding_curve: pda::pump::bonding_curve(&mint).0,
598            global_config: pda::pump_agent_payments::global_config().0,
599            mint,
600            token_agent_payments: pda::pump_agent_payments::token_agent_payments(&mint).0,
601            system_program: system_program::ID,
602            event_authority: pda::pump_agent_payments::event_authority().0,
603            program: crate::pump_agent_payments::ID,
604            sharing_fee_config: pda::pump::sharing_config(&mint).0,
605        };
606        let args = agent_client::args::AgentInitialize {
607            authority: creator,
608            buyback_bps,
609        };
610        Instruction {
611            program_id: crate::pump_agent_payments::ID,
612            accounts: accounts.to_account_metas(None),
613            data: args.data(),
614        }
615    }
616
617    pub fn extend_account_ix(&self, mint: Pubkey, user: Pubkey) -> Instruction {
618        let accounts = client::accounts::ExtendAccount {
619            account: pda::pump::bonding_curve(&mint).0,
620            user,
621            system_program: system_program::ID,
622            event_authority: pda::pump::event_authority().0,
623            program: crate::pump::ID,
624        };
625        Instruction {
626            program_id: crate::pump::ID,
627            accounts: accounts.to_account_metas(None),
628            data: client::args::ExtendAccount.data(),
629        }
630    }
631
632    /// `create_v2` then [`Self::buy_v2_instructions`] (wSOL quote at create).
633    /// Setting `params.tokenized_agent_buyback_bps = Some(bps)` also appends
634    /// [`Self::agent_initialize_instruction`].
635    pub fn create_coin_instructions(
636        &self,
637        params: CreateCoinParams<'_>,
638    ) -> Option<Vec<Instruction>> {
639        let CreateCoinParams {
640            mint,
641            user,
642            creator,
643            name,
644            symbol,
645            uri,
646            mayhem_mode,
647            cashback,
648            global,
649            token_amount,
650            max_sol_cost,
651            tokenized_agent_buyback_bps,
652        } = params;
653        let quote_token_program = constants::SPL_TOKEN_PROGRAM_ID;
654        let bonding_curve_preview = BondingCurve {
655            creator,
656            is_cashback_coin: cashback,
657            is_mayhem_mode: mayhem_mode,
658            quote_mint: constants::NATIVE_MINT,
659            ..Default::default()
660        };
661
662        let mut ixs: Vec<Instruction> = vec![self.create_v2_instruction(
663            mint,
664            user,
665            name,
666            symbol,
667            uri,
668            creator,
669            Pubkey::default(),
670            mayhem_mode,
671            cashback,
672        )];
673        ixs.extend(self.buy_v2_instructions(
674            global,
675            &bonding_curve_preview,
676            mint,
677            quote_token_program,
678            user,
679            token_amount,
680            max_sol_cost,
681        )?);
682        if let Some(buyback_bps) = tokenized_agent_buyback_bps {
683            ixs.push(self.agent_initialize_instruction(mint, user, creator, buyback_bps));
684        }
685        Some(ixs)
686    }
687
688    /// Idempotent ATA creates for the user's base + quote token accounts plus
689    /// the two PDA-owned quote ATAs (`associated_creator_vault` and
690    /// `associated_user_volume_accumulator`) that v2 buy/sell reference. The
691    /// base ATA is skipped when `include_base` is `false` (sell-side flows
692    /// where the user already holds the base balance to spend). A default
693    /// `bonding_curve.quote_mint` resolves to wSOL to match
694    /// [`V2TradeAccounts::derive`].
695    fn user_trade_atas(
696        user: Pubkey,
697        base_mint: Pubkey,
698        bonding_curve: &BondingCurve,
699        base_token_program: Pubkey,
700        quote_token_program: Pubkey,
701        include_base: bool,
702    ) -> Vec<Instruction> {
703        let mut ixs = vec![];
704        if include_base {
705            ixs.push(create_associated_token_account_idempotent(
706                &user,
707                &user,
708                &base_mint,
709                &base_token_program,
710            ));
711        }
712        let bonding_curve_has_non_sol_quote = bonding_curve.quote_mint != Pubkey::default()
713            && bonding_curve.quote_mint != constants::NATIVE_MINT;
714        // associated_quote_fee_recipient: Has alraedy been initialized, we will initalize this during initial testing.
715        // associated_quote_buyback_fee_recipient: Has alraedy been initialized, we will initalize this during initial testing.
716        if bonding_curve_has_non_sol_quote {
717            ixs.push(create_associated_token_account_idempotent(
718                &user,
719                &user,
720                &bonding_curve.quote_mint,
721                &quote_token_program,
722            ));
723            ixs.push(create_associated_token_account_idempotent(
724                &user,
725                &bonding_curve.creator,
726                &bonding_curve.quote_mint,
727                &quote_token_program,
728            ));
729            // associated_quote_bonding_curve
730            let bonding_curve_pda = pda::pump::bonding_curve(&base_mint).0;
731            ixs.push(create_associated_token_account_idempotent(
732                &user,
733                &bonding_curve_pda,
734                &bonding_curve.quote_mint,
735                &quote_token_program,
736            ));
737            if bonding_curve.is_cashback_coin {
738                let user_volume_accumulator = pda::pump::user_volume_accumulator(&user).0;
739                ixs.push(create_associated_token_account_idempotent(
740                    &user,
741                    &user_volume_accumulator,
742                    &bonding_curve.quote_mint,
743                    &quote_token_program,
744                ));
745            }
746        }
747        ixs
748    }
749}