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, BondingCurveFromIdl, 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_quote_tokens: 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_quote_tokens,
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_quote_tokens: 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_quote_tokens,
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`]. Pass
529    /// `quote_mint = Pubkey::default()` for a wSOL-quoted coin, or a
530    /// supported quote mint (e.g. USDC) for a non-native quote.
531    /// Pass `tokenized_agent_buyback_bps = Some(bps)` to also append
532    /// [`Self::agent_initialize_instruction`] in the same tx (mirrors the
533    /// frontend's tokenized-agent flow).
534    pub fn create_v2_and_buy_instruction(
535        &self,
536        mint: Pubkey,
537        user: Pubkey,
538        name: impl Into<String>,
539        symbol: impl Into<String>,
540        uri: impl Into<String>,
541        creator: Pubkey,
542        quote_mint: Pubkey,
543        mayhem_mode: bool,
544        cashback: bool,
545        tokenized_agent_buyback_bps: Option<u16>,
546        global: &Global,
547        amount: u64,
548        max_quote_tokens: u64,
549    ) -> Option<Vec<Instruction>> {
550        let quote_token_program = constants::SPL_TOKEN_PROGRAM_ID;
551        let resolved_quote_mint = if quote_mint == Pubkey::default() {
552            constants::NATIVE_MINT
553        } else {
554            quote_mint
555        };
556        let bonding_curve_preview = BondingCurve::new(BondingCurveFromIdl {
557            creator,
558            is_cashback_coin: cashback,
559            is_mayhem_mode: mayhem_mode,
560            quote_mint: resolved_quote_mint,
561            ..Default::default()
562        });
563        let buy_v2_instructions = self.buy_v2_instructions(
564            global,
565            &bonding_curve_preview,
566            mint,
567            quote_token_program,
568            user,
569            amount,
570            max_quote_tokens,
571        )?;
572        let mut instructions = vec![self.create_v2_instruction(
573            mint,
574            user,
575            name,
576            symbol,
577            uri,
578            creator,
579            quote_mint,
580            mayhem_mode,
581            cashback,
582        )];
583
584        instructions.extend(buy_v2_instructions);
585
586        if let Some(buyback_bps) = tokenized_agent_buyback_bps {
587            instructions.push(self.agent_initialize_instruction(mint, user, creator, buyback_bps));
588        }
589
590        Some(instructions)
591    }
592
593    /// `agent_initialize` (pump_agent_payments). Initializes the
594    /// `TokenAgentPayments` PDA for `mint` with `creator` as the agent
595    /// payment authority. `user` signs and pays for the new account.
596    pub fn agent_initialize_instruction(
597        &self,
598        mint: Pubkey,
599        user: Pubkey,
600        creator: Pubkey,
601        buyback_bps: u16,
602    ) -> Instruction {
603        let accounts = agent_client::accounts::AgentInitialize {
604            authority: user,
605            bonding_curve: pda::pump::bonding_curve(&mint).0,
606            global_config: pda::pump_agent_payments::global_config().0,
607            mint,
608            token_agent_payments: pda::pump_agent_payments::token_agent_payments(&mint).0,
609            system_program: system_program::ID,
610            event_authority: pda::pump_agent_payments::event_authority().0,
611            program: crate::pump_agent_payments::ID,
612            sharing_fee_config: pda::pump::sharing_config(&mint).0,
613        };
614        let args = agent_client::args::AgentInitialize {
615            authority: creator,
616            buyback_bps,
617        };
618        Instruction {
619            program_id: crate::pump_agent_payments::ID,
620            accounts: accounts.to_account_metas(None),
621            data: args.data(),
622        }
623    }
624
625    pub fn extend_account_ix(&self, mint: Pubkey, user: Pubkey) -> Instruction {
626        let accounts = client::accounts::ExtendAccount {
627            account: pda::pump::bonding_curve(&mint).0,
628            user,
629            system_program: system_program::ID,
630            event_authority: pda::pump::event_authority().0,
631            program: crate::pump::ID,
632        };
633        Instruction {
634            program_id: crate::pump::ID,
635            accounts: accounts.to_account_metas(None),
636            data: client::args::ExtendAccount.data(),
637        }
638    }
639
640    /// `claim_cashback_v2`. Pulls accumulated cashback for `user` to either
641    /// the user's lamport balance (legacy wSOL curves) or to their quote ATA
642    /// (non-SOL curves). Pass `quote_mint = Pubkey::default()` or
643    /// [`constants::NATIVE_MINT`] for the legacy/wSOL claim path.
644    pub fn claim_cashback_v2_instruction(
645        &self,
646        user: Pubkey,
647        quote_mint: Pubkey,
648        quote_token_program: Pubkey,
649    ) -> Instruction {
650        let resolved_quote_mint = if quote_mint == Pubkey::default() {
651            constants::NATIVE_MINT
652        } else {
653            quote_mint
654        };
655        let user_volume_accumulator = pda::pump::user_volume_accumulator(&user).0;
656        let associated_user_volume_accumulator = pda::associated_token(
657            &user_volume_accumulator,
658            &quote_token_program,
659            &resolved_quote_mint,
660        )
661        .0;
662        let associated_quote_user =
663            pda::associated_token(&user, &quote_token_program, &resolved_quote_mint).0;
664        let accounts = client::accounts::ClaimCashbackV2 {
665            user,
666            user_volume_accumulator,
667            quote_mint: resolved_quote_mint,
668            quote_token_program,
669            associated_token_program: constants::SPL_ATA_PROGRAM_ID,
670            associated_user_volume_accumulator,
671            associated_quote_user,
672            system_program: system_program::ID,
673            event_authority: pda::pump::event_authority().0,
674            program: crate::pump::ID,
675        };
676        Instruction {
677            program_id: crate::pump::ID,
678            accounts: accounts.to_account_metas(None),
679            data: client::args::ClaimCashbackV2.data(),
680        }
681    }
682
683    /// `create_v2` then [`Self::buy_v2_instructions`]. Set
684    /// `params.quote_mint = Pubkey::default()` for a wSOL-quoted coin, or a
685    /// supported quote mint (e.g. USDC) for a non-native quote.
686    /// Setting `params.tokenized_agent_buyback_bps = Some(bps)` also appends
687    /// [`Self::agent_initialize_instruction`].
688    pub fn create_coin_instructions(
689        &self,
690        params: CreateCoinParams<'_>,
691    ) -> Option<Vec<Instruction>> {
692        let CreateCoinParams {
693            mint,
694            user,
695            creator,
696            name,
697            symbol,
698            uri,
699            mayhem_mode,
700            cashback,
701            quote_mint,
702            global,
703            token_amount,
704            max_quote_tokens,
705            tokenized_agent_buyback_bps,
706        } = params;
707        let quote_token_program = constants::SPL_TOKEN_PROGRAM_ID;
708        let resolved_quote_mint = if quote_mint == Pubkey::default() {
709            constants::NATIVE_MINT
710        } else {
711            quote_mint
712        };
713        let bonding_curve_preview = BondingCurve::new(BondingCurveFromIdl {
714            creator,
715            is_cashback_coin: cashback,
716            is_mayhem_mode: mayhem_mode,
717            quote_mint: resolved_quote_mint,
718            ..Default::default()
719        });
720
721        let mut ixs: Vec<Instruction> = vec![self.create_v2_instruction(
722            mint,
723            user,
724            name,
725            symbol,
726            uri,
727            creator,
728            quote_mint,
729            mayhem_mode,
730            cashback,
731        )];
732        ixs.extend(self.buy_v2_instructions(
733            global,
734            &bonding_curve_preview,
735            mint,
736            quote_token_program,
737            user,
738            token_amount,
739            max_quote_tokens,
740        )?);
741        if let Some(buyback_bps) = tokenized_agent_buyback_bps {
742            ixs.push(self.agent_initialize_instruction(mint, user, creator, buyback_bps));
743        }
744        Some(ixs)
745    }
746
747    /// Idempotent ATA creates for the user's base + quote token accounts plus
748    /// the two PDA-owned quote ATAs (`associated_creator_vault` and
749    /// `associated_user_volume_accumulator`) that v2 buy/sell reference. The
750    /// base ATA is skipped when `include_base` is `false` (sell-side flows
751    /// where the user already holds the base balance to spend). A default
752    /// `bonding_curve.quote_mint` resolves to wSOL to match
753    /// [`V2TradeAccounts::derive`].
754    fn user_trade_atas(
755        user: Pubkey,
756        base_mint: Pubkey,
757        bonding_curve: &BondingCurve,
758        base_token_program: Pubkey,
759        quote_token_program: Pubkey,
760        include_base: bool,
761    ) -> Vec<Instruction> {
762        let mut ixs = vec![];
763        if include_base {
764            ixs.push(create_associated_token_account_idempotent(
765                &user,
766                &user,
767                &base_mint,
768                &base_token_program,
769            ));
770        }
771        let bonding_curve_has_non_sol_quote = bonding_curve.quote_mint != Pubkey::default()
772            && bonding_curve.quote_mint != constants::NATIVE_MINT;
773        // associated_quote_fee_recipient: Has alraedy been initialized, we will initalize this during initial testing.
774        // associated_quote_buyback_fee_recipient: Has alraedy been initialized, we will initalize this during initial testing.
775        if bonding_curve_has_non_sol_quote {
776            ixs.push(create_associated_token_account_idempotent(
777                &user,
778                &user,
779                &bonding_curve.quote_mint,
780                &quote_token_program,
781            ));
782            ixs.push(create_associated_token_account_idempotent(
783                &user,
784                &bonding_curve.creator,
785                &bonding_curve.quote_mint,
786                &quote_token_program,
787            ));
788            // associated_quote_bonding_curve
789            let bonding_curve_pda = pda::pump::bonding_curve(&base_mint).0;
790            ixs.push(create_associated_token_account_idempotent(
791                &user,
792                &bonding_curve_pda,
793                &bonding_curve.quote_mint,
794                &quote_token_program,
795            ));
796            if bonding_curve.is_cashback_coin {
797                let user_volume_accumulator = pda::pump::user_volume_accumulator(&user).0;
798                ixs.push(create_associated_token_account_idempotent(
799                    &user,
800                    &user_volume_accumulator,
801                    &bonding_curve.quote_mint,
802                    &quote_token_program,
803                ));
804            }
805        }
806        ixs
807    }
808}