1use anchor_lang::{InstructionData, ToAccountMetas};
2use solana_program::{
3 instruction::{AccountMeta, Instruction},
4 pubkey::Pubkey,
5 system_program, sysvar,
6};
7
8use crate::math::amm::{
9 self as amm_math, AmmContext, BuyBaseInputResult, BuyQuoteInputResult, SellBaseInputResult,
10};
11use crate::math::bonding_curve as bc_math;
12use crate::math::utils::{add_slippage, sub_slippage};
13pub use crate::math::QuoteError;
14use crate::math::QuoteResult;
15use crate::pump_amm::{
16 client as amm_client, types::OptionBool as AmmOptionBool, ID as PUMP_AMM_PROGRAM_ID,
17};
18use crate::state::pump_amm::{FeeConfig as AmmFeeConfig, GlobalConfig, Pool};
19use crate::state::{BondingCurve, FeeConfig, Global};
20use crate::token::{
21 create_associated_token_account_idempotent, unwrap_sol_instruction, wrap_sol_instructions,
22};
23use crate::{constants, pda, pump::client, pump::types::OptionBool};
24
25#[derive(Clone, Copy, Debug, PartialEq, Eq)]
33pub struct Quote {
34 pub amount: u64,
35 pub min_out: u64,
36 pub input_amount_used: u64,
37 pub max_input: u64,
38}
39
40#[derive(Clone, Copy, Debug)]
44pub enum TradeVenue {
45 BondingCurve,
46 Amm { pool: Pubkey },
47}
48
49#[derive(Clone, Copy, Debug)]
58pub enum AmmQuoteSource<'a> {
59 Pool {
60 pool: &'a Pool,
61 base_reserve: u64,
62 quote_reserve: u64,
63 base_mint_supply: u64,
64 },
65 BondingCurveComplete {
66 global: &'a Global,
67 bonding_curve: &'a BondingCurve,
68 base_mint: &'a Pubkey,
69 base_mint_supply: u64,
70 },
71}
72
73#[derive(Clone, Copy, Debug)]
77pub struct TradeTxParams {
78 pub mint: Pubkey,
79 pub base_token_program: Pubkey,
80 pub user: Pubkey,
81 pub creator: Pubkey,
82 pub fee_recipient: Pubkey,
83 pub buyback_fee_recipient: Pubkey,
84 pub is_buy: bool,
85 pub venue: TradeVenue,
86 pub is_cashback_coin: bool,
88 pub base_amount: u64,
89 pub sol_amount_threshold: u64,
90}
91
92#[derive(Clone, Debug)]
96pub struct CreateCoinParams {
97 pub mint: Pubkey,
98 pub user: Pubkey,
99 pub creator: Pubkey,
100 pub name: String,
101 pub symbol: String,
102 pub uri: String,
103 pub mayhem_mode: bool,
104 pub cashback: bool,
105 pub fee_recipient: Pubkey,
106 pub buyback_fee_recipient: Pubkey,
107 pub token_amount: u64,
108 pub max_sol_cost: u64,
109}
110
111#[derive(Clone, Copy, Debug, Default)]
113pub struct PumpSdk;
114
115impl PumpSdk {
116 pub const fn new() -> Self {
117 Self
118 }
119
120 fn map_amm_quote_source<R>(
121 global_config: &GlobalConfig,
122 fee_config: Option<&AmmFeeConfig>,
123 source: AmmQuoteSource<'_>,
124 with: impl FnOnce(AmmContext<'_>) -> QuoteResult<R>,
125 ) -> QuoteResult<R> {
126 match source {
127 AmmQuoteSource::Pool {
128 pool,
129 base_reserve,
130 quote_reserve,
131 base_mint_supply,
132 } => {
133 let ctx = AmmContext {
134 global_config,
135 fee_config,
136 base_mint: &pool.base_mint,
137 pool_creator: &pool.creator,
138 coin_creator: &pool.coin_creator,
139 base_reserve,
140 quote_reserve,
141 base_mint_supply,
142 };
143 with(ctx)
144 }
145 AmmQuoteSource::BondingCurveComplete {
146 global,
147 bonding_curve,
148 base_mint,
149 base_mint_supply,
150 } => {
151 let (base_reserve, quote_reserve) = estimated_reserves(global, bonding_curve)?;
152 let pool_creator_pk = pda::pump::pool_authority(base_mint).0;
153 let ctx = AmmContext {
154 global_config,
155 fee_config,
156 base_mint,
157 pool_creator: &pool_creator_pk,
158 coin_creator: &bonding_curve.creator,
159 base_reserve,
160 quote_reserve,
161 base_mint_supply,
162 };
163 with(ctx)
164 }
165 }
166 }
167
168 #[deprecated(note = "Use create_v2_instruction instead")]
172 pub fn create_instruction(
173 &self,
174 mint: Pubkey,
175 user: Pubkey,
176 name: impl Into<String>,
177 symbol: impl Into<String>,
178 uri: impl Into<String>,
179 creator: Pubkey,
180 ) -> Instruction {
181 let token_program = constants::SPL_TOKEN_PROGRAM_ID;
182 let bonding_curve = pda::pump::bonding_curve(&mint).0;
183 let accounts = client::accounts::Create {
184 mint,
185 mint_authority: pda::pump::mint_authority().0,
186 bonding_curve,
187 associated_bonding_curve: pda::associated_token(&bonding_curve, &token_program, &mint)
188 .0,
189 global: pda::pump::global().0,
190 mpl_token_metadata: constants::MPL_TOKEN_METADATA_PROGRAM_ID,
191 metadata: pda::pump::metadata(&mint).0,
192 user,
193 system_program: system_program::ID,
194 token_program,
195 associated_token_program: constants::SPL_ATA_PROGRAM_ID,
196 rent: sysvar::rent::ID,
197 event_authority: pda::pump::event_authority().0,
198 program: crate::pump::ID,
199 };
200 let args = client::args::Create {
201 name: name.into(),
202 symbol: symbol.into(),
203 uri: uri.into(),
204 creator,
205 };
206 Instruction {
207 program_id: crate::pump::ID,
208 accounts: accounts.to_account_metas(None),
209 data: args.data(),
210 }
211 }
212
213 pub fn create_v2_instruction(
215 &self,
216 mint: Pubkey,
217 user: Pubkey,
218 name: impl Into<String>,
219 symbol: impl Into<String>,
220 uri: impl Into<String>,
221 creator: Pubkey,
222 mayhem_mode: bool,
223 cashback: bool,
224 ) -> Instruction {
225 let token_program = constants::SPL_TOKEN_2022_PROGRAM_ID;
226 let bonding_curve = pda::pump::bonding_curve(&mint).0;
227 let accounts = client::accounts::CreateV2 {
228 mint,
229 mint_authority: pda::pump::mint_authority().0,
230 bonding_curve,
231 associated_bonding_curve: pda::associated_token(&bonding_curve, &token_program, &mint)
232 .0,
233 global: pda::pump::global().0,
234 user,
235 system_program: system_program::ID,
236 token_program,
237 associated_token_program: constants::SPL_ATA_PROGRAM_ID,
238 mayhem_program_id: constants::MAYHEM_PROGRAM_ID,
239 global_params: pda::mayhem::global_params().0,
240 sol_vault: pda::mayhem::sol_vault().0,
241 mayhem_state: pda::mayhem::mayhem_state(&mint).0,
242 mayhem_token_vault: pda::mayhem::mayhem_token_vault(&mint).0,
243 event_authority: pda::pump::event_authority().0,
244 program: crate::pump::ID,
245 };
246 let args = client::args::CreateV2 {
247 name: name.into(),
248 symbol: symbol.into(),
249 uri: uri.into(),
250 creator,
251 is_mayhem_mode: mayhem_mode,
252 is_cashback_enabled: OptionBool(cashback),
253 };
254 Instruction {
255 program_id: crate::pump::ID,
256 accounts: accounts.to_account_metas(None),
257 data: args.data(),
258 }
259 }
260
261 #[deprecated(note = "Use buy_v2_instruction instead")]
271 pub fn buy_instruction(
272 &self,
273 mint: Pubkey,
274 user: Pubkey,
275 creator: Pubkey,
276 fee_recipient: Pubkey,
277 buyback_fee_recipient: Pubkey,
278 amount: u64,
279 max_sol_cost: u64,
280 token_program: Pubkey,
281 ) -> Instruction {
282 let bonding_curve = pda::pump::bonding_curve(&mint).0;
283 let accounts = client::accounts::Buy {
284 global: pda::pump::global().0,
285 fee_recipient,
286 mint,
287 bonding_curve,
288 associated_bonding_curve: pda::associated_token(&bonding_curve, &token_program, &mint)
289 .0,
290 associated_user: pda::associated_token(&user, &token_program, &mint).0,
291 user,
292 system_program: system_program::ID,
293 token_program,
294 creator_vault: pda::pump::creator_vault(&creator).0,
295 event_authority: pda::pump::event_authority().0,
296 program: crate::pump::ID,
297 global_volume_accumulator: pda::pump::global_volume_accumulator().0,
298 user_volume_accumulator: pda::pump::user_volume_accumulator(&user).0,
299 fee_config: pda::pump::fee_config().0,
300 fee_program: constants::FEE_PROGRAM_ID,
301 };
302 let args = client::args::Buy {
303 amount,
304 max_sol_cost,
305 track_volume: OptionBool(true),
306 };
307 let mut metas = accounts.to_account_metas(None);
308 metas.push(AccountMeta::new_readonly(
309 pda::pump::bonding_curve_v2(&mint).0,
310 false,
311 ));
312 metas.push(AccountMeta::new(buyback_fee_recipient, false));
313 Instruction {
314 program_id: crate::pump::ID,
315 accounts: metas,
316 data: args.data(),
317 }
318 }
319
320 pub fn buy_v2_instruction(
325 &self,
326 base_mint: Pubkey,
327 quote_mint: Pubkey,
328 base_token_program: Pubkey,
329 quote_token_program: Pubkey,
330 user: Pubkey,
331 creator: Pubkey,
332 fee_recipient: Pubkey,
333 buyback_fee_recipient: Pubkey,
334 amount: u64,
335 max_sol_cost: u64,
336 ) -> Instruction {
337 let bonding_curve = pda::pump::bonding_curve(&base_mint).0;
338 let creator_vault = pda::pump::creator_vault(&creator).0;
339 let user_volume_accumulator = pda::pump::user_volume_accumulator(&user).0;
340 let accounts = client::accounts::BuyV2 {
341 global: pda::pump::global().0,
342 base_mint,
343 quote_mint,
344 base_token_program,
345 quote_token_program,
346 associated_token_program: constants::SPL_ATA_PROGRAM_ID,
347 fee_recipient,
348 associated_quote_fee_recipient: pda::associated_token(
349 &fee_recipient,
350 "e_token_program,
351 "e_mint,
352 )
353 .0,
354 buyback_fee_recipient,
355 associated_quote_buyback_fee_recipient: pda::associated_token(
356 &buyback_fee_recipient,
357 "e_token_program,
358 "e_mint,
359 )
360 .0,
361 bonding_curve,
362 associated_base_bonding_curve: pda::associated_token(
363 &bonding_curve,
364 &base_token_program,
365 &base_mint,
366 )
367 .0,
368 associated_quote_bonding_curve: pda::associated_token(
369 &bonding_curve,
370 "e_token_program,
371 "e_mint,
372 )
373 .0,
374 user,
375 associated_base_user: pda::associated_token(&user, &base_token_program, &base_mint).0,
376 associated_quote_user: pda::associated_token(&user, "e_token_program, "e_mint)
377 .0,
378 creator_vault,
379 associated_creator_vault: pda::associated_token(
380 &creator_vault,
381 "e_token_program,
382 "e_mint,
383 )
384 .0,
385 sharing_config: pda::pump::sharing_config(&base_mint).0,
386 global_volume_accumulator: pda::pump::global_volume_accumulator().0,
387 user_volume_accumulator,
388 associated_user_volume_accumulator: pda::associated_token(
389 &user_volume_accumulator,
390 "e_token_program,
391 "e_mint,
392 )
393 .0,
394 fee_config: pda::pump::fee_config().0,
395 fee_program: constants::FEE_PROGRAM_ID,
396 system_program: system_program::ID,
397 event_authority: pda::pump::event_authority().0,
398 program: crate::pump::ID,
399 };
400 let args = client::args::BuyV2 {
401 amount,
402 max_sol_cost,
403 };
404 Instruction {
405 program_id: crate::pump::ID,
406 accounts: accounts.to_account_metas(None),
407 data: args.data(),
408 }
409 }
410
411 pub fn buy_v2_instructions(
424 &self,
425 base_mint: Pubkey,
426 quote_mint: Pubkey,
427 base_token_program: Pubkey,
428 quote_token_program: Pubkey,
429 user: Pubkey,
430 creator: Pubkey,
431 fee_recipient: Pubkey,
432 buyback_fee_recipient: Pubkey,
433 amount: u64,
434 max_sol_cost: u64,
435 ) -> Vec<Instruction> {
436 let bonding_curve = pda::pump::bonding_curve(&base_mint).0;
437 vec![
438 create_associated_token_account_idempotent(
439 &user,
440 &user,
441 &base_mint,
442 &base_token_program,
443 ),
444 create_associated_token_account_idempotent(
445 &user,
446 &bonding_curve,
447 "e_mint,
448 "e_token_program,
449 ),
450 create_associated_token_account_idempotent(
451 &user,
452 &user,
453 "e_mint,
454 "e_token_program,
455 ),
456 create_associated_token_account_idempotent(
457 &user,
458 &buyback_fee_recipient,
459 "e_mint,
460 "e_token_program,
461 ),
462 self.buy_v2_instruction(
463 base_mint,
464 quote_mint,
465 base_token_program,
466 quote_token_program,
467 user,
468 creator,
469 fee_recipient,
470 buyback_fee_recipient,
471 amount,
472 max_sol_cost,
473 ),
474 ]
475 }
476
477 #[deprecated(note = "Use sell_v2_instruction instead")]
488 pub fn sell_instruction(
489 &self,
490 mint: Pubkey,
491 user: Pubkey,
492 creator: Pubkey,
493 fee_recipient: Pubkey,
494 amount: u64,
495 min_sol_output: u64,
496 token_program: Pubkey,
497 ) -> Instruction {
498 let bonding_curve = pda::pump::bonding_curve(&mint).0;
499 let accounts = client::accounts::Sell {
500 global: pda::pump::global().0,
501 fee_recipient,
502 mint,
503 bonding_curve,
504 associated_bonding_curve: pda::associated_token(&bonding_curve, &token_program, &mint)
505 .0,
506 associated_user: pda::associated_token(&user, &token_program, &mint).0,
507 user,
508 system_program: system_program::ID,
509 creator_vault: pda::pump::creator_vault(&creator).0,
510 token_program,
511 event_authority: pda::pump::event_authority().0,
512 program: crate::pump::ID,
513 fee_config: pda::pump::fee_config().0,
514 fee_program: constants::FEE_PROGRAM_ID,
515 };
516 let args = client::args::Sell {
517 amount,
518 min_sol_output,
519 };
520 let mut metas = accounts.to_account_metas(None);
521 metas.push(AccountMeta::new(
522 pda::pump::user_volume_accumulator(&user).0,
523 false,
524 ));
525 metas.push(AccountMeta::new_readonly(
526 pda::pump::bonding_curve_v2(&mint).0,
527 false,
528 ));
529 Instruction {
530 program_id: crate::pump::ID,
531 accounts: metas,
532 data: args.data(),
533 }
534 }
535
536 pub fn sell_v2_instruction(
541 &self,
542 base_mint: Pubkey,
543 quote_mint: Pubkey,
544 base_token_program: Pubkey,
545 quote_token_program: Pubkey,
546 user: Pubkey,
547 creator: Pubkey,
548 fee_recipient: Pubkey,
549 buyback_fee_recipient: Pubkey,
550 amount: u64,
551 min_sol_output: u64,
552 ) -> Instruction {
553 let bonding_curve = pda::pump::bonding_curve(&base_mint).0;
554 let creator_vault = pda::pump::creator_vault(&creator).0;
555 let user_volume_accumulator = pda::pump::user_volume_accumulator(&user).0;
556 let accounts = client::accounts::SellV2 {
557 global: pda::pump::global().0,
558 base_mint,
559 quote_mint,
560 base_token_program,
561 quote_token_program,
562 associated_token_program: constants::SPL_ATA_PROGRAM_ID,
563 fee_recipient,
564 associated_quote_fee_recipient: pda::associated_token(
565 &fee_recipient,
566 "e_token_program,
567 "e_mint,
568 )
569 .0,
570 buyback_fee_recipient,
571 associated_quote_buyback_fee_recipient: pda::associated_token(
572 &buyback_fee_recipient,
573 "e_token_program,
574 "e_mint,
575 )
576 .0,
577 bonding_curve,
578 associated_base_bonding_curve: pda::associated_token(
579 &bonding_curve,
580 &base_token_program,
581 &base_mint,
582 )
583 .0,
584 associated_quote_bonding_curve: pda::associated_token(
585 &bonding_curve,
586 "e_token_program,
587 "e_mint,
588 )
589 .0,
590 user,
591 associated_base_user: pda::associated_token(&user, &base_token_program, &base_mint).0,
592 associated_quote_user: pda::associated_token(&user, "e_token_program, "e_mint)
593 .0,
594 creator_vault,
595 associated_creator_vault: pda::associated_token(
596 &creator_vault,
597 "e_token_program,
598 "e_mint,
599 )
600 .0,
601 sharing_config: pda::pump::sharing_config(&base_mint).0,
602 user_volume_accumulator,
603 associated_user_volume_accumulator: pda::associated_token(
604 &user_volume_accumulator,
605 "e_token_program,
606 "e_mint,
607 )
608 .0,
609 fee_config: pda::pump::fee_config().0,
610 fee_program: constants::FEE_PROGRAM_ID,
611 system_program: system_program::ID,
612 event_authority: pda::pump::event_authority().0,
613 program: crate::pump::ID,
614 };
615 let args = client::args::SellV2 {
616 amount,
617 min_sol_output,
618 };
619 Instruction {
620 program_id: crate::pump::ID,
621 accounts: accounts.to_account_metas(None),
622 data: args.data(),
623 }
624 }
625
626 pub fn sell_v2_instructions(
634 &self,
635 base_mint: Pubkey,
636 quote_mint: Pubkey,
637 base_token_program: Pubkey,
638 quote_token_program: Pubkey,
639 user: Pubkey,
640 creator: Pubkey,
641 fee_recipient: Pubkey,
642 buyback_fee_recipient: Pubkey,
643 amount: u64,
644 min_sol_output: u64,
645 ) -> Vec<Instruction> {
646 let bonding_curve = pda::pump::bonding_curve(&base_mint).0;
647 vec![
648 create_associated_token_account_idempotent(
649 &user,
650 &user,
651 &base_mint,
652 &base_token_program,
653 ),
654 create_associated_token_account_idempotent(
655 &user,
656 &bonding_curve,
657 "e_mint,
658 "e_token_program,
659 ),
660 create_associated_token_account_idempotent(
661 &user,
662 &user,
663 "e_mint,
664 "e_token_program,
665 ),
666 create_associated_token_account_idempotent(
667 &user,
668 &buyback_fee_recipient,
669 "e_mint,
670 "e_token_program,
671 ),
672 self.sell_v2_instruction(
673 base_mint,
674 quote_mint,
675 base_token_program,
676 quote_token_program,
677 user,
678 creator,
679 fee_recipient,
680 buyback_fee_recipient,
681 amount,
682 min_sol_output,
683 ),
684 ]
685 }
686
687 pub fn create_v2_and_buy_instruction(
694 &self,
695 mint: Pubkey,
696 user: Pubkey,
697 name: impl Into<String>,
698 symbol: impl Into<String>,
699 uri: impl Into<String>,
700 creator: Pubkey,
701 mayhem_mode: bool,
702 cashback: bool,
703 fee_recipient: Pubkey,
704 buyback_fee_recipient: Pubkey,
705 amount: u64,
706 max_sol_cost: u64,
707 ) -> Vec<Instruction> {
708 let base_token_program = constants::SPL_TOKEN_2022_PROGRAM_ID;
709 let quote_mint = constants::NATIVE_MINT;
710 let quote_token_program = constants::SPL_TOKEN_PROGRAM_ID;
711 let buy_v2_instructions = self.buy_v2_instructions(
712 mint,
713 quote_mint,
714 base_token_program,
715 quote_token_program,
716 user,
717 creator,
718 fee_recipient,
719 buyback_fee_recipient,
720 amount,
721 max_sol_cost,
722 );
723 let mut instructions = vec![self.create_v2_instruction(
724 mint,
725 user,
726 name,
727 symbol,
728 uri,
729 creator,
730 mayhem_mode,
731 cashback,
732 )];
733
734 instructions.extend(buy_v2_instructions);
735
736 instructions
737 }
738
739 pub fn extend_account_ix(&self, mint: Pubkey, user: Pubkey) -> Instruction {
740 let accounts = client::accounts::ExtendAccount {
741 account: pda::pump::bonding_curve(&mint).0,
742 user,
743 system_program: system_program::ID,
744 event_authority: pda::pump::event_authority().0,
745 program: crate::pump::ID,
746 };
747 Instruction {
748 program_id: crate::pump::ID,
749 accounts: accounts.to_account_metas(None),
750 data: client::args::ExtendAccount.data(),
751 }
752 }
753
754 #[allow(clippy::too_many_arguments)]
763 pub fn buy_amm_instruction(
764 &self,
765 pool: Pubkey,
766 base_mint: Pubkey,
767 quote_mint: Pubkey,
768 base_token_program: Pubkey,
769 quote_token_program: Pubkey,
770 user: Pubkey,
771 coin_creator: Pubkey,
772 protocol_fee_recipient: Pubkey,
773 buyback_fee_recipient: Pubkey,
774 is_cashback_coin: bool,
775 base_amount_out: u64,
776 max_quote_amount_in: u64,
777 ) -> Instruction {
778 let coin_creator_vault_authority =
779 pda::pump_amm::coin_creator_vault_authority(&coin_creator).0;
780 let user_volume_accumulator = pda::pump_amm::user_volume_accumulator(&user).0;
781 let accounts = amm_client::accounts::Buy {
782 pool,
783 user,
784 global_config: pda::pump_amm::global_config().0,
785 base_mint,
786 quote_mint,
787 user_base_token_account: pda::associated_token(&user, &base_token_program, &base_mint)
788 .0,
789 user_quote_token_account: pda::associated_token(
790 &user,
791 "e_token_program,
792 "e_mint,
793 )
794 .0,
795 pool_base_token_account: pda::associated_token(&pool, &base_token_program, &base_mint)
796 .0,
797 pool_quote_token_account: pda::associated_token(
798 &pool,
799 "e_token_program,
800 "e_mint,
801 )
802 .0,
803 protocol_fee_recipient,
804 protocol_fee_recipient_token_account: pda::associated_token(
805 &protocol_fee_recipient,
806 "e_token_program,
807 "e_mint,
808 )
809 .0,
810 base_token_program,
811 quote_token_program,
812 system_program: system_program::ID,
813 associated_token_program: constants::SPL_ATA_PROGRAM_ID,
814 event_authority: pda::pump_amm::event_authority().0,
815 program: PUMP_AMM_PROGRAM_ID,
816 coin_creator_vault_ata: pda::associated_token(
817 &coin_creator_vault_authority,
818 "e_token_program,
819 "e_mint,
820 )
821 .0,
822 coin_creator_vault_authority,
823 global_volume_accumulator: pda::pump_amm::global_volume_accumulator().0,
824 user_volume_accumulator,
825 fee_config: pda::pump_amm::fee_config().0,
826 fee_program: constants::FEE_PROGRAM_ID,
827 };
828 let args = amm_client::args::Buy {
829 base_amount_out,
830 max_quote_amount_in,
831 track_volume: AmmOptionBool(true),
832 };
833 let mut metas = accounts.to_account_metas(None);
834 if is_cashback_coin {
835 metas.push(AccountMeta::new(
836 pda::associated_token(
837 &user_volume_accumulator,
838 "e_token_program,
839 &constants::NATIVE_MINT,
840 )
841 .0,
842 false,
843 ));
844 }
845 if coin_creator != Pubkey::default() {
846 metas.push(AccountMeta::new_readonly(
847 pda::pump::bonding_curve_v2(&base_mint).0,
848 false,
849 ));
850 }
851 metas.push(AccountMeta::new(buyback_fee_recipient, false));
852 metas.push(AccountMeta::new(
853 pda::associated_token(&buyback_fee_recipient, "e_token_program, "e_mint).0,
854 false,
855 ));
856 Instruction {
857 program_id: PUMP_AMM_PROGRAM_ID,
858 accounts: metas,
859 data: args.data(),
860 }
861 }
862
863 #[allow(clippy::too_many_arguments)]
868 pub fn buy_amm_instructions(
869 &self,
870 pool: Pubkey,
871 base_mint: Pubkey,
872 quote_mint: Pubkey,
873 base_token_program: Pubkey,
874 quote_token_program: Pubkey,
875 user: Pubkey,
876 coin_creator: Pubkey,
877 protocol_fee_recipient: Pubkey,
878 buyback_fee_recipient: Pubkey,
879 is_cashback_coin: bool,
880 base_amount_out: u64,
881 max_quote_amount_in: u64,
882 ) -> Vec<Instruction> {
883 vec![
884 create_associated_token_account_idempotent(
885 &user,
886 &user,
887 &base_mint,
888 &base_token_program,
889 ),
890 self.buy_amm_instruction(
891 pool,
892 base_mint,
893 quote_mint,
894 base_token_program,
895 quote_token_program,
896 user,
897 coin_creator,
898 protocol_fee_recipient,
899 buyback_fee_recipient,
900 is_cashback_coin,
901 base_amount_out,
902 max_quote_amount_in,
903 ),
904 ]
905 }
906
907 #[allow(clippy::too_many_arguments)]
914 pub fn sell_amm_instruction(
915 &self,
916 pool: Pubkey,
917 base_mint: Pubkey,
918 quote_mint: Pubkey,
919 base_token_program: Pubkey,
920 quote_token_program: Pubkey,
921 user: Pubkey,
922 coin_creator: Pubkey,
923 protocol_fee_recipient: Pubkey,
924 buyback_fee_recipient: Pubkey,
925 is_cashback_coin: bool,
926 base_amount_in: u64,
927 min_quote_amount_out: u64,
928 ) -> Instruction {
929 let coin_creator_vault_authority =
930 pda::pump_amm::coin_creator_vault_authority(&coin_creator).0;
931 let user_volume_accumulator = pda::pump_amm::user_volume_accumulator(&user).0;
932 let accounts = amm_client::accounts::Sell {
933 pool,
934 user,
935 global_config: pda::pump_amm::global_config().0,
936 base_mint,
937 quote_mint,
938 user_base_token_account: pda::associated_token(&user, &base_token_program, &base_mint)
939 .0,
940 user_quote_token_account: pda::associated_token(
941 &user,
942 "e_token_program,
943 "e_mint,
944 )
945 .0,
946 pool_base_token_account: pda::associated_token(&pool, &base_token_program, &base_mint)
947 .0,
948 pool_quote_token_account: pda::associated_token(
949 &pool,
950 "e_token_program,
951 "e_mint,
952 )
953 .0,
954 protocol_fee_recipient,
955 protocol_fee_recipient_token_account: pda::associated_token(
956 &protocol_fee_recipient,
957 "e_token_program,
958 "e_mint,
959 )
960 .0,
961 base_token_program,
962 quote_token_program,
963 system_program: system_program::ID,
964 associated_token_program: constants::SPL_ATA_PROGRAM_ID,
965 event_authority: pda::pump_amm::event_authority().0,
966 program: PUMP_AMM_PROGRAM_ID,
967 coin_creator_vault_ata: pda::associated_token(
968 &coin_creator_vault_authority,
969 "e_token_program,
970 "e_mint,
971 )
972 .0,
973 coin_creator_vault_authority,
974 fee_config: pda::pump_amm::fee_config().0,
975 fee_program: constants::FEE_PROGRAM_ID,
976 };
977 let args = amm_client::args::Sell {
978 base_amount_in,
979 min_quote_amount_out,
980 };
981 let mut metas = accounts.to_account_metas(None);
982 if is_cashback_coin {
983 metas.push(AccountMeta::new(
984 pda::associated_token(&user_volume_accumulator, "e_token_program, "e_mint)
985 .0,
986 false,
987 ));
988 metas.push(AccountMeta::new(user_volume_accumulator, false));
989 }
990 if coin_creator != Pubkey::default() {
991 metas.push(AccountMeta::new_readonly(
992 pda::pump::bonding_curve_v2(&base_mint).0,
993 false,
994 ));
995 }
996 metas.push(AccountMeta::new_readonly(buyback_fee_recipient, false));
997 metas.push(AccountMeta::new(
998 pda::associated_token(&buyback_fee_recipient, "e_token_program, "e_mint).0,
999 false,
1000 ));
1001 Instruction {
1002 program_id: PUMP_AMM_PROGRAM_ID,
1003 accounts: metas,
1004 data: args.data(),
1005 }
1006 }
1007
1008 #[allow(clippy::too_many_arguments)]
1011 pub fn sell_amm_instructions(
1012 &self,
1013 pool: Pubkey,
1014 base_mint: Pubkey,
1015 quote_mint: Pubkey,
1016 base_token_program: Pubkey,
1017 quote_token_program: Pubkey,
1018 user: Pubkey,
1019 coin_creator: Pubkey,
1020 protocol_fee_recipient: Pubkey,
1021 buyback_fee_recipient: Pubkey,
1022 is_cashback_coin: bool,
1023 base_amount_in: u64,
1024 min_quote_amount_out: u64,
1025 ) -> Vec<Instruction> {
1026 vec![
1027 create_associated_token_account_idempotent(
1028 &user,
1029 &user,
1030 "e_mint,
1031 "e_token_program,
1032 ),
1033 self.sell_amm_instruction(
1034 pool,
1035 base_mint,
1036 quote_mint,
1037 base_token_program,
1038 quote_token_program,
1039 user,
1040 coin_creator,
1041 protocol_fee_recipient,
1042 buyback_fee_recipient,
1043 is_cashback_coin,
1044 base_amount_in,
1045 min_quote_amount_out,
1046 ),
1047 ]
1048 }
1049
1050 pub fn trade_tx_instructions(&self, params: TradeTxParams) -> Vec<Instruction> {
1063 let TradeTxParams {
1064 mint,
1065 base_token_program,
1066 user,
1067 creator,
1068 fee_recipient,
1069 buyback_fee_recipient,
1070 is_buy,
1071 venue,
1072 is_cashback_coin,
1073 base_amount,
1074 sol_amount_threshold,
1075 } = params;
1076 let quote_mint = constants::NATIVE_MINT;
1077 let quote_token_program = constants::SPL_TOKEN_PROGRAM_ID;
1078
1079 match venue {
1080 TradeVenue::BondingCurve => {
1081 let bonding_curve = pda::pump::bonding_curve(&mint).0;
1082 let mut ixs = vec![
1083 create_associated_token_account_idempotent(
1084 &user,
1085 &user,
1086 &mint,
1087 &base_token_program,
1088 ),
1089 create_associated_token_account_idempotent(
1090 &user,
1091 &bonding_curve,
1092 "e_mint,
1093 "e_token_program,
1094 ),
1095 create_associated_token_account_idempotent(
1096 &user,
1097 &user,
1098 "e_mint,
1099 "e_token_program,
1100 ),
1101 create_associated_token_account_idempotent(
1102 &user,
1103 &buyback_fee_recipient,
1104 "e_mint,
1105 "e_token_program,
1106 ),
1107 ];
1108 if is_buy {
1109 ixs.extend(wrap_sol_instructions(&user, sol_amount_threshold));
1110 ixs.push(self.buy_v2_instruction(
1111 mint,
1112 quote_mint,
1113 base_token_program,
1114 quote_token_program,
1115 user,
1116 creator,
1117 fee_recipient,
1118 buyback_fee_recipient,
1119 base_amount,
1120 sol_amount_threshold,
1121 ));
1122 } else {
1123 ixs.push(self.sell_v2_instruction(
1124 mint,
1125 quote_mint,
1126 base_token_program,
1127 quote_token_program,
1128 user,
1129 creator,
1130 fee_recipient,
1131 buyback_fee_recipient,
1132 base_amount,
1133 sol_amount_threshold,
1134 ));
1135 }
1136 ixs.push(unwrap_sol_instruction(&user));
1137 ixs
1138 }
1139 TradeVenue::Amm { pool } => {
1140 let mut ixs: Vec<Instruction> = Vec::with_capacity(6);
1141 if is_buy {
1142 ixs.push(create_associated_token_account_idempotent(
1143 &user,
1144 &user,
1145 &mint,
1146 &base_token_program,
1147 ));
1148 ixs.push(create_associated_token_account_idempotent(
1149 &user,
1150 &user,
1151 "e_mint,
1152 "e_token_program,
1153 ));
1154 ixs.extend(wrap_sol_instructions(&user, sol_amount_threshold));
1155 ixs.push(self.buy_amm_instruction(
1156 pool,
1157 mint,
1158 quote_mint,
1159 base_token_program,
1160 quote_token_program,
1161 user,
1162 creator,
1163 fee_recipient,
1164 buyback_fee_recipient,
1165 is_cashback_coin,
1166 base_amount,
1167 sol_amount_threshold,
1168 ));
1169 } else {
1170 ixs.push(create_associated_token_account_idempotent(
1171 &user,
1172 &user,
1173 "e_mint,
1174 "e_token_program,
1175 ));
1176 ixs.push(self.sell_amm_instruction(
1177 pool,
1178 mint,
1179 quote_mint,
1180 base_token_program,
1181 quote_token_program,
1182 user,
1183 creator,
1184 fee_recipient,
1185 buyback_fee_recipient,
1186 is_cashback_coin,
1187 base_amount,
1188 sol_amount_threshold,
1189 ));
1190 }
1191 ixs.push(unwrap_sol_instruction(&user));
1192 ixs
1193 }
1194 }
1195 }
1196
1197 pub fn create_coin_instructions(&self, params: CreateCoinParams) -> Vec<Instruction> {
1201 let CreateCoinParams {
1202 mint,
1203 user,
1204 creator,
1205 name,
1206 symbol,
1207 uri,
1208 mayhem_mode,
1209 cashback,
1210 fee_recipient,
1211 buyback_fee_recipient,
1212 token_amount,
1213 max_sol_cost,
1214 } = params;
1215 let base_token_program = constants::SPL_TOKEN_2022_PROGRAM_ID;
1216 let quote_mint = constants::NATIVE_MINT;
1217 let quote_token_program = constants::SPL_TOKEN_PROGRAM_ID;
1218 let bonding_curve = pda::pump::bonding_curve(&mint).0;
1219
1220 let mut ixs: Vec<Instruction> = Vec::with_capacity(9);
1221 ixs.push(self.create_v2_instruction(
1222 mint,
1223 user,
1224 name,
1225 symbol,
1226 uri,
1227 creator,
1228 mayhem_mode,
1229 cashback,
1230 ));
1231 ixs.push(create_associated_token_account_idempotent(
1232 &user,
1233 &user,
1234 &mint,
1235 &base_token_program,
1236 ));
1237 ixs.push(create_associated_token_account_idempotent(
1238 &user,
1239 &bonding_curve,
1240 "e_mint,
1241 "e_token_program,
1242 ));
1243 ixs.push(create_associated_token_account_idempotent(
1244 &user,
1245 &user,
1246 "e_mint,
1247 "e_token_program,
1248 ));
1249 ixs.push(create_associated_token_account_idempotent(
1250 &user,
1251 &buyback_fee_recipient,
1252 "e_mint,
1253 "e_token_program,
1254 ));
1255 ixs.extend(wrap_sol_instructions(&user, max_sol_cost));
1256 ixs.push(self.buy_v2_instruction(
1257 mint,
1258 quote_mint,
1259 base_token_program,
1260 quote_token_program,
1261 user,
1262 creator,
1263 fee_recipient,
1264 buyback_fee_recipient,
1265 token_amount,
1266 max_sol_cost,
1267 ));
1268 ixs.push(unwrap_sol_instruction(&user));
1269 ixs
1270 }
1271
1272 pub fn buy_quote_bonding_curve_sol_in(
1294 &self,
1295 global: &Global,
1296 fee_config: Option<&FeeConfig>,
1297 bonding_curve: &BondingCurve,
1298 mint_supply: u64,
1299 sol_amount: u64,
1300 slippage_bps: u16,
1301 ) -> QuoteResult<Quote> {
1302 let tokens_out = bc_math::buy_token_amount_from_sol_amount(
1303 global,
1304 fee_config,
1305 bonding_curve,
1306 mint_supply,
1307 sol_amount,
1308 )?;
1309 Ok(Quote {
1310 amount: tokens_out,
1311 min_out: sub_slippage(tokens_out, slippage_bps),
1312 input_amount_used: sol_amount,
1313 max_input: sol_amount,
1314 })
1315 }
1316
1317 pub fn buy_quote_bonding_curve_token_out(
1319 &self,
1320 global: &Global,
1321 fee_config: Option<&FeeConfig>,
1322 bonding_curve: &BondingCurve,
1323 mint_supply: u64,
1324 token_amount: u64,
1325 slippage_bps: u16,
1326 ) -> QuoteResult<Quote> {
1327 let sol_cost = bc_math::buy_sol_amount_from_token_amount(
1328 global,
1329 fee_config,
1330 bonding_curve,
1331 mint_supply,
1332 token_amount,
1333 )?;
1334 Ok(Quote {
1335 amount: sol_cost,
1336 min_out: token_amount,
1338 input_amount_used: token_amount,
1339 max_input: add_slippage(sol_cost, slippage_bps),
1340 })
1341 }
1342
1343 pub fn sell_quote_bonding_curve(
1345 &self,
1346 global: &Global,
1347 fee_config: Option<&FeeConfig>,
1348 bonding_curve: &BondingCurve,
1349 mint_supply: u64,
1350 token_amount: u64,
1351 slippage_bps: u16,
1352 ) -> QuoteResult<Quote> {
1353 let sol_out = bc_math::sell_sol_amount_from_token_amount(
1354 global,
1355 fee_config,
1356 bonding_curve,
1357 mint_supply,
1358 token_amount,
1359 )?;
1360 Ok(Quote {
1361 amount: sol_out,
1362 min_out: sub_slippage(sol_out, slippage_bps),
1363 input_amount_used: token_amount,
1364 max_input: token_amount,
1365 })
1366 }
1367
1368 pub fn buy_quote_amm_sol_in(
1370 &self,
1371 global_config: &GlobalConfig,
1372 fee_config: Option<&AmmFeeConfig>,
1373 source: AmmQuoteSource<'_>,
1374 sol_amount: u64,
1375 slippage_bps: u16,
1376 ) -> QuoteResult<Quote> {
1377 Self::map_amm_quote_source(global_config, fee_config, source, |ctx| {
1378 let BuyQuoteInputResult {
1379 base_amount_out, ..
1380 } = amm_math::buy_quote_input(&ctx, sol_amount)?;
1381 Ok(Quote {
1382 amount: base_amount_out,
1383 min_out: sub_slippage(base_amount_out, slippage_bps),
1384 input_amount_used: sol_amount,
1385 max_input: add_slippage(sol_amount, slippage_bps),
1386 })
1387 })
1388 }
1389
1390 pub fn buy_quote_amm_token_out(
1392 &self,
1393 global_config: &GlobalConfig,
1394 fee_config: Option<&AmmFeeConfig>,
1395 source: AmmQuoteSource<'_>,
1396 token_amount: u64,
1397 slippage_bps: u16,
1398 ) -> QuoteResult<Quote> {
1399 Self::map_amm_quote_source(global_config, fee_config, source, |ctx| {
1400 let BuyBaseInputResult { total_quote_in, .. } =
1401 amm_math::buy_base_input(&ctx, token_amount)?;
1402 Ok(Quote {
1403 amount: total_quote_in,
1404 min_out: token_amount,
1405 input_amount_used: token_amount,
1406 max_input: add_slippage(total_quote_in, slippage_bps),
1407 })
1408 })
1409 }
1410
1411 pub fn sell_quote_amm(
1413 &self,
1414 global_config: &GlobalConfig,
1415 fee_config: Option<&AmmFeeConfig>,
1416 source: AmmQuoteSource<'_>,
1417 token_amount: u64,
1418 slippage_bps: u16,
1419 ) -> QuoteResult<Quote> {
1420 Self::map_amm_quote_source(global_config, fee_config, source, |ctx| {
1421 let SellBaseInputResult {
1422 final_quote_out, ..
1423 } = amm_math::sell_base_input(&ctx, token_amount)?;
1424 Ok(Quote {
1425 amount: final_quote_out,
1426 min_out: sub_slippage(final_quote_out, slippage_bps),
1427 input_amount_used: token_amount,
1428 max_input: token_amount,
1429 })
1430 })
1431 }
1432}
1433
1434fn estimated_reserves(global: &Global, bonding_curve: &BondingCurve) -> QuoteResult<(u64, u64)> {
1439 let base_reserve = global
1440 .token_total_supply
1441 .checked_sub(global.initial_real_token_reserves)
1442 .ok_or(QuoteError::EmptyReserves)?;
1443 let quote_reserve = bonding_curve
1444 .real_quote_reserves
1445 .checked_sub(global.pool_migration_fee)
1446 .ok_or(QuoteError::EmptyReserves)?;
1447 if base_reserve == 0 || quote_reserve == 0 {
1448 return Err(QuoteError::EmptyReserves);
1449 }
1450 Ok((base_reserve, quote_reserve))
1451}
1452
1453#[cfg(test)]
1454mod tests {
1455 use super::*;
1456 use anchor_lang::Discriminator;
1457 use solana_program::{pubkey, pubkey::Pubkey};
1458
1459 fn fake_pubkey(seed: u8) -> Pubkey {
1460 Pubkey::new_from_array([seed; 32])
1461 }
1462
1463 #[test]
1464 #[allow(deprecated)]
1465 fn create_instruction_wires_metadata_pdas() {
1466 let sdk = PumpSdk::new();
1467 let mint = fake_pubkey(51);
1468 let user = fake_pubkey(52);
1469 let creator = fake_pubkey(53);
1470
1471 let ix = sdk.create_instruction(
1472 mint,
1473 user,
1474 "Test",
1475 "TST",
1476 "https://example.com/metadata.json",
1477 creator,
1478 );
1479
1480 assert_eq!(ix.program_id, crate::pump::ID);
1481 assert_eq!(
1482 &ix.data[..8],
1483 <client::args::Create as Discriminator>::DISCRIMINATOR,
1484 );
1485 let metas: Vec<Pubkey> = ix.accounts.iter().map(|m| m.pubkey).collect();
1486 assert!(metas.contains(&pda::pump::metadata(&mint).0));
1487 assert!(metas.contains(&constants::MPL_TOKEN_METADATA_PROGRAM_ID));
1488 assert!(metas.contains(&constants::SPL_TOKEN_PROGRAM_ID));
1489 assert!(metas.contains(&pda::pump::bonding_curve(&mint).0));
1490 assert!(metas.contains(&sysvar::rent::ID));
1491 }
1492
1493 #[test]
1494 fn create_v2_instruction_wires_mayhem_pdas() {
1495 let sdk = PumpSdk::new();
1496 let mint = fake_pubkey(21);
1497 let user = fake_pubkey(22);
1498 let creator = fake_pubkey(23);
1499
1500 let ix = sdk.create_v2_instruction(
1501 mint,
1502 user,
1503 "Test",
1504 "TST",
1505 "https://example.com/metadata.json",
1506 creator,
1507 false,
1508 false,
1509 );
1510
1511 assert_eq!(ix.program_id, crate::pump::ID);
1512 assert_eq!(
1513 &ix.data[..8],
1514 <client::args::CreateV2 as Discriminator>::DISCRIMINATOR,
1515 );
1516 let metas: Vec<Pubkey> = ix.accounts.iter().map(|m| m.pubkey).collect();
1517 assert!(metas.contains(&pda::mayhem::global_params().0));
1518 assert!(metas.contains(&pda::mayhem::sol_vault().0));
1519 assert!(metas.contains(&pda::mayhem::mayhem_state(&mint).0));
1520 assert!(metas.contains(&pda::mayhem::mayhem_token_vault(&mint).0));
1521 assert!(metas.contains(&constants::MAYHEM_PROGRAM_ID));
1522 assert!(metas.contains(&constants::SPL_TOKEN_2022_PROGRAM_ID));
1523 }
1524
1525 #[test]
1526 #[allow(deprecated)]
1527 fn buy_instruction_appends_bonding_curve_v2_then_buyback_fee_recipient() {
1528 let sdk = PumpSdk::new();
1529 let mint = fake_pubkey(61);
1530 let user = fake_pubkey(62);
1531 let creator = fake_pubkey(63);
1532 let fee_recipient = fake_pubkey(64);
1533 let buyback_fee_recipient = fake_pubkey(65);
1534 let token_program = constants::SPL_TOKEN_PROGRAM_ID;
1535
1536 let ix = sdk.buy_instruction(
1537 mint,
1538 user,
1539 creator,
1540 fee_recipient,
1541 buyback_fee_recipient,
1542 100,
1543 5_000_000,
1544 token_program,
1545 );
1546
1547 assert_eq!(ix.program_id, crate::pump::ID);
1548 assert_eq!(
1549 &ix.data[..8],
1550 <client::args::Buy as Discriminator>::DISCRIMINATOR,
1551 );
1552
1553 let metas: Vec<Pubkey> = ix.accounts.iter().map(|m| m.pubkey).collect();
1554 assert!(metas.contains(&pda::pump::global().0));
1555 assert!(metas.contains(&pda::pump::bonding_curve(&mint).0));
1556 assert!(metas.contains(&pda::pump::creator_vault(&creator).0));
1557 assert!(metas.contains(&pda::pump::user_volume_accumulator(&user).0));
1558 assert!(metas.contains(&pda::pump::fee_config().0));
1559 assert!(metas.contains(&constants::FEE_PROGRAM_ID));
1560 assert!(metas.contains(&fee_recipient));
1561
1562 let n = ix.accounts.len();
1564 let bcv2 = &ix.accounts[n - 2];
1565 let buyback = &ix.accounts[n - 1];
1566 assert_eq!(bcv2.pubkey, pda::pump::bonding_curve_v2(&mint).0);
1567 assert!(!bcv2.is_writable);
1568 assert!(!bcv2.is_signer);
1569 assert_eq!(buyback.pubkey, buyback_fee_recipient);
1570 assert!(buyback.is_writable);
1571 assert!(!buyback.is_signer);
1572 }
1573
1574 #[test]
1575 fn create_v2_and_buy_instruction_returns_create_then_buy_v2_prefix_in_order() {
1576 let sdk = PumpSdk::new();
1577 let mint = fake_pubkey(71);
1578 let user = fake_pubkey(72);
1579 let creator = fake_pubkey(73);
1580 let fee_recipient = fake_pubkey(74);
1581 let buyback_fee_recipient = fake_pubkey(75);
1582
1583 let ixs = sdk.create_v2_and_buy_instruction(
1584 mint,
1585 user,
1586 "Test",
1587 "TST",
1588 "https://example.com/metadata.json",
1589 creator,
1590 false,
1591 false,
1592 fee_recipient,
1593 buyback_fee_recipient,
1594 1_000,
1595 500_000,
1596 );
1597
1598 assert_eq!(ixs.len(), 6);
1600 assert_eq!(
1601 &ixs[0].data[..8],
1602 <client::args::CreateV2 as Discriminator>::DISCRIMINATOR,
1603 );
1604 for i in 1..5 {
1605 assert_eq!(ixs[i].data, vec![1], "expected SPL ATA idempotent ix");
1606 }
1607 assert_eq!(
1608 &ixs[5].data[..8],
1609 <client::args::BuyV2 as Discriminator>::DISCRIMINATOR,
1610 );
1611
1612 let buy_metas: Vec<Pubkey> = ixs[5].accounts.iter().map(|m| m.pubkey).collect();
1614 assert!(buy_metas.contains(&constants::SPL_TOKEN_2022_PROGRAM_ID));
1615 assert!(buy_metas.contains(&constants::SPL_TOKEN_PROGRAM_ID));
1616 assert!(buy_metas.contains(&constants::NATIVE_MINT));
1617 assert!(buy_metas.contains(&pda::pump::sharing_config(&mint).0));
1618 }
1619
1620 #[test]
1621 fn buy_v2_instruction_wires_quote_and_sharing_config_pdas() {
1622 let sdk = PumpSdk::new();
1623 let base_mint = fake_pubkey(81);
1624 let quote_mint = fake_pubkey(82);
1625 let user = fake_pubkey(83);
1626 let creator = fake_pubkey(84);
1627 let fee_recipient = fake_pubkey(85);
1628 let buyback_fee_recipient = fake_pubkey(86);
1629 let base_token_program = constants::SPL_TOKEN_2022_PROGRAM_ID;
1630 let quote_token_program = constants::SPL_TOKEN_PROGRAM_ID;
1631
1632 let ix = sdk.buy_v2_instruction(
1633 base_mint,
1634 quote_mint,
1635 base_token_program,
1636 quote_token_program,
1637 user,
1638 creator,
1639 fee_recipient,
1640 buyback_fee_recipient,
1641 1_000,
1642 5_000_000,
1643 );
1644
1645 assert_eq!(ix.program_id, crate::pump::ID);
1646 assert_eq!(
1647 &ix.data[..8],
1648 <client::args::BuyV2 as Discriminator>::DISCRIMINATOR,
1649 );
1650
1651 let metas: Vec<Pubkey> = ix.accounts.iter().map(|m| m.pubkey).collect();
1652 assert!(metas.contains(&pda::pump::global().0));
1653 assert!(metas.contains(&base_mint));
1654 assert!(metas.contains("e_mint));
1655 assert!(metas.contains(&fee_recipient));
1656 assert!(metas.contains(&buyback_fee_recipient));
1657 assert!(metas.contains(&pda::pump::bonding_curve(&base_mint).0));
1658 assert!(metas.contains(&pda::pump::creator_vault(&creator).0));
1659 assert!(metas.contains(&pda::pump::sharing_config(&base_mint).0));
1660 assert!(metas.contains(&pda::pump::user_volume_accumulator(&user).0));
1661 assert!(metas.contains(&pda::pump::fee_config().0));
1662 assert!(metas.contains(&constants::FEE_PROGRAM_ID));
1663
1664 assert!(metas
1666 .contains(&pda::associated_token(&fee_recipient, "e_token_program, "e_mint).0));
1667 assert!(metas.contains(
1668 &pda::associated_token(&buyback_fee_recipient, "e_token_program, "e_mint).0
1669 ));
1670
1671 let signers: Vec<Pubkey> = ix
1673 .accounts
1674 .iter()
1675 .filter(|m| m.is_signer)
1676 .map(|m| m.pubkey)
1677 .collect();
1678 assert_eq!(signers, vec![user]);
1679 }
1680
1681 #[test]
1682 fn buy_v2_instructions_prepends_four_ata_creates() {
1683 let sdk = PumpSdk::new();
1684 let base_mint = fake_pubkey(91);
1685 let quote_mint = fake_pubkey(92);
1686 let user = fake_pubkey(93);
1687 let creator = fake_pubkey(94);
1688 let fee_recipient = fake_pubkey(95);
1689 let buyback_fee_recipient = fake_pubkey(96);
1690 let base_token_program = constants::SPL_TOKEN_2022_PROGRAM_ID;
1691 let quote_token_program = constants::SPL_TOKEN_PROGRAM_ID;
1692
1693 let ixs = sdk.buy_v2_instructions(
1694 base_mint,
1695 quote_mint,
1696 base_token_program,
1697 quote_token_program,
1698 user,
1699 creator,
1700 fee_recipient,
1701 buyback_fee_recipient,
1702 1_000,
1703 5_000_000,
1704 );
1705
1706 assert_eq!(ixs.len(), 5);
1708 for ata_ix in &ixs[..4] {
1709 assert_eq!(ata_ix.program_id, constants::SPL_ATA_PROGRAM_ID);
1710 assert_eq!(ata_ix.data, vec![1]);
1712 }
1713 assert_eq!(
1714 &ixs[4].data[..8],
1715 <client::args::BuyV2 as Discriminator>::DISCRIMINATOR,
1716 );
1717
1718 let bonding_curve = pda::pump::bonding_curve(&base_mint).0;
1721 let expected_atas: [Pubkey; 4] = [
1722 pda::associated_token(&user, &base_token_program, &base_mint).0,
1723 pda::associated_token(&bonding_curve, "e_token_program, "e_mint).0,
1724 pda::associated_token(&user, "e_token_program, "e_mint).0,
1725 pda::associated_token(&buyback_fee_recipient, "e_token_program, "e_mint).0,
1726 ];
1727 for (i, expected) in expected_atas.iter().enumerate() {
1728 let metas: Vec<Pubkey> = ixs[i].accounts.iter().map(|m| m.pubkey).collect();
1729 assert!(metas.contains(expected), "ix {i} missing expected ATA");
1730 }
1731 }
1732
1733 #[test]
1734 #[allow(deprecated)]
1735 fn sell_instruction_appends_user_volume_accumulator_then_bonding_curve_v2() {
1736 let sdk = PumpSdk::new();
1737 let mint = fake_pubkey(101);
1738 let user = fake_pubkey(102);
1739 let creator = fake_pubkey(103);
1740 let fee_recipient = fake_pubkey(104);
1741 let token_program = constants::SPL_TOKEN_PROGRAM_ID;
1742
1743 let ix = sdk.sell_instruction(
1744 mint,
1745 user,
1746 creator,
1747 fee_recipient,
1748 100,
1749 1_000_000,
1750 token_program,
1751 );
1752
1753 assert_eq!(ix.program_id, crate::pump::ID);
1754 assert_eq!(
1755 &ix.data[..8],
1756 <client::args::Sell as Discriminator>::DISCRIMINATOR,
1757 );
1758
1759 let metas: Vec<Pubkey> = ix.accounts.iter().map(|m| m.pubkey).collect();
1760 assert!(metas.contains(&pda::pump::global().0));
1761 assert!(metas.contains(&pda::pump::bonding_curve(&mint).0));
1762 assert!(metas.contains(&pda::pump::creator_vault(&creator).0));
1763 assert!(metas.contains(&pda::pump::fee_config().0));
1764 assert!(metas.contains(&constants::FEE_PROGRAM_ID));
1765 assert!(metas.contains(&fee_recipient));
1766 assert!(metas.contains(&pda::associated_token(&user, &token_program, &mint).0));
1767
1768 let n = ix.accounts.len();
1770 let uva = &ix.accounts[n - 2];
1771 let bcv2 = &ix.accounts[n - 1];
1772 assert_eq!(uva.pubkey, pda::pump::user_volume_accumulator(&user).0);
1773 assert!(uva.is_writable);
1774 assert!(!uva.is_signer);
1775 assert_eq!(bcv2.pubkey, pda::pump::bonding_curve_v2(&mint).0);
1776 assert!(!bcv2.is_writable);
1777 assert!(!bcv2.is_signer);
1778 }
1779
1780 #[test]
1781 fn sell_v2_instruction_wires_quote_and_sharing_config_pdas() {
1782 let sdk = PumpSdk::new();
1783 let base_mint = fake_pubkey(111);
1784 let quote_mint = fake_pubkey(112);
1785 let user = fake_pubkey(113);
1786 let creator = fake_pubkey(114);
1787 let fee_recipient = fake_pubkey(115);
1788 let buyback_fee_recipient = fake_pubkey(116);
1789 let base_token_program = constants::SPL_TOKEN_2022_PROGRAM_ID;
1790 let quote_token_program = constants::SPL_TOKEN_PROGRAM_ID;
1791
1792 let ix = sdk.sell_v2_instruction(
1793 base_mint,
1794 quote_mint,
1795 base_token_program,
1796 quote_token_program,
1797 user,
1798 creator,
1799 fee_recipient,
1800 buyback_fee_recipient,
1801 1_000,
1802 1_000_000,
1803 );
1804
1805 assert_eq!(ix.program_id, crate::pump::ID);
1806 assert_eq!(
1807 &ix.data[..8],
1808 <client::args::SellV2 as Discriminator>::DISCRIMINATOR,
1809 );
1810
1811 let metas: Vec<Pubkey> = ix.accounts.iter().map(|m| m.pubkey).collect();
1812 assert!(metas.contains(&pda::pump::global().0));
1813 assert!(metas.contains(&base_mint));
1814 assert!(metas.contains("e_mint));
1815 assert!(metas.contains(&fee_recipient));
1816 assert!(metas.contains(&buyback_fee_recipient));
1817 assert!(metas.contains(&pda::pump::bonding_curve(&base_mint).0));
1818 assert!(metas.contains(&pda::pump::creator_vault(&creator).0));
1819 assert!(metas.contains(&pda::pump::sharing_config(&base_mint).0));
1820 assert!(metas.contains(&pda::pump::user_volume_accumulator(&user).0));
1821 assert!(metas.contains(&pda::pump::fee_config().0));
1822 assert!(metas.contains(&constants::FEE_PROGRAM_ID));
1823
1824 assert!(!metas.contains(&pda::pump::global_volume_accumulator().0));
1826
1827 assert!(metas
1829 .contains(&pda::associated_token(&fee_recipient, "e_token_program, "e_mint).0));
1830 assert!(metas.contains(
1831 &pda::associated_token(&buyback_fee_recipient, "e_token_program, "e_mint).0
1832 ));
1833
1834 let signers: Vec<Pubkey> = ix
1836 .accounts
1837 .iter()
1838 .filter(|m| m.is_signer)
1839 .map(|m| m.pubkey)
1840 .collect();
1841 assert_eq!(signers, vec![user]);
1842 }
1843
1844 #[test]
1845 fn sell_v2_instructions_prepends_four_ata_creates() {
1846 let sdk = PumpSdk::new();
1847 let base_mint = fake_pubkey(121);
1848 let quote_mint = fake_pubkey(122);
1849 let user = fake_pubkey(123);
1850 let creator = fake_pubkey(124);
1851 let fee_recipient = fake_pubkey(125);
1852 let buyback_fee_recipient = fake_pubkey(126);
1853 let base_token_program = constants::SPL_TOKEN_2022_PROGRAM_ID;
1854 let quote_token_program = constants::SPL_TOKEN_PROGRAM_ID;
1855
1856 let ixs = sdk.sell_v2_instructions(
1857 base_mint,
1858 quote_mint,
1859 base_token_program,
1860 quote_token_program,
1861 user,
1862 creator,
1863 fee_recipient,
1864 buyback_fee_recipient,
1865 1_000,
1866 1_000_000,
1867 );
1868
1869 assert_eq!(ixs.len(), 5);
1871 for ata_ix in &ixs[..4] {
1872 assert_eq!(ata_ix.program_id, constants::SPL_ATA_PROGRAM_ID);
1873 assert_eq!(ata_ix.data, vec![1]);
1875 }
1876 assert_eq!(
1877 &ixs[4].data[..8],
1878 <client::args::SellV2 as Discriminator>::DISCRIMINATOR,
1879 );
1880
1881 let bonding_curve = pda::pump::bonding_curve(&base_mint).0;
1884 let expected_atas: [Pubkey; 4] = [
1885 pda::associated_token(&user, &base_token_program, &base_mint).0,
1886 pda::associated_token(&bonding_curve, "e_token_program, "e_mint).0,
1887 pda::associated_token(&user, "e_token_program, "e_mint).0,
1888 pda::associated_token(&buyback_fee_recipient, "e_token_program, "e_mint).0,
1889 ];
1890 for (i, expected) in expected_atas.iter().enumerate() {
1891 let metas: Vec<Pubkey> = ixs[i].accounts.iter().map(|m| m.pubkey).collect();
1892 assert!(metas.contains(expected), "ix {i} missing expected ATA");
1893 }
1894 }
1895
1896 fn fake_amm_inputs() -> (
1905 Pubkey,
1906 Pubkey,
1907 Pubkey,
1908 Pubkey,
1909 Pubkey,
1910 Pubkey,
1911 Pubkey,
1912 Pubkey,
1913 ) {
1914 (
1915 fake_pubkey(0xA1), fake_pubkey(0xA2), fake_pubkey(0xA3), fake_pubkey(0xA4), fake_pubkey(0xA5), fake_pubkey(0xA6), fake_pubkey(0xA7), constants::SPL_TOKEN_PROGRAM_ID, )
1924 }
1925
1926 #[test]
1927 fn buy_amm_instruction_wires_pump_amm_pdas() {
1928 let sdk = PumpSdk::new();
1929 let (pool, base_mint, quote_mint, user, coin_creator, fee_rec, buyback, _) =
1930 fake_amm_inputs();
1931 let base_token_program = constants::SPL_TOKEN_2022_PROGRAM_ID;
1932 let quote_token_program = constants::SPL_TOKEN_PROGRAM_ID;
1933
1934 let ix = sdk.buy_amm_instruction(
1935 pool,
1936 base_mint,
1937 quote_mint,
1938 base_token_program,
1939 quote_token_program,
1940 user,
1941 coin_creator,
1942 fee_rec,
1943 buyback,
1944 true, 1_000,
1946 500_000,
1947 );
1948
1949 assert_eq!(ix.program_id, crate::pump_amm::ID);
1950 assert_eq!(
1951 &ix.data[..8],
1952 <amm_client::args::Buy as Discriminator>::DISCRIMINATOR,
1953 );
1954 assert_eq!(ix.accounts.len(), 27);
1956
1957 let pubkeys: Vec<Pubkey> = ix.accounts.iter().map(|m| m.pubkey).collect();
1958 assert!(pubkeys.contains(&pda::pump_amm::global_config().0));
1960 assert!(pubkeys.contains(&pda::pump_amm::event_authority().0));
1961 assert!(pubkeys.contains(&pda::pump_amm::global_volume_accumulator().0));
1962 assert!(pubkeys.contains(&pda::pump_amm::user_volume_accumulator(&user).0));
1963 assert!(pubkeys.contains(&pda::pump_amm::fee_config().0));
1964 assert!(pubkeys.contains(&pda::pump_amm::coin_creator_vault_authority(&coin_creator).0));
1965
1966 assert_eq!(ix.accounts[0].pubkey, pool);
1968 assert!(ix.accounts[0].is_writable);
1969 assert!(!ix.accounts[0].is_signer);
1970 assert_eq!(ix.accounts[1].pubkey, user);
1971 assert!(ix.accounts[1].is_signer);
1972
1973 let n = ix.accounts.len();
1975 let cashback = &ix.accounts[n - 4];
1976 let bcv2 = &ix.accounts[n - 3];
1977 let buyback_meta = &ix.accounts[n - 2];
1978 let buyback_ata = &ix.accounts[n - 1];
1979
1980 let user_vol_accum = pda::pump_amm::user_volume_accumulator(&user).0;
1982 assert_eq!(
1983 cashback.pubkey,
1984 pda::associated_token(
1985 &user_vol_accum,
1986 "e_token_program,
1987 &constants::NATIVE_MINT,
1988 )
1989 .0
1990 );
1991 assert!(cashback.is_writable);
1992
1993 assert_eq!(bcv2.pubkey, pda::pump::bonding_curve_v2(&base_mint).0);
1994 assert!(!bcv2.is_writable);
1995
1996 assert_eq!(buyback_meta.pubkey, buyback);
1998 assert!(buyback_meta.is_writable);
1999
2000 assert_eq!(
2001 buyback_ata.pubkey,
2002 pda::associated_token(&buyback, "e_token_program, "e_mint).0
2003 );
2004 assert!(buyback_ata.is_writable);
2005 }
2006
2007 #[test]
2008 fn buy_amm_instruction_skips_remaining_when_no_cashback_no_creator() {
2009 let sdk = PumpSdk::new();
2010 let (pool, base_mint, quote_mint, user, _, fee_rec, buyback, _) = fake_amm_inputs();
2011
2012 let ix = sdk.buy_amm_instruction(
2013 pool,
2014 base_mint,
2015 quote_mint,
2016 constants::SPL_TOKEN_2022_PROGRAM_ID,
2017 constants::SPL_TOKEN_PROGRAM_ID,
2018 user,
2019 Pubkey::default(), fee_rec,
2021 buyback,
2022 false, 1_000,
2024 500_000,
2025 );
2026
2027 assert_eq!(ix.accounts.len(), 25);
2029 let n = ix.accounts.len();
2030 assert_eq!(ix.accounts[n - 2].pubkey, buyback);
2031 assert!(ix.accounts[n - 2].is_writable);
2032 }
2033
2034 #[test]
2035 fn sell_amm_instruction_wires_pump_amm_pdas() {
2036 let sdk = PumpSdk::new();
2037 let (pool, base_mint, quote_mint, user, coin_creator, fee_rec, buyback, _) =
2038 fake_amm_inputs();
2039 let base_token_program = constants::SPL_TOKEN_2022_PROGRAM_ID;
2040 let quote_token_program = constants::SPL_TOKEN_PROGRAM_ID;
2041
2042 let ix = sdk.sell_amm_instruction(
2043 pool,
2044 base_mint,
2045 quote_mint,
2046 base_token_program,
2047 quote_token_program,
2048 user,
2049 coin_creator,
2050 fee_rec,
2051 buyback,
2052 true, 1_000_000,
2054 500,
2055 );
2056
2057 assert_eq!(ix.program_id, crate::pump_amm::ID);
2058 assert_eq!(
2059 &ix.data[..8],
2060 <amm_client::args::Sell as Discriminator>::DISCRIMINATOR,
2061 );
2062 assert_eq!(ix.accounts.len(), 26);
2064
2065 let pubkeys: Vec<Pubkey> = ix.accounts.iter().map(|m| m.pubkey).collect();
2067 assert!(!pubkeys.contains(&pda::pump_amm::global_volume_accumulator().0));
2068
2069 let user_vol_accum = pda::pump_amm::user_volume_accumulator(&user).0;
2070 let n = ix.accounts.len();
2071 let cashback_ata = &ix.accounts[n - 5];
2073 let cashback_uva = &ix.accounts[n - 4];
2074 let bcv2 = &ix.accounts[n - 3];
2075 let buyback_meta = &ix.accounts[n - 2];
2076 let buyback_ata = &ix.accounts[n - 1];
2077
2078 assert_eq!(
2080 cashback_ata.pubkey,
2081 pda::associated_token(&user_vol_accum, "e_token_program, "e_mint).0
2082 );
2083 assert!(cashback_ata.is_writable);
2084
2085 assert_eq!(cashback_uva.pubkey, user_vol_accum);
2086 assert!(cashback_uva.is_writable);
2087
2088 assert_eq!(bcv2.pubkey, pda::pump::bonding_curve_v2(&base_mint).0);
2089 assert!(!bcv2.is_writable);
2090
2091 assert_eq!(buyback_meta.pubkey, buyback);
2093 assert!(!buyback_meta.is_writable);
2094
2095 assert_eq!(
2096 buyback_ata.pubkey,
2097 pda::associated_token(&buyback, "e_token_program, "e_mint).0
2098 );
2099 assert!(buyback_ata.is_writable);
2100 }
2101
2102 #[test]
2103 fn sell_amm_instruction_skips_remaining_when_no_cashback_no_creator() {
2104 let sdk = PumpSdk::new();
2105 let (pool, base_mint, quote_mint, user, _, fee_rec, buyback, _) = fake_amm_inputs();
2106
2107 let ix = sdk.sell_amm_instruction(
2108 pool,
2109 base_mint,
2110 quote_mint,
2111 constants::SPL_TOKEN_2022_PROGRAM_ID,
2112 constants::SPL_TOKEN_PROGRAM_ID,
2113 user,
2114 Pubkey::default(),
2115 fee_rec,
2116 buyback,
2117 false,
2118 1_000_000,
2119 500,
2120 );
2121
2122 assert_eq!(ix.accounts.len(), 23);
2124 let n = ix.accounts.len();
2125 assert_eq!(ix.accounts[n - 2].pubkey, buyback);
2127 assert!(!ix.accounts[n - 2].is_writable);
2128 }
2129
2130 #[test]
2131 fn buy_amm_instructions_prepends_one_ata_create() {
2132 let sdk = PumpSdk::new();
2133 let (pool, base_mint, quote_mint, user, coin_creator, fee_rec, buyback, _) =
2134 fake_amm_inputs();
2135
2136 let ixs = sdk.buy_amm_instructions(
2137 pool,
2138 base_mint,
2139 quote_mint,
2140 constants::SPL_TOKEN_2022_PROGRAM_ID,
2141 constants::SPL_TOKEN_PROGRAM_ID,
2142 user,
2143 coin_creator,
2144 fee_rec,
2145 buyback,
2146 false,
2147 1_000,
2148 500_000,
2149 );
2150
2151 assert_eq!(ixs.len(), 2);
2152 assert_eq!(ixs[0].program_id, constants::SPL_ATA_PROGRAM_ID);
2154 assert_eq!(ixs[0].data, vec![1]);
2155 assert_eq!(ixs[1].program_id, crate::pump_amm::ID);
2157 assert_eq!(
2158 &ixs[1].data[..8],
2159 <amm_client::args::Buy as Discriminator>::DISCRIMINATOR,
2160 );
2161 }
2162
2163 #[test]
2164 fn sell_amm_instructions_prepends_one_ata_create() {
2165 let sdk = PumpSdk::new();
2166 let (pool, base_mint, quote_mint, user, coin_creator, fee_rec, buyback, _) =
2167 fake_amm_inputs();
2168
2169 let ixs = sdk.sell_amm_instructions(
2170 pool,
2171 base_mint,
2172 quote_mint,
2173 constants::SPL_TOKEN_2022_PROGRAM_ID,
2174 constants::SPL_TOKEN_PROGRAM_ID,
2175 user,
2176 coin_creator,
2177 fee_rec,
2178 buyback,
2179 false,
2180 1_000_000,
2181 500,
2182 );
2183
2184 assert_eq!(ixs.len(), 2);
2185 assert_eq!(ixs[0].program_id, constants::SPL_ATA_PROGRAM_ID);
2186 assert_eq!(ixs[0].data, vec![1]);
2187 assert_eq!(ixs[1].program_id, crate::pump_amm::ID);
2188 assert_eq!(
2189 &ixs[1].data[..8],
2190 <amm_client::args::Sell as Discriminator>::DISCRIMINATOR,
2191 );
2192 }
2193
2194 fn user_wsol_ata(user: &Pubkey) -> Pubkey {
2199 pda::associated_token(
2200 user,
2201 &constants::SPL_TOKEN_PROGRAM_ID,
2202 &constants::NATIVE_MINT,
2203 )
2204 .0
2205 }
2206
2207 fn parse_system_transfer_lamports(ix: &Instruction) -> u64 {
2210 assert_eq!(ix.program_id, system_program::ID);
2211 assert_eq!(ix.data.len(), 12);
2212 assert_eq!(&ix.data[..4], &2u32.to_le_bytes());
2213 u64::from_le_bytes(ix.data[4..12].try_into().unwrap())
2214 }
2215
2216 #[test]
2217 fn trade_tx_buy_bc_wraps_sol_and_unwraps_residual() {
2218 let sdk = PumpSdk::new();
2219 let mint = fake_pubkey(0xB1);
2220 let user = fake_pubkey(0xB2);
2221 let creator = fake_pubkey(0xB3);
2222 let fee_recipient = fake_pubkey(0xB4);
2223 let buyback_fee_recipient = fake_pubkey(0xB5);
2224 let max_sol = 7_500_000u64;
2225
2226 let ixs = sdk.trade_tx_instructions(TradeTxParams {
2227 mint,
2228 base_token_program: constants::SPL_TOKEN_2022_PROGRAM_ID,
2229 user,
2230 creator,
2231 fee_recipient,
2232 buyback_fee_recipient,
2233 is_buy: true,
2234 venue: TradeVenue::BondingCurve,
2235 is_cashback_coin: false,
2236 base_amount: 1_000,
2237 sol_amount_threshold: max_sol,
2238 });
2239
2240 assert_eq!(ixs.len(), 8);
2242 for ata_ix in &ixs[..4] {
2243 assert_eq!(ata_ix.program_id, constants::SPL_ATA_PROGRAM_ID);
2244 assert_eq!(ata_ix.data, vec![1]);
2245 }
2246 assert_eq!(parse_system_transfer_lamports(&ixs[4]), max_sol);
2247 assert_eq!(ixs[5].program_id, constants::SPL_TOKEN_PROGRAM_ID);
2248 assert_eq!(ixs[6].program_id, crate::pump::ID);
2249 assert_eq!(
2250 &ixs[6].data[..8],
2251 <client::args::BuyV2 as Discriminator>::DISCRIMINATOR,
2252 );
2253 assert_eq!(ixs[7].program_id, constants::SPL_TOKEN_PROGRAM_ID);
2254
2255 let wsol_ata = user_wsol_ata(&user);
2257 assert!(ixs[5].accounts.iter().any(|m| m.pubkey == wsol_ata));
2258 assert!(ixs[7].accounts.iter().any(|m| m.pubkey == wsol_ata));
2259
2260 let bonding_curve = pda::pump::bonding_curve(&mint).0;
2262 let expected_atas: [Pubkey; 4] = [
2263 pda::associated_token(&user, &constants::SPL_TOKEN_2022_PROGRAM_ID, &mint).0,
2264 pda::associated_token(
2265 &bonding_curve,
2266 &constants::SPL_TOKEN_PROGRAM_ID,
2267 &constants::NATIVE_MINT,
2268 )
2269 .0,
2270 wsol_ata,
2271 pda::associated_token(
2272 &buyback_fee_recipient,
2273 &constants::SPL_TOKEN_PROGRAM_ID,
2274 &constants::NATIVE_MINT,
2275 )
2276 .0,
2277 ];
2278 for (i, expected) in expected_atas.iter().enumerate() {
2279 let metas: Vec<Pubkey> = ixs[i].accounts.iter().map(|m| m.pubkey).collect();
2280 assert!(metas.contains(expected), "ata ix {i} missing expected ATA");
2281 }
2282 }
2283
2284 #[test]
2285 fn trade_tx_sell_bc_unwraps_proceeds_no_wrap() {
2286 let sdk = PumpSdk::new();
2287 let mint = fake_pubkey(0xC1);
2288 let user = fake_pubkey(0xC2);
2289 let creator = fake_pubkey(0xC3);
2290 let fee_recipient = fake_pubkey(0xC4);
2291 let buyback_fee_recipient = fake_pubkey(0xC5);
2292
2293 let ixs = sdk.trade_tx_instructions(TradeTxParams {
2294 mint,
2295 base_token_program: constants::SPL_TOKEN_2022_PROGRAM_ID,
2296 user,
2297 creator,
2298 fee_recipient,
2299 buyback_fee_recipient,
2300 is_buy: false,
2301 venue: TradeVenue::BondingCurve,
2302 is_cashback_coin: false,
2303 base_amount: 1_000,
2304 sol_amount_threshold: 1_000_000,
2305 });
2306
2307 assert_eq!(ixs.len(), 6);
2309 for ata_ix in &ixs[..4] {
2310 assert_eq!(ata_ix.program_id, constants::SPL_ATA_PROGRAM_ID);
2311 assert_eq!(ata_ix.data, vec![1]);
2312 }
2313 assert_eq!(ixs[4].program_id, crate::pump::ID);
2314 assert_eq!(
2315 &ixs[4].data[..8],
2316 <client::args::SellV2 as Discriminator>::DISCRIMINATOR,
2317 );
2318 assert_eq!(ixs[5].program_id, constants::SPL_TOKEN_PROGRAM_ID);
2319 assert!(ixs[5]
2320 .accounts
2321 .iter()
2322 .any(|m| m.pubkey == user_wsol_ata(&user)));
2323 }
2324
2325 #[test]
2326 fn trade_tx_buy_amm_wraps_and_unwraps_with_cashback() {
2327 let sdk = PumpSdk::new();
2328 let mint = fake_pubkey(0xD1);
2329 let user = fake_pubkey(0xD2);
2330 let coin_creator = fake_pubkey(0xD3);
2331 let fee_recipient = fake_pubkey(0xD4);
2332 let buyback_fee_recipient = fake_pubkey(0xD5);
2333 let pool = fake_pubkey(0xD6);
2334 let max_sol = 2_500_000u64;
2335
2336 let ixs = sdk.trade_tx_instructions(TradeTxParams {
2337 mint,
2338 base_token_program: constants::SPL_TOKEN_2022_PROGRAM_ID,
2339 user,
2340 creator: coin_creator,
2341 fee_recipient,
2342 buyback_fee_recipient,
2343 is_buy: true,
2344 venue: TradeVenue::Amm { pool },
2345 is_cashback_coin: true,
2346 base_amount: 1_000,
2347 sol_amount_threshold: max_sol,
2348 });
2349
2350 assert_eq!(ixs.len(), 6);
2352 assert_eq!(ixs[0].program_id, constants::SPL_ATA_PROGRAM_ID);
2353 assert_eq!(ixs[1].program_id, constants::SPL_ATA_PROGRAM_ID);
2354 assert_eq!(parse_system_transfer_lamports(&ixs[2]), max_sol);
2355 assert_eq!(ixs[3].program_id, constants::SPL_TOKEN_PROGRAM_ID);
2356 assert_eq!(ixs[4].program_id, crate::pump_amm::ID);
2357 assert_eq!(
2358 &ixs[4].data[..8],
2359 <amm_client::args::Buy as Discriminator>::DISCRIMINATOR,
2360 );
2361 assert_eq!(ixs[5].program_id, constants::SPL_TOKEN_PROGRAM_ID);
2362
2363 let pubkeys: Vec<Pubkey> = ixs[4].accounts.iter().map(|m| m.pubkey).collect();
2366 let user_vol_accum = pda::pump_amm::user_volume_accumulator(&user).0;
2367 assert!(pubkeys.contains(
2368 &pda::associated_token(
2369 &user_vol_accum,
2370 &constants::SPL_TOKEN_PROGRAM_ID,
2371 &constants::NATIVE_MINT,
2372 )
2373 .0
2374 ));
2375 assert!(pubkeys.contains(&pda::pump::bonding_curve_v2(&mint).0));
2376 }
2377
2378 #[test]
2379 fn trade_tx_sell_amm_unwraps_proceeds_no_wrap() {
2380 let sdk = PumpSdk::new();
2381 let mint = fake_pubkey(0xE1);
2382 let user = fake_pubkey(0xE2);
2383 let coin_creator = fake_pubkey(0xE3);
2384 let fee_recipient = fake_pubkey(0xE4);
2385 let buyback_fee_recipient = fake_pubkey(0xE5);
2386 let pool = fake_pubkey(0xE6);
2387
2388 let ixs = sdk.trade_tx_instructions(TradeTxParams {
2389 mint,
2390 base_token_program: constants::SPL_TOKEN_2022_PROGRAM_ID,
2391 user,
2392 creator: coin_creator,
2393 fee_recipient,
2394 buyback_fee_recipient,
2395 is_buy: false,
2396 venue: TradeVenue::Amm { pool },
2397 is_cashback_coin: false,
2398 base_amount: 5_000,
2399 sol_amount_threshold: 100,
2400 });
2401
2402 assert_eq!(ixs.len(), 3);
2404 assert_eq!(ixs[0].program_id, constants::SPL_ATA_PROGRAM_ID);
2405 let wsol_ata = user_wsol_ata(&user);
2406 assert!(ixs[0].accounts.iter().any(|m| m.pubkey == wsol_ata));
2407 assert_eq!(ixs[1].program_id, crate::pump_amm::ID);
2408 assert_eq!(
2409 &ixs[1].data[..8],
2410 <amm_client::args::Sell as Discriminator>::DISCRIMINATOR,
2411 );
2412 assert_eq!(ixs[2].program_id, constants::SPL_TOKEN_PROGRAM_ID);
2413 assert!(ixs[2].accounts.iter().any(|m| m.pubkey == wsol_ata));
2414 }
2415
2416 #[test]
2417 fn create_coin_instructions_emits_full_create_buy_with_wrap() {
2418 let sdk = PumpSdk::new();
2419 let mint = fake_pubkey(0xF1);
2420 let user = fake_pubkey(0xF2);
2421 let creator = fake_pubkey(0xF3);
2422 let fee_recipient = fake_pubkey(0xF4);
2423 let buyback_fee_recipient = fake_pubkey(0xF5);
2424 let max_sol = 1_234_567u64;
2425
2426 let ixs = sdk.create_coin_instructions(CreateCoinParams {
2427 mint,
2428 user,
2429 creator,
2430 name: "Test".into(),
2431 symbol: "TST".into(),
2432 uri: "https://example.com/metadata.json".into(),
2433 mayhem_mode: false,
2434 cashback: false,
2435 fee_recipient,
2436 buyback_fee_recipient,
2437 token_amount: 1_000,
2438 max_sol_cost: max_sol,
2439 });
2440
2441 assert_eq!(ixs.len(), 9);
2443 assert_eq!(
2444 &ixs[0].data[..8],
2445 <client::args::CreateV2 as Discriminator>::DISCRIMINATOR,
2446 );
2447 for ata_ix in &ixs[1..5] {
2448 assert_eq!(ata_ix.program_id, constants::SPL_ATA_PROGRAM_ID);
2449 assert_eq!(ata_ix.data, vec![1]);
2450 }
2451 assert_eq!(parse_system_transfer_lamports(&ixs[5]), max_sol);
2452 assert_eq!(ixs[6].program_id, constants::SPL_TOKEN_PROGRAM_ID);
2453 assert_eq!(ixs[7].program_id, crate::pump::ID);
2454 assert_eq!(
2455 &ixs[7].data[..8],
2456 <client::args::BuyV2 as Discriminator>::DISCRIMINATOR,
2457 );
2458 let buy_metas: Vec<Pubkey> = ixs[7].accounts.iter().map(|m| m.pubkey).collect();
2460 assert!(buy_metas.contains(&constants::SPL_TOKEN_2022_PROGRAM_ID));
2461 assert!(buy_metas.contains(&constants::NATIVE_MINT));
2462 assert!(buy_metas.contains(&pda::pump::sharing_config(&mint).0));
2463
2464 assert_eq!(ixs[8].program_id, constants::SPL_TOKEN_PROGRAM_ID);
2465 let wsol_ata = user_wsol_ata(&user);
2466 assert!(ixs[8].accounts.iter().any(|m| m.pubkey == wsol_ata));
2467 }
2468
2469 #[test]
2472 fn program_ids_match_idl() {
2473 assert_eq!(
2474 crate::pump::ID,
2475 pubkey!("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P")
2476 );
2477 assert_eq!(
2478 crate::pump_amm::ID,
2479 pubkey!("pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA")
2480 );
2481 }
2482
2483 use crate::state::pump_amm::{GlobalConfig, Pool};
2494 use crate::state::{BondingCurve, Global};
2495
2496 fn fixture_global() -> Global {
2499 Global {
2500 fee_basis_points: 100,
2501 creator_fee_basis_points: 50,
2502 token_total_supply: 1_000_000_000_000_000,
2503 initial_real_token_reserves: 793_100_000_000_000,
2504 pool_migration_fee: 6_900_000_000,
2505 ..Default::default()
2506 }
2507 }
2508
2509 fn fixture_bonding_curve(creator: Pubkey) -> BondingCurve {
2511 BondingCurve {
2512 virtual_token_reserves: 1_073_000_000_000_000,
2513 virtual_quote_reserves: 30_000_000_000,
2514 real_token_reserves: 793_100_000_000_000,
2515 real_quote_reserves: 0,
2516 token_total_supply: 1_000_000_000_000_000,
2517 complete: false,
2518 creator,
2519 is_mayhem_mode: false,
2520 is_cashback_coin: false,
2521 quote_mint: constants::NATIVE_MINT,
2522 }
2523 }
2524
2525 fn fixture_amm_global_config() -> GlobalConfig {
2526 GlobalConfig {
2527 lp_fee_basis_points: 20,
2528 protocol_fee_basis_points: 5,
2529 coin_creator_fee_basis_points: 5,
2530 ..Default::default()
2531 }
2532 }
2533
2534 fn fixture_pool(coin_creator: Pubkey) -> Pool {
2537 Pool {
2538 base_mint: fake_pubkey(0xAA),
2539 creator: fake_pubkey(0xBB),
2540 coin_creator,
2541 quote_mint: constants::NATIVE_MINT,
2542 ..Default::default()
2543 }
2544 }
2545
2546 #[test]
2549 fn bc_buy_sol_in_matches_ts() {
2550 let sdk = PumpSdk::new();
2551 let g = fixture_global();
2552 let bc = fixture_bonding_curve(fake_pubkey(7));
2553 let q = sdk
2554 .buy_quote_bonding_curve_sol_in(
2555 &g,
2556 None,
2557 &bc,
2558 g.token_total_supply,
2559 1_000_000_000, 100, )
2562 .unwrap();
2563 assert_eq!(q.amount, 34_117_646_995_895);
2564 assert_eq!(q.min_out, 33_776_470_525_936);
2565 assert_eq!(q.input_amount_used, 1_000_000_000);
2566 assert_eq!(q.max_input, 1_000_000_000);
2567 }
2568
2569 #[test]
2570 fn bc_buy_token_out_matches_ts() {
2571 let sdk = PumpSdk::new();
2572 let g = fixture_global();
2573 let bc = fixture_bonding_curve(fake_pubkey(7));
2574 let q = sdk
2575 .buy_quote_bonding_curve_token_out(
2576 &g,
2577 None,
2578 &bc,
2579 g.token_total_supply,
2580 100_000_000_000_000,
2581 200, )
2583 .unwrap();
2584 assert_eq!(q.amount, 3_129_496_404);
2585 assert_eq!(q.max_input, 3_192_086_332);
2586 assert_eq!(q.min_out, 100_000_000_000_000);
2587 assert_eq!(q.input_amount_used, 100_000_000_000_000);
2588 }
2589
2590 #[test]
2591 fn bc_sell_with_creator_matches_ts() {
2592 let sdk = PumpSdk::new();
2593 let g = fixture_global();
2594 let bc = fixture_bonding_curve(fake_pubkey(7));
2595 let q = sdk
2596 .sell_quote_bonding_curve(
2597 &g,
2598 None,
2599 &bc,
2600 g.token_total_supply,
2601 50_000_000_000_000,
2602 50, )
2604 .unwrap();
2605 assert_eq!(q.amount, 1_315_672_305);
2606 assert_eq!(q.min_out, 1_309_093_943);
2607 }
2608
2609 #[test]
2610 fn bc_sell_default_creator_skips_creator_fee() {
2611 let sdk = PumpSdk::new();
2612 let g = fixture_global();
2613 let bc = fixture_bonding_curve(Pubkey::default());
2614 let q = sdk
2615 .sell_quote_bonding_curve(&g, None, &bc, g.token_total_supply, 50_000_000_000_000, 0)
2616 .unwrap();
2617 assert_eq!(q.amount, 1_322_350_845);
2620 }
2621
2622 #[test]
2625 fn amm_buy_sol_in_matches_ts() {
2626 let sdk = PumpSdk::new();
2627 let gc = fixture_amm_global_config();
2628 let pool = fixture_pool(fake_pubkey(0xCC));
2629 let q = sdk
2630 .buy_quote_amm_sol_in(
2631 &gc,
2632 None,
2633 AmmQuoteSource::Pool {
2634 pool: &pool,
2635 base_reserve: 200_000_000_000_000,
2636 quote_reserve: 30_000_000_000,
2637 base_mint_supply: 1_000_000_000_000_000,
2638 },
2639 1_000_000_000,
2640 100,
2641 )
2642 .unwrap();
2643 assert_eq!(q.amount, 6_432_936_635_069);
2644 assert_eq!(q.min_out, 6_368_607_268_718);
2645 assert_eq!(q.max_input, 1_010_000_000); }
2647
2648 #[test]
2649 fn amm_buy_token_out_matches_ts() {
2650 let sdk = PumpSdk::new();
2651 let gc = fixture_amm_global_config();
2652 let pool = fixture_pool(fake_pubkey(0xCC));
2653 let q = sdk
2654 .buy_quote_amm_token_out(
2655 &gc,
2656 None,
2657 AmmQuoteSource::Pool {
2658 pool: &pool,
2659 base_reserve: 200_000_000_000_000,
2660 quote_reserve: 30_000_000_000,
2661 base_mint_supply: 1_000_000_000_000_000,
2662 },
2663 1_000_000_000_000,
2664 100,
2665 )
2666 .unwrap();
2667 assert_eq!(q.amount, 151_206_031);
2668 assert_eq!(q.max_input, 152_718_091);
2669 }
2670
2671 #[test]
2672 fn amm_sell_with_coin_creator_matches_ts() {
2673 let sdk = PumpSdk::new();
2674 let gc = fixture_amm_global_config();
2675 let pool = fixture_pool(fake_pubkey(0xCC));
2676 let q = sdk
2677 .sell_quote_amm(
2678 &gc,
2679 None,
2680 AmmQuoteSource::Pool {
2681 pool: &pool,
2682 base_reserve: 200_000_000_000_000,
2683 quote_reserve: 30_000_000_000,
2684 base_mint_supply: 1_000_000_000_000_000,
2685 },
2686 1_000_000_000_000,
2687 100,
2688 )
2689 .unwrap();
2690 assert_eq!(q.amount, 148_805_969);
2691 assert_eq!(q.min_out, 147_317_909);
2692 }
2693
2694 #[test]
2695 fn amm_sell_default_coin_creator_skips_creator_fee() {
2696 let sdk = PumpSdk::new();
2697 let gc = fixture_amm_global_config();
2698 let pool = fixture_pool(Pubkey::default());
2699 let q = sdk
2700 .sell_quote_amm(
2701 &gc,
2702 None,
2703 AmmQuoteSource::Pool {
2704 pool: &pool,
2705 base_reserve: 200_000_000_000_000,
2706 quote_reserve: 30_000_000_000,
2707 base_mint_supply: 1_000_000_000_000_000,
2708 },
2709 1_000_000_000_000,
2710 0,
2711 )
2712 .unwrap();
2713 assert_eq!(q.amount, 148_880_596);
2714 }
2715
2716 #[test]
2719 fn zero_input_returns_zero() {
2720 let sdk = PumpSdk::new();
2721 let g = fixture_global();
2722 let bc = fixture_bonding_curve(fake_pubkey(7));
2723 let q = sdk
2724 .buy_quote_bonding_curve_sol_in(&g, None, &bc, g.token_total_supply, 0, 100)
2725 .unwrap();
2726 assert_eq!(q.amount, 0);
2727 let q = sdk
2728 .sell_quote_bonding_curve(&g, None, &bc, g.token_total_supply, 0, 100)
2729 .unwrap();
2730 assert_eq!(q.amount, 0);
2731 }
2732
2733 #[test]
2734 fn migrated_curve_returns_zero() {
2735 let sdk = PumpSdk::new();
2736 let g = fixture_global();
2737 let mut bc = fixture_bonding_curve(fake_pubkey(7));
2738 bc.virtual_token_reserves = 0; let q = sdk
2740 .buy_quote_bonding_curve_sol_in(&g, None, &bc, g.token_total_supply, 1_000_000_000, 0)
2741 .unwrap();
2742 assert_eq!(q.amount, 0);
2743 }
2744
2745 #[test]
2748 fn amm_empty_reserves_errors() {
2749 let sdk = PumpSdk::new();
2750 let gc = fixture_amm_global_config();
2751 let pool = fixture_pool(fake_pubkey(0xCC));
2752 let err = sdk
2753 .buy_quote_amm_sol_in(
2754 &gc,
2755 None,
2756 AmmQuoteSource::Pool {
2757 pool: &pool,
2758 base_reserve: 0,
2759 quote_reserve: 1_000,
2760 base_mint_supply: 1_000,
2761 },
2762 1,
2763 0,
2764 )
2765 .unwrap_err();
2766 assert_eq!(err, QuoteError::EmptyReserves);
2767 let err = sdk
2768 .sell_quote_amm(
2769 &gc,
2770 None,
2771 AmmQuoteSource::Pool {
2772 pool: &pool,
2773 base_reserve: 1_000,
2774 quote_reserve: 0,
2775 base_mint_supply: 1_000,
2776 },
2777 1,
2778 0,
2779 )
2780 .unwrap_err();
2781 assert_eq!(err, QuoteError::EmptyReserves);
2782 }
2783
2784 #[test]
2785 fn amm_buy_token_out_oversize_errors() {
2786 let sdk = PumpSdk::new();
2787 let gc = fixture_amm_global_config();
2788 let pool = fixture_pool(fake_pubkey(0xCC));
2789 let err = sdk
2791 .buy_quote_amm_token_out(
2792 &gc,
2793 None,
2794 AmmQuoteSource::Pool {
2795 pool: &pool,
2796 base_reserve: 1_000,
2797 quote_reserve: 1_000,
2798 base_mint_supply: 1_000_000,
2799 },
2800 1_000,
2801 0,
2802 )
2803 .unwrap_err();
2804 assert_eq!(err, QuoteError::BaseOutExceedsReserve);
2805 let err = sdk
2807 .buy_quote_amm_token_out(
2808 &gc,
2809 None,
2810 AmmQuoteSource::Pool {
2811 pool: &pool,
2812 base_reserve: 1_000,
2813 quote_reserve: 1_000,
2814 base_mint_supply: 1_000_000,
2815 },
2816 5_000,
2817 0,
2818 )
2819 .unwrap_err();
2820 assert_eq!(err, QuoteError::BaseOutExceedsReserve);
2821 }
2822
2823 #[test]
2824 fn bc_token_out_buy_at_real_reserves_errors() {
2825 let sdk = PumpSdk::new();
2826 let g = fixture_global();
2827 let mut bc = fixture_bonding_curve(fake_pubkey(7));
2828 bc.real_token_reserves = bc.virtual_token_reserves;
2830 let err = sdk
2831 .buy_quote_bonding_curve_token_out(
2832 &g,
2833 None,
2834 &bc,
2835 g.token_total_supply,
2836 bc.virtual_token_reserves,
2837 0,
2838 )
2839 .unwrap_err();
2840 assert_eq!(err, QuoteError::DepletedBondingCurve);
2841 }
2842
2843 #[test]
2844 fn amm_quote_bonding_curve_complete_underflow_errors() {
2845 let sdk = PumpSdk::new();
2846 let g = fixture_global();
2847 let gc = fixture_amm_global_config();
2848 let bc = fixture_bonding_curve(fake_pubkey(7)); let base_mint = fake_pubkey(0xAA);
2850 let err = sdk
2851 .buy_quote_amm_sol_in(
2852 &gc,
2853 None,
2854 AmmQuoteSource::BondingCurveComplete {
2855 global: &g,
2856 bonding_curve: &bc,
2857 base_mint: &base_mint,
2858 base_mint_supply: g.token_total_supply,
2859 },
2860 1_000_000_000,
2861 0,
2862 )
2863 .unwrap_err();
2864 assert_eq!(err, QuoteError::EmptyReserves);
2865 }
2866
2867 #[test]
2868 fn amm_quote_bonding_curve_complete_matches_formula() {
2869 let sdk = PumpSdk::new();
2870 let g = fixture_global();
2871 let gc = fixture_amm_global_config();
2872 let mut bc = fixture_bonding_curve(fake_pubkey(7));
2873 bc.real_quote_reserves = 100_000_000_000;
2874 let base_mint = fake_pubkey(0xAA);
2875
2876 let q = sdk
2877 .buy_quote_amm_sol_in(
2878 &gc,
2879 None,
2880 AmmQuoteSource::BondingCurveComplete {
2881 global: &g,
2882 bonding_curve: &bc,
2883 base_mint: &base_mint,
2884 base_mint_supply: g.token_total_supply,
2885 },
2886 1_000_000_000,
2887 0,
2888 )
2889 .unwrap();
2890
2891 let base_reserve = g.token_total_supply - g.initial_real_token_reserves;
2892 let quote_reserve = bc.real_quote_reserves - g.pool_migration_fee;
2893 let pool_creator = pda::pump::pool_authority(&base_mint).0;
2894 let ctx = AmmContext {
2895 global_config: &gc,
2896 fee_config: None,
2897 base_mint: &base_mint,
2898 pool_creator: &pool_creator,
2899 coin_creator: &bc.creator,
2900 base_reserve,
2901 quote_reserve,
2902 base_mint_supply: g.token_total_supply,
2903 };
2904 let expected = amm_math::buy_quote_input(&ctx, 1_000_000_000)
2905 .unwrap()
2906 .base_amount_out;
2907 assert_eq!(q.amount, expected);
2908 }
2909}