1use solana_program::pubkey::Pubkey;
2
3use crate::math::amm::{
4 self as amm_math, AmmContext, BuyBaseInputResult, BuyQuoteInputResult, SellBaseInputResult,
5};
6use crate::math::bonding_curve as bc_math;
7use crate::math::utils::{add_slippage, sub_slippage};
8pub use crate::math::QuoteError;
9use crate::math::QuoteResult;
10use crate::pda;
11use crate::state::pump_amm::{FeeConfig as AmmFeeConfig, GlobalConfig, Pool};
12use crate::state::{BondingCurve, FeeConfig, Global};
13
14mod pump_amm_ix;
15mod pump_legacy;
16mod pump_v2;
17mod trade_tx;
18
19#[derive(Clone, Copy, Debug, PartialEq, Eq)]
21pub struct Quote {
22 pub amount: u64,
23 pub min_out: u64,
24 pub input_amount_used: u64,
25 pub max_input: u64,
26}
27
28#[derive(Clone, Copy, Debug)]
30pub enum TradeVenue<'a> {
31 BondingCurve {
32 global: &'a Global,
33 bonding_curve: &'a BondingCurve,
34 },
35 Amm {
36 pool: Pubkey,
37 amm_global: &'a GlobalConfig,
38 pool_state: &'a Pool,
39 },
40}
41
42#[derive(Clone, Copy, Debug)]
44pub enum AmmQuoteSource<'a> {
45 Pool {
46 pool: &'a Pool,
47 base_reserve: u64,
48 quote_reserve: u64,
49 base_mint_supply: u64,
50 },
51 BondingCurveComplete {
52 global: &'a Global,
53 bonding_curve: &'a BondingCurve,
54 base_mint: &'a Pubkey,
55 base_mint_supply: u64,
56 },
57}
58
59#[derive(Clone, Copy, Debug)]
62pub struct TradeTxWithVenueParams<'a> {
63 pub mint: Pubkey,
64 pub base_token_program: Pubkey,
65 pub quote_token_program: Pubkey,
66 pub user: Pubkey,
67 pub is_buy: bool,
68 pub venue: TradeVenue<'a>,
69 pub base_amount: u64,
70 pub sol_amount_threshold: u64,
71}
72
73#[derive(Clone, Copy, Debug)]
75pub struct PumpPoolCtx<'a> {
76 pub pool: Pubkey,
77 pub amm_global: &'a GlobalConfig,
78 pub pool_state: &'a Pool,
79}
80
81#[derive(Clone, Copy, Debug)]
84pub struct TradeTxParams<'a> {
85 pub mint: Pubkey,
86 pub base_token_program: Pubkey,
87 pub quote_token_program: Pubkey,
88 pub user: Pubkey,
89 pub is_buy: bool,
90 pub base_amount: u64,
91 pub sol_amount_threshold: u64,
92 pub pump_global: &'a Global,
93 pub bonding_curve: &'a BondingCurve,
94 pub pump_pool: Option<PumpPoolCtx<'a>>,
95}
96
97#[derive(Clone, Copy, Debug)]
100pub struct PumpPoolQuoteCtx<'a> {
101 pub amm_global: &'a GlobalConfig,
102 pub amm_fee_config: Option<&'a AmmFeeConfig>,
103 pub pool_state: &'a Pool,
104 pub base_reserve: u64,
105 pub quote_reserve: u64,
106}
107
108#[derive(Clone, Copy, Debug)]
111pub struct TradeQuoteParams<'a> {
112 pub is_buy: bool,
113 pub base_amount: u64,
114 pub slippage_bps: u16,
115 pub base_mint_supply: u64,
116 pub pump_global: &'a Global,
117 pub pump_fee_config: Option<&'a FeeConfig>,
118 pub bonding_curve: &'a BondingCurve,
119 pub pump_pool: Option<PumpPoolQuoteCtx<'a>>,
120}
121
122#[derive(Clone, Debug)]
129pub struct CreateCoinParams<'a> {
130 pub mint: Pubkey,
131 pub user: Pubkey,
132 pub creator: Pubkey,
133 pub name: String,
134 pub symbol: String,
135 pub uri: String,
136 pub mayhem_mode: bool,
137 pub cashback: bool,
138 pub global: &'a Global,
139 pub token_amount: u64,
140 pub max_sol_cost: u64,
141 pub tokenized_agent_buyback_bps: Option<u16>,
142}
143
144#[derive(Clone, Copy, Debug, Default)]
146pub struct PumpSdk;
147
148impl PumpSdk {
149 pub const fn new() -> Self {
150 Self
151 }
152
153 pub fn fee_recipient_from_pump_global(global: &Global, is_mayhem: bool) -> Option<Pubkey> {
155 let mut candidates = Vec::new();
156 let (primary, pool) = if is_mayhem {
157 (
158 global.reserved_fee_recipient,
159 global.reserved_fee_recipients.as_slice(),
160 )
161 } else {
162 (global.fee_recipient, global.fee_recipients.as_slice())
163 };
164 if primary != Pubkey::default() {
165 candidates.push(primary);
166 }
167 candidates.extend(pool.iter().copied().filter(|p| *p != Pubkey::default()));
168 Self::dedupe_sorted(&mut candidates);
169 Self::pick_random_pubkey(&candidates)
170 }
171
172 pub fn buyback_fee_recipient_from_pump_global(global: &Global) -> Option<Pubkey> {
174 let mut candidates: Vec<Pubkey> = global
175 .buyback_fee_recipients
176 .iter()
177 .copied()
178 .filter(|p| *p != Pubkey::default())
179 .collect();
180 Self::dedupe_sorted(&mut candidates);
181 Self::pick_random_pubkey(&candidates)
182 }
183
184 pub fn protocol_fee_recipient_from_amm_global(global_config: &GlobalConfig) -> Option<Pubkey> {
186 let mut candidates: Vec<Pubkey> = global_config
187 .protocol_fee_recipients
188 .iter()
189 .copied()
190 .filter(|p| *p != Pubkey::default())
191 .collect();
192 Self::dedupe_sorted(&mut candidates);
193 Self::pick_random_pubkey(&candidates)
194 }
195
196 pub fn buyback_fee_recipient_from_amm_global(global_config: &GlobalConfig) -> Option<Pubkey> {
198 let mut candidates: Vec<Pubkey> = global_config
199 .buyback_fee_recipients
200 .iter()
201 .copied()
202 .filter(|p| *p != Pubkey::default())
203 .collect();
204 Self::dedupe_sorted(&mut candidates);
205 Self::pick_random_pubkey(&candidates)
206 }
207
208 pub fn pump_fee_recipients_pair(
210 global: &Global,
211 is_mayhem: bool,
212 ) -> Option<(Pubkey, Pubkey)> {
213 Some((
214 Self::fee_recipient_from_pump_global(global, is_mayhem)?,
215 Self::buyback_fee_recipient_from_pump_global(global)?,
216 ))
217 }
218
219 pub fn amm_fee_recipients_pair(global_config: &GlobalConfig) -> Option<(Pubkey, Pubkey)> {
221 Some((
222 Self::protocol_fee_recipient_from_amm_global(global_config)?,
223 Self::buyback_fee_recipient_from_amm_global(global_config)?,
224 ))
225 }
226
227 fn dedupe_sorted(keys: &mut Vec<Pubkey>) {
228 keys.sort();
229 keys.dedup();
230 }
231
232 fn pick_random_pubkey(candidates: &[Pubkey]) -> Option<Pubkey> {
233 if candidates.is_empty() {
234 return None;
235 }
236 Some(candidates[fastrand::usize(..candidates.len())])
237 }
238
239 fn map_amm_quote_source<R>(
240 global_config: &GlobalConfig,
241 fee_config: Option<&AmmFeeConfig>,
242 source: AmmQuoteSource<'_>,
243 with: impl FnOnce(AmmContext<'_>) -> QuoteResult<R>,
244 ) -> QuoteResult<R> {
245 match source {
246 AmmQuoteSource::Pool {
247 pool,
248 base_reserve,
249 quote_reserve,
250 base_mint_supply,
251 } => {
252 let ctx = AmmContext {
253 global_config,
254 fee_config,
255 base_mint: &pool.base_mint,
256 pool_creator: &pool.creator,
257 coin_creator: &pool.coin_creator,
258 base_reserve,
259 quote_reserve,
260 base_mint_supply,
261 };
262 with(ctx)
263 }
264 AmmQuoteSource::BondingCurveComplete {
265 global,
266 bonding_curve,
267 base_mint,
268 base_mint_supply,
269 } => {
270 let (base_reserve, quote_reserve) = estimated_reserves(global, bonding_curve)?;
271 let pool_creator_pk = pda::pump::pool_authority(base_mint).0;
272 let ctx = AmmContext {
273 global_config,
274 fee_config,
275 base_mint,
276 pool_creator: &pool_creator_pk,
277 coin_creator: &bonding_curve.creator,
278 base_reserve,
279 quote_reserve,
280 base_mint_supply,
281 };
282 with(ctx)
283 }
284 }
285 }
286
287 pub fn buy_quote_bonding_curve_sol_in(
289 &self,
290 global: &Global,
291 fee_config: Option<&FeeConfig>,
292 bonding_curve: &BondingCurve,
293 mint_supply: u64,
294 sol_amount: u64,
295 slippage_bps: u16,
296 ) -> QuoteResult<Quote> {
297 let tokens_out = bc_math::buy_token_amount_from_sol_amount(
298 global,
299 fee_config,
300 bonding_curve,
301 mint_supply,
302 sol_amount,
303 )?;
304 Ok(Quote {
305 amount: tokens_out,
306 min_out: sub_slippage(tokens_out, slippage_bps),
307 input_amount_used: sol_amount,
308 max_input: sol_amount,
309 })
310 }
311
312 pub fn buy_quote_bonding_curve_token_out(
314 &self,
315 global: &Global,
316 fee_config: Option<&FeeConfig>,
317 bonding_curve: &BondingCurve,
318 mint_supply: u64,
319 token_amount: u64,
320 slippage_bps: u16,
321 ) -> QuoteResult<Quote> {
322 let sol_cost = bc_math::buy_sol_amount_from_token_amount(
323 global,
324 fee_config,
325 bonding_curve,
326 mint_supply,
327 token_amount,
328 )?;
329 Ok(Quote {
330 amount: sol_cost,
331 min_out: token_amount,
332 input_amount_used: token_amount,
333 max_input: add_slippage(sol_cost, slippage_bps),
334 })
335 }
336
337 pub fn sell_quote_bonding_curve(
339 &self,
340 global: &Global,
341 fee_config: Option<&FeeConfig>,
342 bonding_curve: &BondingCurve,
343 mint_supply: u64,
344 token_amount: u64,
345 slippage_bps: u16,
346 ) -> QuoteResult<Quote> {
347 let sol_out = bc_math::sell_sol_amount_from_token_amount(
348 global,
349 fee_config,
350 bonding_curve,
351 mint_supply,
352 token_amount,
353 )?;
354 Ok(Quote {
355 amount: sol_out,
356 min_out: sub_slippage(sol_out, slippage_bps),
357 input_amount_used: token_amount,
358 max_input: token_amount,
359 })
360 }
361
362 pub fn buy_quote_amm_sol_in(
364 &self,
365 global_config: &GlobalConfig,
366 fee_config: Option<&AmmFeeConfig>,
367 source: AmmQuoteSource<'_>,
368 sol_amount: u64,
369 slippage_bps: u16,
370 ) -> QuoteResult<Quote> {
371 Self::map_amm_quote_source(global_config, fee_config, source, |ctx| {
372 let BuyQuoteInputResult {
373 base_amount_out, ..
374 } = amm_math::buy_quote_input(&ctx, sol_amount)?;
375 Ok(Quote {
376 amount: base_amount_out,
377 min_out: sub_slippage(base_amount_out, slippage_bps),
378 input_amount_used: sol_amount,
379 max_input: add_slippage(sol_amount, slippage_bps),
380 })
381 })
382 }
383
384 pub fn buy_quote_amm_token_out(
386 &self,
387 global_config: &GlobalConfig,
388 fee_config: Option<&AmmFeeConfig>,
389 source: AmmQuoteSource<'_>,
390 token_amount: u64,
391 slippage_bps: u16,
392 ) -> QuoteResult<Quote> {
393 Self::map_amm_quote_source(global_config, fee_config, source, |ctx| {
394 let BuyBaseInputResult { total_quote_in, .. } =
395 amm_math::buy_base_input(&ctx, token_amount)?;
396 Ok(Quote {
397 amount: total_quote_in,
398 min_out: token_amount,
399 input_amount_used: token_amount,
400 max_input: add_slippage(total_quote_in, slippage_bps),
401 })
402 })
403 }
404
405 pub fn sell_quote_amm(
407 &self,
408 global_config: &GlobalConfig,
409 fee_config: Option<&AmmFeeConfig>,
410 source: AmmQuoteSource<'_>,
411 token_amount: u64,
412 slippage_bps: u16,
413 ) -> QuoteResult<Quote> {
414 Self::map_amm_quote_source(global_config, fee_config, source, |ctx| {
415 let SellBaseInputResult {
416 final_quote_out, ..
417 } = amm_math::sell_base_input(&ctx, token_amount)?;
418 Ok(Quote {
419 amount: final_quote_out,
420 min_out: sub_slippage(final_quote_out, slippage_bps),
421 input_amount_used: token_amount,
422 max_input: token_amount,
423 })
424 })
425 }
426}
427
428fn estimated_reserves(global: &Global, bonding_curve: &BondingCurve) -> QuoteResult<(u64, u64)> {
430 let base_reserve = global
431 .token_total_supply
432 .checked_sub(global.initial_real_token_reserves)
433 .ok_or(QuoteError::EmptyReserves)?;
434 let quote_reserve = bonding_curve
435 .real_quote_reserves
436 .checked_sub(global.pool_migration_fee)
437 .ok_or(QuoteError::EmptyReserves)?;
438 if base_reserve == 0 || quote_reserve == 0 {
439 return Err(QuoteError::EmptyReserves);
440 }
441 Ok((base_reserve, quote_reserve))
442}
443
444#[cfg(test)]
445mod tests {
446 use super::*;
447 use anchor_lang::Discriminator;
448 use solana_program::{pubkey, pubkey::Pubkey, system_program};
449
450 use crate::constants;
451 use crate::math::amm::AmmContext;
452 use crate::pda;
453 use crate::pump::client;
454 use crate::pump_agent_payments::client as agent_client;
455 use crate::pump_amm::client as amm_client;
456 use crate::state::pump_amm::{GlobalConfig, Pool};
457 use crate::state::{BondingCurve, Global};
458
459 fn fake_pubkey(seed: u8) -> Pubkey {
460 Pubkey::new_from_array([seed; 32])
461 }
462
463 fn pump_global_with_fees(fee_recipient: Pubkey, buyback_fee_recipient: Pubkey) -> Global {
464 let mut g = Global::default();
465 g.fee_recipient = fee_recipient;
466 g.buyback_fee_recipients[0] = buyback_fee_recipient;
467 g
468 }
469
470 fn amm_global_with_fees(protocol: Pubkey, buyback: Pubkey) -> GlobalConfig {
471 let mut g = GlobalConfig::default();
472 g.protocol_fee_recipients[0] = protocol;
473 g.buyback_fee_recipients[0] = buyback;
474 g
475 }
476
477 #[test]
478 #[allow(deprecated)]
479 fn create_instruction_wires_metadata_pdas() {
480 let sdk = PumpSdk::new();
481 let mint = fake_pubkey(51);
482 let user = fake_pubkey(52);
483 let creator = fake_pubkey(53);
484
485 let ix = sdk.create_instruction(
486 mint,
487 user,
488 "Test",
489 "TST",
490 "https://example.com/metadata.json",
491 creator,
492 );
493
494 assert_eq!(ix.program_id, crate::pump::ID);
495 assert_eq!(
496 &ix.data[..8],
497 <client::args::Create as Discriminator>::DISCRIMINATOR,
498 );
499 let metas: Vec<Pubkey> = ix.accounts.iter().map(|m| m.pubkey).collect();
500 assert!(metas.contains(&pda::pump::metadata(&mint).0));
501 assert!(metas.contains(&constants::MPL_TOKEN_METADATA_PROGRAM_ID));
502 assert!(metas.contains(&constants::SPL_TOKEN_PROGRAM_ID));
503 assert!(metas.contains(&pda::pump::bonding_curve(&mint).0));
504 assert!(metas.contains(&solana_program::sysvar::rent::ID));
505 }
506
507 #[test]
508 fn create_v2_instruction_wires_mayhem_pdas() {
509 let sdk = PumpSdk::new();
510 let mint = fake_pubkey(21);
511 let user = fake_pubkey(22);
512 let creator = fake_pubkey(23);
513
514 let ix = sdk.create_v2_instruction(
515 mint,
516 user,
517 "Test",
518 "TST",
519 "https://example.com/metadata.json",
520 creator,
521 Pubkey::default(),
522 false,
523 false,
524 );
525
526 assert_eq!(ix.program_id, crate::pump::ID);
527 assert_eq!(
528 &ix.data[..8],
529 <client::args::CreateV2 as Discriminator>::DISCRIMINATOR,
530 );
531 let metas: Vec<Pubkey> = ix.accounts.iter().map(|m| m.pubkey).collect();
532 assert!(metas.contains(&pda::mayhem::global_params().0));
533 assert!(metas.contains(&pda::mayhem::sol_vault().0));
534 assert!(metas.contains(&pda::mayhem::mayhem_state(&mint).0));
535 assert!(metas.contains(&pda::mayhem::mayhem_token_vault(&mint).0));
536 assert!(metas.contains(&constants::MAYHEM_PROGRAM_ID));
537 assert!(metas.contains(&constants::SPL_TOKEN_2022_PROGRAM_ID));
538 }
539
540 #[test]
541 fn create_v2_instruction_appends_remaining_accounts_for_non_sol_quote() {
542 let sdk = PumpSdk::new();
543 let mint = fake_pubkey(0xA1);
544 let user = fake_pubkey(0xA2);
545 let creator = fake_pubkey(0xA3);
546 let quote_mint = fake_pubkey(0xC1);
547
548 let baseline = sdk.create_v2_instruction(
549 mint,
550 user,
551 "Test",
552 "TST",
553 "https://example.com/metadata.json",
554 creator,
555 Pubkey::default(),
556 false,
557 false,
558 );
559 let ix = sdk.create_v2_instruction(
560 mint,
561 user,
562 "Test",
563 "TST",
564 "https://example.com/metadata.json",
565 creator,
566 quote_mint,
567 false,
568 false,
569 );
570
571 assert_eq!(ix.accounts.len(), baseline.accounts.len() + 3);
572
573 let n = ix.accounts.len();
574 let quote_meta = &ix.accounts[n - 3];
575 let ata_meta = &ix.accounts[n - 2];
576 let token_program_meta = &ix.accounts[n - 1];
577
578 assert_eq!(quote_meta.pubkey, quote_mint);
579 assert!(!quote_meta.is_writable);
580 assert!(!quote_meta.is_signer);
581
582 let bonding_curve = pda::pump::bonding_curve(&mint).0;
583 let expected_ata =
584 pda::associated_token(&bonding_curve, &constants::SPL_TOKEN_PROGRAM_ID, "e_mint).0;
585 assert_eq!(ata_meta.pubkey, expected_ata);
586 assert!(ata_meta.is_writable);
587 assert!(!ata_meta.is_signer);
588
589 assert_eq!(token_program_meta.pubkey, constants::SPL_TOKEN_PROGRAM_ID);
590 assert!(!token_program_meta.is_writable);
591 assert!(!token_program_meta.is_signer);
592 }
593
594 #[test]
595 fn create_v2_instruction_omits_remaining_accounts_for_sol_quote() {
596 let sdk = PumpSdk::new();
597 let mint = fake_pubkey(0xB1);
598 let user = fake_pubkey(0xB2);
599 let creator = fake_pubkey(0xB3);
600
601 let with_default = sdk.create_v2_instruction(
602 mint,
603 user,
604 "Test",
605 "TST",
606 "https://example.com/metadata.json",
607 creator,
608 Pubkey::default(),
609 false,
610 false,
611 );
612 let with_native = sdk.create_v2_instruction(
613 mint,
614 user,
615 "Test",
616 "TST",
617 "https://example.com/metadata.json",
618 creator,
619 constants::NATIVE_MINT,
620 false,
621 false,
622 );
623
624 assert_eq!(with_default.accounts.len(), with_native.accounts.len());
625 for (a, b) in with_default
626 .accounts
627 .iter()
628 .zip(with_native.accounts.iter())
629 {
630 assert_eq!(a.pubkey, b.pubkey);
631 assert_eq!(a.is_writable, b.is_writable);
632 assert_eq!(a.is_signer, b.is_signer);
633 }
634 assert!(!with_default
635 .accounts
636 .iter()
637 .any(|m| m.pubkey == constants::SPL_TOKEN_PROGRAM_ID));
638 }
639
640 #[test]
641 #[allow(deprecated)]
642 fn buy_instruction_appends_bonding_curve_v2_then_buyback_fee_recipient() {
643 let sdk = PumpSdk::new();
644 let mint = fake_pubkey(61);
645 let user = fake_pubkey(62);
646 let creator = fake_pubkey(63);
647 let fee_recipient = fake_pubkey(64);
648 let buyback_fee_recipient = fake_pubkey(65);
649 let token_program = constants::SPL_TOKEN_PROGRAM_ID;
650
651 let ix = sdk.buy_instruction(
652 mint,
653 user,
654 creator,
655 fee_recipient,
656 buyback_fee_recipient,
657 100,
658 5_000_000,
659 token_program,
660 );
661
662 assert_eq!(ix.program_id, crate::pump::ID);
663 assert_eq!(
664 &ix.data[..8],
665 <client::args::Buy as Discriminator>::DISCRIMINATOR,
666 );
667
668 let metas: Vec<Pubkey> = ix.accounts.iter().map(|m| m.pubkey).collect();
669 assert!(metas.contains(&pda::pump::global().0));
670 assert!(metas.contains(&pda::pump::bonding_curve(&mint).0));
671 assert!(metas.contains(&pda::pump::creator_vault(&creator).0));
672 assert!(metas.contains(&pda::pump::user_volume_accumulator(&user).0));
673 assert!(metas.contains(&pda::pump::fee_config().0));
674 assert!(metas.contains(&constants::FEE_PROGRAM_ID));
675 assert!(metas.contains(&fee_recipient));
676
677 let n = ix.accounts.len();
678 let bcv2 = &ix.accounts[n - 2];
679 let buyback = &ix.accounts[n - 1];
680 assert_eq!(bcv2.pubkey, pda::pump::bonding_curve_v2(&mint).0);
681 assert!(!bcv2.is_writable);
682 assert!(!bcv2.is_signer);
683 assert_eq!(buyback.pubkey, buyback_fee_recipient);
684 assert!(buyback.is_writable);
685 assert!(!buyback.is_signer);
686 }
687
688 #[test]
689 fn create_v2_and_buy_instruction_returns_create_then_buy_v2_prefix_in_order() {
690 let sdk = PumpSdk::new();
691 let mint = fake_pubkey(71);
692 let user = fake_pubkey(72);
693 let creator = fake_pubkey(73);
694 let fee_recipient = fake_pubkey(74);
695 let buyback_fee_recipient = fake_pubkey(75);
696 let global = pump_global_with_fees(fee_recipient, buyback_fee_recipient);
697
698 let ixs = sdk
699 .create_v2_and_buy_instruction(
700 mint,
701 user,
702 "Test",
703 "TST",
704 "https://example.com/metadata.json",
705 creator,
706 false,
707 false,
708 None,
709 &global,
710 1_000,
711 500_000,
712 )
713 .expect("create_v2_and_buy_instruction");
714
715 assert_eq!(ixs.len(), 6);
716 assert_eq!(
717 &ixs[0].data[..8],
718 <client::args::CreateV2 as Discriminator>::DISCRIMINATOR,
719 );
720 for i in 1..5 {
721 assert_eq!(ixs[i].data, vec![1], "expected SPL ATA idempotent ix");
722 }
723 assert_eq!(
724 &ixs[5].data[..8],
725 <client::args::BuyV2 as Discriminator>::DISCRIMINATOR,
726 );
727
728 let buy_metas: Vec<Pubkey> = ixs[5].accounts.iter().map(|m| m.pubkey).collect();
729 assert!(buy_metas.contains(&constants::SPL_TOKEN_2022_PROGRAM_ID));
730 assert!(buy_metas.contains(&constants::SPL_TOKEN_PROGRAM_ID));
731 assert!(buy_metas.contains(&constants::NATIVE_MINT));
732 assert!(buy_metas.contains(&pda::pump::sharing_config(&mint).0));
733 }
734
735 #[test]
736 fn buy_v2_instruction_wires_quote_and_sharing_config_pdas() {
737 let sdk = PumpSdk::new();
738 let base_mint = fake_pubkey(81);
739 let quote_mint = fake_pubkey(82);
740 let user = fake_pubkey(83);
741 let creator = fake_pubkey(84);
742 let fee_recipient = fake_pubkey(85);
743 let buyback_fee_recipient = fake_pubkey(86);
744 let base_token_program = constants::SPL_TOKEN_2022_PROGRAM_ID;
745 let quote_token_program = constants::SPL_TOKEN_PROGRAM_ID;
746 let global = pump_global_with_fees(fee_recipient, buyback_fee_recipient);
747 let bonding_curve = BondingCurve {
748 creator,
749 quote_mint,
750 ..Default::default()
751 };
752
753 let ix = sdk
754 .buy_v2_instruction(
755 &global,
756 &bonding_curve,
757 base_mint,
758 quote_token_program,
759 user,
760 1_000,
761 5_000_000,
762 )
763 .expect("buy_v2_instruction");
764
765 assert_eq!(ix.program_id, crate::pump::ID);
766 assert_eq!(
767 &ix.data[..8],
768 <client::args::BuyV2 as Discriminator>::DISCRIMINATOR,
769 );
770
771 let metas: Vec<Pubkey> = ix.accounts.iter().map(|m| m.pubkey).collect();
772 assert!(metas.contains(&pda::pump::global().0));
773 assert!(metas.contains(&base_mint));
774 assert!(metas.contains("e_mint));
775 assert!(metas.contains(&fee_recipient));
776 assert!(metas.contains(&buyback_fee_recipient));
777 assert!(metas.contains(&pda::pump::bonding_curve(&base_mint).0));
778 assert!(metas.contains(&pda::pump::creator_vault(&creator).0));
779 assert!(metas.contains(&pda::pump::sharing_config(&base_mint).0));
780 assert!(metas.contains(&pda::pump::user_volume_accumulator(&user).0));
781 assert!(metas.contains(&pda::pump::fee_config().0));
782 assert!(metas.contains(&constants::FEE_PROGRAM_ID));
783
784 assert!(metas
785 .contains(&pda::associated_token(&fee_recipient, "e_token_program, "e_mint).0));
786 assert!(metas.contains(
787 &pda::associated_token(&buyback_fee_recipient, "e_token_program, "e_mint).0
788 ));
789
790 let signers: Vec<Pubkey> = ix
791 .accounts
792 .iter()
793 .filter(|m| m.is_signer)
794 .map(|m| m.pubkey)
795 .collect();
796 assert_eq!(signers, vec![user]);
797 }
798
799 #[test]
800 fn buy_v2_instructions_prepends_four_ata_creates() {
801 let sdk = PumpSdk::new();
802 let base_mint = fake_pubkey(91);
803 let quote_mint = fake_pubkey(92);
804 let user = fake_pubkey(93);
805 let creator = fake_pubkey(94);
806 let fee_recipient = fake_pubkey(95);
807 let buyback_fee_recipient = fake_pubkey(96);
808 let base_token_program = constants::SPL_TOKEN_2022_PROGRAM_ID;
809 let quote_token_program = constants::SPL_TOKEN_PROGRAM_ID;
810 let global = pump_global_with_fees(fee_recipient, buyback_fee_recipient);
811 let bonding_curve = BondingCurve {
812 creator,
813 quote_mint,
814 ..Default::default()
815 };
816
817 let ixs = sdk
818 .buy_v2_instructions(
819 &global,
820 &bonding_curve,
821 base_mint,
822 quote_token_program,
823 user,
824 1_000,
825 5_000_000,
826 )
827 .expect("buy_v2_instructions");
828
829 assert_eq!(ixs.len(), 5);
830 for ata_ix in &ixs[..4] {
831 assert_eq!(ata_ix.program_id, constants::SPL_ATA_PROGRAM_ID);
832 assert_eq!(ata_ix.data, vec![1]);
833 }
834 assert_eq!(
835 &ixs[4].data[..8],
836 <client::args::BuyV2 as Discriminator>::DISCRIMINATOR,
837 );
838
839 let bonding_curve = pda::pump::bonding_curve(&base_mint).0;
840 let expected_atas: [Pubkey; 4] = [
841 pda::associated_token(&user, &base_token_program, &base_mint).0,
842 pda::associated_token(&bonding_curve, "e_token_program, "e_mint).0,
843 pda::associated_token(&user, "e_token_program, "e_mint).0,
844 pda::associated_token(&buyback_fee_recipient, "e_token_program, "e_mint).0,
845 ];
846 for (i, expected) in expected_atas.iter().enumerate() {
847 let metas: Vec<Pubkey> = ixs[i].accounts.iter().map(|m| m.pubkey).collect();
848 assert!(metas.contains(expected), "ix {i} missing expected ATA");
849 }
850 }
851
852 #[test]
853 #[allow(deprecated)]
854 fn sell_instruction_cashback_appends_uva_then_bonding_curve_v2() {
855 let sdk = PumpSdk::new();
856 let mint = fake_pubkey(101);
857 let user = fake_pubkey(102);
858 let creator = fake_pubkey(103);
859 let fee_recipient = fake_pubkey(104);
860 let buyback_fee_recipient = fake_pubkey(105);
861 let token_program = constants::SPL_TOKEN_PROGRAM_ID;
862
863 let ix = sdk.sell_instruction(
864 mint,
865 user,
866 creator,
867 fee_recipient,
868 buyback_fee_recipient,
869 100,
870 1_000_000,
871 token_program,
872 true,
873 );
874
875 assert_eq!(ix.program_id, crate::pump::ID);
876 assert_eq!(
877 &ix.data[..8],
878 <client::args::Sell as Discriminator>::DISCRIMINATOR,
879 );
880
881 let metas: Vec<Pubkey> = ix.accounts.iter().map(|m| m.pubkey).collect();
882 assert!(metas.contains(&pda::pump::global().0));
883 assert!(metas.contains(&pda::pump::bonding_curve(&mint).0));
884 assert!(metas.contains(&pda::pump::creator_vault(&creator).0));
885 assert!(metas.contains(&pda::pump::fee_config().0));
886 assert!(metas.contains(&constants::FEE_PROGRAM_ID));
887 assert!(metas.contains(&fee_recipient));
888 assert!(metas.contains(&pda::associated_token(&user, &token_program, &mint).0));
889
890 let n = ix.accounts.len();
891 let uva = &ix.accounts[n - 2];
892 let bcv2 = &ix.accounts[n - 1];
893 assert_eq!(uva.pubkey, pda::pump::user_volume_accumulator(&user).0);
894 assert!(uva.is_writable);
895 assert!(!uva.is_signer);
896 assert_eq!(bcv2.pubkey, pda::pump::bonding_curve_v2(&mint).0);
897 assert!(!bcv2.is_writable);
898 assert!(!bcv2.is_signer);
899 }
900
901 #[test]
902 #[allow(deprecated)]
903 fn sell_instruction_non_cashback_skips_uva() {
904 let sdk = PumpSdk::new();
905 let mint = fake_pubkey(101);
906 let user = fake_pubkey(102);
907 let creator = fake_pubkey(103);
908 let fee_recipient = fake_pubkey(104);
909 let buyback_fee_recipient = fake_pubkey(105);
910 let token_program = constants::SPL_TOKEN_PROGRAM_ID;
911
912 let ix = sdk.sell_instruction(
913 mint,
914 user,
915 creator,
916 fee_recipient,
917 buyback_fee_recipient,
918 100,
919 1_000_000,
920 token_program,
921 false,
922 );
923
924 let metas: Vec<Pubkey> = ix.accounts.iter().map(|m| m.pubkey).collect();
925 assert!(!metas.contains(&pda::pump::user_volume_accumulator(&user).0));
926
927 let bcv2 = &ix.accounts[ix.accounts.len() - 1];
928 assert_eq!(bcv2.pubkey, pda::pump::bonding_curve_v2(&mint).0);
929 assert!(!bcv2.is_writable);
930 assert!(!bcv2.is_signer);
931 }
932
933 #[test]
934 fn sell_v2_instruction_wires_quote_and_sharing_config_pdas() {
935 let sdk = PumpSdk::new();
936 let base_mint = fake_pubkey(111);
937 let quote_mint = fake_pubkey(112);
938 let user = fake_pubkey(113);
939 let creator = fake_pubkey(114);
940 let fee_recipient = fake_pubkey(115);
941 let buyback_fee_recipient = fake_pubkey(116);
942 let quote_token_program = constants::SPL_TOKEN_PROGRAM_ID;
943 let global = pump_global_with_fees(fee_recipient, buyback_fee_recipient);
944 let bonding_curve = BondingCurve {
945 creator,
946 quote_mint,
947 ..Default::default()
948 };
949
950 let ix = sdk
951 .sell_v2_instruction(
952 &global,
953 &bonding_curve,
954 base_mint,
955 quote_token_program,
956 user,
957 1_000,
958 1_000_000,
959 )
960 .expect("sell_v2_instruction");
961
962 assert_eq!(ix.program_id, crate::pump::ID);
963 assert_eq!(
964 &ix.data[..8],
965 <client::args::SellV2 as Discriminator>::DISCRIMINATOR,
966 );
967
968 let metas: Vec<Pubkey> = ix.accounts.iter().map(|m| m.pubkey).collect();
969 assert!(metas.contains(&pda::pump::global().0));
970 assert!(metas.contains(&base_mint));
971 assert!(metas.contains("e_mint));
972 assert!(metas.contains(&fee_recipient));
973 assert!(metas.contains(&buyback_fee_recipient));
974 assert!(metas.contains(&pda::pump::bonding_curve(&base_mint).0));
975 assert!(metas.contains(&pda::pump::creator_vault(&creator).0));
976 assert!(metas.contains(&pda::pump::sharing_config(&base_mint).0));
977 assert!(metas.contains(&pda::pump::user_volume_accumulator(&user).0));
978 assert!(metas.contains(&pda::pump::fee_config().0));
979 assert!(metas.contains(&constants::FEE_PROGRAM_ID));
980
981 assert!(!metas.contains(&pda::pump::global_volume_accumulator().0));
982
983 assert!(metas
984 .contains(&pda::associated_token(&fee_recipient, "e_token_program, "e_mint).0));
985 assert!(metas.contains(
986 &pda::associated_token(&buyback_fee_recipient, "e_token_program, "e_mint).0
987 ));
988
989 let signers: Vec<Pubkey> = ix
990 .accounts
991 .iter()
992 .filter(|m| m.is_signer)
993 .map(|m| m.pubkey)
994 .collect();
995 assert_eq!(signers, vec![user]);
996 }
997
998 #[test]
999 fn sell_v2_instructions_prepends_four_ata_creates() {
1000 let sdk = PumpSdk::new();
1001 let base_mint = fake_pubkey(121);
1002 let quote_mint = fake_pubkey(122);
1003 let user = fake_pubkey(123);
1004 let creator = fake_pubkey(124);
1005 let fee_recipient = fake_pubkey(125);
1006 let buyback_fee_recipient = fake_pubkey(126);
1007 let base_token_program = constants::SPL_TOKEN_2022_PROGRAM_ID;
1008 let quote_token_program = constants::SPL_TOKEN_PROGRAM_ID;
1009 let global = pump_global_with_fees(fee_recipient, buyback_fee_recipient);
1010 let bonding_curve = BondingCurve {
1011 creator,
1012 quote_mint,
1013 ..Default::default()
1014 };
1015
1016 let ixs = sdk
1017 .sell_v2_instructions(
1018 &global,
1019 &bonding_curve,
1020 base_mint,
1021 quote_token_program,
1022 user,
1023 1_000,
1024 1_000_000,
1025 )
1026 .expect("sell_v2_instructions");
1027
1028 assert_eq!(ixs.len(), 5);
1029 for ata_ix in &ixs[..4] {
1030 assert_eq!(ata_ix.program_id, constants::SPL_ATA_PROGRAM_ID);
1031 assert_eq!(ata_ix.data, vec![1]);
1032 }
1033 assert_eq!(
1034 &ixs[4].data[..8],
1035 <client::args::SellV2 as Discriminator>::DISCRIMINATOR,
1036 );
1037
1038 let bonding_curve = pda::pump::bonding_curve(&base_mint).0;
1039 let expected_atas: [Pubkey; 4] = [
1040 pda::associated_token(&user, &base_token_program, &base_mint).0,
1041 pda::associated_token(&bonding_curve, "e_token_program, "e_mint).0,
1042 pda::associated_token(&user, "e_token_program, "e_mint).0,
1043 pda::associated_token(&buyback_fee_recipient, "e_token_program, "e_mint).0,
1044 ];
1045 for (i, expected) in expected_atas.iter().enumerate() {
1046 let metas: Vec<Pubkey> = ixs[i].accounts.iter().map(|m| m.pubkey).collect();
1047 assert!(metas.contains(expected), "ix {i} missing expected ATA");
1048 }
1049 }
1050
1051 fn fake_amm_inputs() -> (
1052 Pubkey,
1053 Pubkey,
1054 Pubkey,
1055 Pubkey,
1056 Pubkey,
1057 Pubkey,
1058 Pubkey,
1059 Pubkey,
1060 ) {
1061 (
1062 fake_pubkey(0xA1),
1063 fake_pubkey(0xA2),
1064 fake_pubkey(0xA3),
1065 fake_pubkey(0xA4),
1066 fake_pubkey(0xA5),
1067 fake_pubkey(0xA6),
1068 fake_pubkey(0xA7),
1069 constants::SPL_TOKEN_PROGRAM_ID,
1070 )
1071 }
1072
1073 #[test]
1074 fn buy_amm_instruction_wires_pump_amm_pdas() {
1075 let sdk = PumpSdk::new();
1076 let (pool, base_mint, quote_mint, user, coin_creator, fee_rec, buyback, _) =
1077 fake_amm_inputs();
1078 let base_token_program = constants::SPL_TOKEN_2022_PROGRAM_ID;
1079 let quote_token_program = constants::SPL_TOKEN_PROGRAM_ID;
1080 let amm_global = amm_global_with_fees(fee_rec, buyback);
1081 let pool_state = Pool {
1082 base_mint,
1083 quote_mint,
1084 coin_creator,
1085 is_cashback_coin: true,
1086 ..Default::default()
1087 };
1088
1089 let ix = sdk
1090 .buy_amm_instruction(
1091 pool,
1092 &amm_global,
1093 &pool_state,
1094 base_token_program,
1095 quote_token_program,
1096 user,
1097 1_000,
1098 500_000,
1099 )
1100 .expect("buy_amm_instruction");
1101
1102 assert_eq!(ix.program_id, crate::pump_amm::ID);
1103 assert_eq!(
1104 &ix.data[..8],
1105 <amm_client::args::Buy as Discriminator>::DISCRIMINATOR,
1106 );
1107 assert_eq!(ix.accounts.len(), 27);
1108
1109 let pubkeys: Vec<Pubkey> = ix.accounts.iter().map(|m| m.pubkey).collect();
1110 assert!(pubkeys.contains(&pda::pump_amm::global_config().0));
1111 assert!(pubkeys.contains(&pda::pump_amm::event_authority().0));
1112 assert!(pubkeys.contains(&pda::pump_amm::global_volume_accumulator().0));
1113 assert!(pubkeys.contains(&pda::pump_amm::user_volume_accumulator(&user).0));
1114 assert!(pubkeys.contains(&pda::pump_amm::fee_config().0));
1115 assert!(pubkeys.contains(&pda::pump_amm::coin_creator_vault_authority(&coin_creator).0));
1116
1117 assert_eq!(ix.accounts[0].pubkey, pool);
1118 assert!(ix.accounts[0].is_writable);
1119 assert!(!ix.accounts[0].is_signer);
1120 assert_eq!(ix.accounts[1].pubkey, user);
1121 assert!(ix.accounts[1].is_signer);
1122
1123 let n = ix.accounts.len();
1124 let cashback = &ix.accounts[n - 4];
1125 let bcv2 = &ix.accounts[n - 3];
1126 let buyback_meta = &ix.accounts[n - 2];
1127 let buyback_ata = &ix.accounts[n - 1];
1128
1129 let user_vol_accum = pda::pump_amm::user_volume_accumulator(&user).0;
1130 assert_eq!(
1131 cashback.pubkey,
1132 pda::associated_token(
1133 &user_vol_accum,
1134 "e_token_program,
1135 &constants::NATIVE_MINT,
1136 )
1137 .0
1138 );
1139 assert!(cashback.is_writable);
1140
1141 assert_eq!(bcv2.pubkey, pda::pump_amm::pool_v2(&base_mint).0);
1142 assert!(!bcv2.is_writable);
1143
1144 assert_eq!(buyback_meta.pubkey, buyback);
1145 assert!(buyback_meta.is_writable);
1146
1147 assert_eq!(
1148 buyback_ata.pubkey,
1149 pda::associated_token(&buyback, "e_token_program, "e_mint).0
1150 );
1151 assert!(buyback_ata.is_writable);
1152 }
1153
1154 #[test]
1155 fn buy_amm_instruction_skips_remaining_when_no_cashback_no_creator() {
1156 let sdk = PumpSdk::new();
1157 let (pool, base_mint, quote_mint, user, _, fee_rec, buyback, _) = fake_amm_inputs();
1158 let amm_global = amm_global_with_fees(fee_rec, buyback);
1159 let pool_state = Pool {
1160 base_mint,
1161 quote_mint,
1162 coin_creator: Pubkey::default(),
1163 is_cashback_coin: false,
1164 ..Default::default()
1165 };
1166
1167 let ix = sdk
1168 .buy_amm_instruction(
1169 pool,
1170 &amm_global,
1171 &pool_state,
1172 constants::SPL_TOKEN_2022_PROGRAM_ID,
1173 constants::SPL_TOKEN_PROGRAM_ID,
1174 user,
1175 1_000,
1176 500_000,
1177 )
1178 .expect("buy_amm_instruction");
1179
1180 assert_eq!(ix.accounts.len(), 25);
1181 let n = ix.accounts.len();
1182 assert_eq!(ix.accounts[n - 2].pubkey, buyback);
1183 assert!(ix.accounts[n - 2].is_writable);
1184 }
1185
1186 #[test]
1187 fn sell_amm_instruction_wires_pump_amm_pdas() {
1188 let sdk = PumpSdk::new();
1189 let (pool, base_mint, quote_mint, user, coin_creator, fee_rec, buyback, _) =
1190 fake_amm_inputs();
1191 let base_token_program = constants::SPL_TOKEN_2022_PROGRAM_ID;
1192 let quote_token_program = constants::SPL_TOKEN_PROGRAM_ID;
1193 let amm_global = amm_global_with_fees(fee_rec, buyback);
1194 let pool_state = Pool {
1195 base_mint,
1196 quote_mint,
1197 coin_creator,
1198 is_cashback_coin: true,
1199 ..Default::default()
1200 };
1201
1202 let ix = sdk
1203 .sell_amm_instruction(
1204 pool,
1205 &amm_global,
1206 &pool_state,
1207 base_token_program,
1208 quote_token_program,
1209 user,
1210 1_000_000,
1211 500,
1212 )
1213 .expect("sell_amm_instruction");
1214
1215 assert_eq!(ix.program_id, crate::pump_amm::ID);
1216 assert_eq!(
1217 &ix.data[..8],
1218 <amm_client::args::Sell as Discriminator>::DISCRIMINATOR,
1219 );
1220 assert_eq!(ix.accounts.len(), 26);
1221
1222 let pubkeys: Vec<Pubkey> = ix.accounts.iter().map(|m| m.pubkey).collect();
1223 assert!(!pubkeys.contains(&pda::pump_amm::global_volume_accumulator().0));
1224
1225 let user_vol_accum = pda::pump_amm::user_volume_accumulator(&user).0;
1226 let n = ix.accounts.len();
1227 let cashback_ata = &ix.accounts[n - 5];
1228 let cashback_uva = &ix.accounts[n - 4];
1229 let bcv2 = &ix.accounts[n - 3];
1230 let buyback_meta = &ix.accounts[n - 2];
1231 let buyback_ata = &ix.accounts[n - 1];
1232
1233 assert_eq!(
1234 cashback_ata.pubkey,
1235 pda::associated_token(&user_vol_accum, "e_token_program, "e_mint).0
1236 );
1237 assert!(cashback_ata.is_writable);
1238
1239 assert_eq!(cashback_uva.pubkey, user_vol_accum);
1240 assert!(cashback_uva.is_writable);
1241
1242 assert_eq!(bcv2.pubkey, pda::pump_amm::pool_v2(&base_mint).0);
1243 assert!(!bcv2.is_writable);
1244
1245 assert_eq!(buyback_meta.pubkey, buyback);
1246 assert!(!buyback_meta.is_writable);
1247
1248 assert_eq!(
1249 buyback_ata.pubkey,
1250 pda::associated_token(&buyback, "e_token_program, "e_mint).0
1251 );
1252 assert!(buyback_ata.is_writable);
1253 }
1254
1255 #[test]
1256 fn sell_amm_instruction_skips_remaining_when_no_cashback_no_creator() {
1257 let sdk = PumpSdk::new();
1258 let (pool, base_mint, quote_mint, user, _, fee_rec, buyback, _) = fake_amm_inputs();
1259 let amm_global = amm_global_with_fees(fee_rec, buyback);
1260 let pool_state = Pool {
1261 base_mint,
1262 quote_mint,
1263 coin_creator: Pubkey::default(),
1264 is_cashback_coin: false,
1265 ..Default::default()
1266 };
1267
1268 let ix = sdk
1269 .sell_amm_instruction(
1270 pool,
1271 &amm_global,
1272 &pool_state,
1273 constants::SPL_TOKEN_2022_PROGRAM_ID,
1274 constants::SPL_TOKEN_PROGRAM_ID,
1275 user,
1276 1_000_000,
1277 500,
1278 )
1279 .expect("sell_amm_instruction");
1280
1281 assert_eq!(ix.accounts.len(), 23);
1282 let n = ix.accounts.len();
1283 assert_eq!(ix.accounts[n - 2].pubkey, buyback);
1284 assert!(!ix.accounts[n - 2].is_writable);
1285 }
1286
1287 #[test]
1288 fn buy_amm_instructions_prepends_one_ata_create() {
1289 let sdk = PumpSdk::new();
1290 let (pool, base_mint, quote_mint, user, coin_creator, fee_rec, buyback, _) =
1291 fake_amm_inputs();
1292 let amm_global = amm_global_with_fees(fee_rec, buyback);
1293 let pool_state = Pool {
1294 base_mint,
1295 quote_mint,
1296 coin_creator,
1297 is_cashback_coin: false,
1298 ..Default::default()
1299 };
1300
1301 let ixs = sdk
1302 .buy_amm_instructions(
1303 pool,
1304 &amm_global,
1305 &pool_state,
1306 constants::SPL_TOKEN_2022_PROGRAM_ID,
1307 constants::SPL_TOKEN_PROGRAM_ID,
1308 user,
1309 1_000,
1310 500_000,
1311 )
1312 .expect("buy_amm_instructions");
1313
1314 assert_eq!(ixs.len(), 2);
1315 assert_eq!(ixs[0].program_id, constants::SPL_ATA_PROGRAM_ID);
1316 assert_eq!(ixs[0].data, vec![1]);
1317 assert_eq!(ixs[1].program_id, crate::pump_amm::ID);
1318 assert_eq!(
1319 &ixs[1].data[..8],
1320 <amm_client::args::Buy as Discriminator>::DISCRIMINATOR,
1321 );
1322 }
1323
1324 #[test]
1325 fn sell_amm_instructions_prepends_one_ata_create() {
1326 let sdk = PumpSdk::new();
1327 let (pool, base_mint, quote_mint, user, coin_creator, fee_rec, buyback, _) =
1328 fake_amm_inputs();
1329 let amm_global = amm_global_with_fees(fee_rec, buyback);
1330 let pool_state = Pool {
1331 base_mint,
1332 quote_mint,
1333 coin_creator,
1334 is_cashback_coin: false,
1335 ..Default::default()
1336 };
1337
1338 let ixs = sdk
1339 .sell_amm_instructions(
1340 pool,
1341 &amm_global,
1342 &pool_state,
1343 constants::SPL_TOKEN_2022_PROGRAM_ID,
1344 constants::SPL_TOKEN_PROGRAM_ID,
1345 user,
1346 1_000_000,
1347 500,
1348 )
1349 .expect("sell_amm_instructions");
1350
1351 assert_eq!(ixs.len(), 2);
1352 assert_eq!(ixs[0].program_id, constants::SPL_ATA_PROGRAM_ID);
1353 assert_eq!(ixs[0].data, vec![1]);
1354 assert_eq!(ixs[1].program_id, crate::pump_amm::ID);
1355 assert_eq!(
1356 &ixs[1].data[..8],
1357 <amm_client::args::Sell as Discriminator>::DISCRIMINATOR,
1358 );
1359 }
1360
1361 fn user_wsol_ata(user: &Pubkey) -> Pubkey {
1362 pda::associated_token(
1363 user,
1364 &constants::SPL_TOKEN_PROGRAM_ID,
1365 &constants::NATIVE_MINT,
1366 )
1367 .0
1368 }
1369
1370 fn parse_system_transfer_lamports(ix: &solana_program::instruction::Instruction) -> u64 {
1372 assert_eq!(ix.program_id, system_program::ID);
1373 assert_eq!(ix.data.len(), 12);
1374 assert_eq!(&ix.data[..4], &2u32.to_le_bytes());
1375 u64::from_le_bytes(ix.data[4..12].try_into().unwrap())
1376 }
1377
1378 #[test]
1379 fn trade_tx_buy_bc_delegates_to_buy_v2_instructions() {
1380 let sdk = PumpSdk::new();
1381 let mint = fake_pubkey(0xB1);
1382 let user = fake_pubkey(0xB2);
1383 let creator = fake_pubkey(0xB3);
1384 let fee_recipient = fake_pubkey(0xB4);
1385 let buyback_fee_recipient = fake_pubkey(0xB5);
1386 let max_sol = 7_500_000u64;
1387 let global = pump_global_with_fees(fee_recipient, buyback_fee_recipient);
1388 let bonding_curve = BondingCurve {
1389 creator,
1390 quote_mint: constants::NATIVE_MINT,
1391 ..Default::default()
1392 };
1393
1394 let ixs = sdk
1395 .trade_tx_instructions_with_venue(TradeTxWithVenueParams {
1396 mint,
1397 base_token_program: constants::SPL_TOKEN_2022_PROGRAM_ID,
1398 quote_token_program: constants::SPL_TOKEN_PROGRAM_ID,
1399 user,
1400 is_buy: true,
1401 venue: TradeVenue::BondingCurve {
1402 global: &global,
1403 bonding_curve: &bonding_curve,
1404 },
1405 base_amount: 1_000,
1406 sol_amount_threshold: max_sol,
1407 })
1408 .expect("trade_tx_instructions");
1409
1410 assert_eq!(ixs.len(), 3);
1411 for ata_ix in &ixs[..2] {
1412 assert_eq!(ata_ix.program_id, constants::SPL_ATA_PROGRAM_ID);
1413 assert_eq!(ata_ix.data, vec![1]);
1414 }
1415 assert_eq!(ixs[2].program_id, crate::pump::ID);
1416 assert_eq!(
1417 &ixs[2].data[..8],
1418 <client::args::BuyV2 as Discriminator>::DISCRIMINATOR,
1419 );
1420
1421 let expected_atas: [Pubkey; 2] = [
1422 pda::associated_token(&user, &constants::SPL_TOKEN_2022_PROGRAM_ID, &mint).0,
1423 user_wsol_ata(&user),
1424 ];
1425 for (i, expected) in expected_atas.iter().enumerate() {
1426 let metas: Vec<Pubkey> = ixs[i].accounts.iter().map(|m| m.pubkey).collect();
1427 assert!(metas.contains(expected), "ata ix {i} missing expected ATA");
1428 }
1429 }
1430
1431 #[test]
1432 fn trade_tx_sell_bc_delegates_to_sell_v2_instructions() {
1433 let sdk = PumpSdk::new();
1434 let mint = fake_pubkey(0xC1);
1435 let user = fake_pubkey(0xC2);
1436 let creator = fake_pubkey(0xC3);
1437 let fee_recipient = fake_pubkey(0xC4);
1438 let buyback_fee_recipient = fake_pubkey(0xC5);
1439 let global = pump_global_with_fees(fee_recipient, buyback_fee_recipient);
1440 let bonding_curve = BondingCurve {
1441 creator,
1442 quote_mint: constants::NATIVE_MINT,
1443 ..Default::default()
1444 };
1445
1446 let ixs = sdk
1447 .trade_tx_instructions_with_venue(TradeTxWithVenueParams {
1448 mint,
1449 base_token_program: constants::SPL_TOKEN_2022_PROGRAM_ID,
1450 quote_token_program: constants::SPL_TOKEN_PROGRAM_ID,
1451 user,
1452 is_buy: false,
1453 venue: TradeVenue::BondingCurve {
1454 global: &global,
1455 bonding_curve: &bonding_curve,
1456 },
1457 base_amount: 1_000,
1458 sol_amount_threshold: 1_000_000,
1459 })
1460 .expect("trade_tx_instructions");
1461
1462 assert_eq!(ixs.len(), 2);
1463 assert_eq!(ixs[0].program_id, constants::SPL_ATA_PROGRAM_ID);
1464 assert_eq!(ixs[0].data, vec![1]);
1465 assert_eq!(ixs[1].program_id, crate::pump::ID);
1466 assert_eq!(
1467 &ixs[1].data[..8],
1468 <client::args::SellV2 as Discriminator>::DISCRIMINATOR,
1469 );
1470 let metas: Vec<Pubkey> = ixs[0].accounts.iter().map(|m| m.pubkey).collect();
1471 assert!(metas.contains(&user_wsol_ata(&user)));
1472 }
1473
1474 #[test]
1475 fn trade_tx_auto_matches_explicit_bc_when_not_complete() {
1476 let sdk = PumpSdk::new();
1477 let mint = fake_pubkey(0x70);
1478 let user = fake_pubkey(0x71);
1479 let creator = fake_pubkey(0x72);
1480 let fee_recipient = fake_pubkey(0x73);
1481 let buyback_fee_recipient = fake_pubkey(0x74);
1482 let max_sol = 7_500_000u64;
1483 let global = pump_global_with_fees(fee_recipient, buyback_fee_recipient);
1484 let bonding_curve = BondingCurve {
1485 creator,
1486 quote_mint: constants::NATIVE_MINT,
1487 complete: false,
1488 ..Default::default()
1489 };
1490
1491 let explicit = sdk
1492 .trade_tx_instructions_with_venue(TradeTxWithVenueParams {
1493 mint,
1494 base_token_program: constants::SPL_TOKEN_2022_PROGRAM_ID,
1495 quote_token_program: constants::SPL_TOKEN_PROGRAM_ID,
1496 user,
1497 is_buy: true,
1498 venue: TradeVenue::BondingCurve {
1499 global: &global,
1500 bonding_curve: &bonding_curve,
1501 },
1502 base_amount: 1_000,
1503 sol_amount_threshold: max_sol,
1504 })
1505 .expect("explicit bc");
1506
1507 let pool = fake_pubkey(0x75);
1508 let amm_g = amm_global_with_fees(fake_pubkey(0x76), fake_pubkey(0x77));
1509 let ps = Pool {
1510 base_mint: mint,
1511 quote_mint: constants::NATIVE_MINT,
1512 ..Default::default()
1513 };
1514 let auto = sdk
1515 .trade_tx_instructions(TradeTxParams {
1516 mint,
1517 base_token_program: constants::SPL_TOKEN_2022_PROGRAM_ID,
1518 quote_token_program: constants::SPL_TOKEN_PROGRAM_ID,
1519 user,
1520 is_buy: true,
1521 base_amount: 1_000,
1522 sol_amount_threshold: max_sol,
1523 pump_global: &global,
1524 bonding_curve: &bonding_curve,
1525 pump_pool: Some(PumpPoolCtx {
1526 pool,
1527 amm_global: &amm_g,
1528 pool_state: &ps,
1529 }),
1530 })
1531 .expect("auto bc");
1532
1533 assert_eq!(explicit.len(), auto.len());
1534 for (a, b) in explicit.iter().zip(auto.iter()) {
1535 assert_eq!(a.program_id, b.program_id);
1536 assert_eq!(a.data, b.data);
1537 }
1538 }
1539
1540 #[test]
1541 fn trade_tx_auto_returns_none_when_complete_without_graduated() {
1542 let sdk = PumpSdk::new();
1543 let global = pump_global_with_fees(fake_pubkey(0x80), fake_pubkey(0x81));
1544 let bonding_curve = BondingCurve {
1545 complete: true,
1546 ..Default::default()
1547 };
1548 assert!(
1549 sdk.trade_tx_instructions(TradeTxParams {
1550 mint: fake_pubkey(0x82),
1551 base_token_program: constants::SPL_TOKEN_2022_PROGRAM_ID,
1552 quote_token_program: constants::SPL_TOKEN_PROGRAM_ID,
1553 user: fake_pubkey(0x83),
1554 is_buy: true,
1555 base_amount: 1,
1556 sol_amount_threshold: 1,
1557 pump_global: &global,
1558 bonding_curve: &bonding_curve,
1559 pump_pool: None,
1560 })
1561 .is_none()
1562 );
1563 }
1564
1565 #[test]
1566 fn trade_tx_auto_matches_explicit_amm_when_complete() {
1567 let sdk = PumpSdk::new();
1568 let mint = fake_pubkey(0x90);
1569 let user = fake_pubkey(0x91);
1570 let coin_creator = fake_pubkey(0x92);
1571 let fee_recipient = fake_pubkey(0x93);
1572 let buyback_fee_recipient = fake_pubkey(0x94);
1573 let pool = fake_pubkey(0x95);
1574 let max_sol = 2_500_000u64;
1575 let amm_global = amm_global_with_fees(fee_recipient, buyback_fee_recipient);
1576 let pool_state = Pool {
1577 base_mint: mint,
1578 quote_mint: constants::NATIVE_MINT,
1579 coin_creator,
1580 is_cashback_coin: true,
1581 ..Default::default()
1582 };
1583 let pump_global = pump_global_with_fees(fee_recipient, buyback_fee_recipient);
1584 let bonding_curve = BondingCurve {
1585 creator: coin_creator,
1586 quote_mint: constants::NATIVE_MINT,
1587 complete: true,
1588 ..Default::default()
1589 };
1590
1591 let explicit = sdk
1592 .trade_tx_instructions_with_venue(TradeTxWithVenueParams {
1593 mint,
1594 base_token_program: constants::SPL_TOKEN_2022_PROGRAM_ID,
1595 quote_token_program: constants::SPL_TOKEN_PROGRAM_ID,
1596 user,
1597 is_buy: true,
1598 venue: TradeVenue::Amm {
1599 pool,
1600 amm_global: &amm_global,
1601 pool_state: &pool_state,
1602 },
1603 base_amount: 1_000,
1604 sol_amount_threshold: max_sol,
1605 })
1606 .expect("explicit amm");
1607
1608 let auto = sdk
1609 .trade_tx_instructions(TradeTxParams {
1610 mint,
1611 base_token_program: constants::SPL_TOKEN_2022_PROGRAM_ID,
1612 quote_token_program: constants::SPL_TOKEN_PROGRAM_ID,
1613 user,
1614 is_buy: true,
1615 base_amount: 1_000,
1616 sol_amount_threshold: max_sol,
1617 pump_global: &pump_global,
1618 bonding_curve: &bonding_curve,
1619 pump_pool: Some(PumpPoolCtx {
1620 pool,
1621 amm_global: &amm_global,
1622 pool_state: &pool_state,
1623 }),
1624 })
1625 .expect("auto amm");
1626
1627 assert_eq!(explicit.len(), auto.len());
1628 for (a, b) in explicit.iter().zip(auto.iter()) {
1629 assert_eq!(a.program_id, b.program_id);
1630 assert_eq!(a.data, b.data);
1631 }
1632 }
1633
1634 #[test]
1635 fn quote_trade_bc_matches_direct_buy_and_sell_quotes() {
1636 let sdk = PumpSdk::new();
1637 let g = fixture_global();
1638 let bc = fixture_bonding_curve(fake_pubkey(7));
1639 let supply = g.token_total_supply;
1640 let target = 100_000_000_000_000u64;
1641 let slip = 200u16;
1642
1643 let buy_direct = sdk
1644 .buy_quote_bonding_curve_token_out(&g, None, &bc, supply, target, slip)
1645 .expect("buy direct");
1646 let buy_auto = sdk
1647 .quote_trade(TradeQuoteParams {
1648 is_buy: true,
1649 base_amount: target,
1650 slippage_bps: slip,
1651 base_mint_supply: supply,
1652 pump_global: &g,
1653 pump_fee_config: None,
1654 bonding_curve: &bc,
1655 pump_pool: None,
1656 })
1657 .expect("auto routed")
1658 .expect("buy auto");
1659 assert_eq!(buy_direct, buy_auto);
1660
1661 let sell_direct = sdk
1662 .sell_quote_bonding_curve(&g, None, &bc, supply, target, slip)
1663 .expect("sell direct");
1664 let sell_auto = sdk
1665 .quote_trade(TradeQuoteParams {
1666 is_buy: false,
1667 base_amount: target,
1668 slippage_bps: slip,
1669 base_mint_supply: supply,
1670 pump_global: &g,
1671 pump_fee_config: None,
1672 bonding_curve: &bc,
1673 pump_pool: None,
1674 })
1675 .expect("auto routed")
1676 .expect("sell auto");
1677 assert_eq!(sell_direct, sell_auto);
1678 }
1679
1680 #[test]
1681 fn quote_trade_amm_matches_direct_buy_and_sell_quotes() {
1682 let sdk = PumpSdk::new();
1683 let g = fixture_global();
1684 let gc = fixture_amm_global_config();
1685 let pool_state = fixture_pool(fake_pubkey(0xCC));
1686 let mut bc = fixture_bonding_curve(fake_pubkey(7));
1687 bc.complete = true;
1688
1689 let base_reserve = 200_000_000_000_000u64;
1690 let quote_reserve = 30_000_000_000u64;
1691 let base_mint_supply = 1_000_000_000_000_000u64;
1692 let target = 1_000_000_000_000u64;
1693 let slip = 100u16;
1694
1695 let source = AmmQuoteSource::Pool {
1696 pool: &pool_state,
1697 base_reserve,
1698 quote_reserve,
1699 base_mint_supply,
1700 };
1701 let buy_direct = sdk
1702 .buy_quote_amm_token_out(&gc, None, source, target, slip)
1703 .expect("buy direct");
1704 let buy_auto = sdk
1705 .quote_trade(TradeQuoteParams {
1706 is_buy: true,
1707 base_amount: target,
1708 slippage_bps: slip,
1709 base_mint_supply,
1710 pump_global: &g,
1711 pump_fee_config: None,
1712 bonding_curve: &bc,
1713 pump_pool: Some(PumpPoolQuoteCtx {
1714 amm_global: &gc,
1715 amm_fee_config: None,
1716 pool_state: &pool_state,
1717 base_reserve,
1718 quote_reserve,
1719 }),
1720 })
1721 .expect("auto routed")
1722 .expect("buy auto");
1723 assert_eq!(buy_direct, buy_auto);
1724
1725 let sell_source = AmmQuoteSource::Pool {
1726 pool: &pool_state,
1727 base_reserve,
1728 quote_reserve,
1729 base_mint_supply,
1730 };
1731 let sell_direct = sdk
1732 .sell_quote_amm(&gc, None, sell_source, target, slip)
1733 .expect("sell direct");
1734 let sell_auto = sdk
1735 .quote_trade(TradeQuoteParams {
1736 is_buy: false,
1737 base_amount: target,
1738 slippage_bps: slip,
1739 base_mint_supply,
1740 pump_global: &g,
1741 pump_fee_config: None,
1742 bonding_curve: &bc,
1743 pump_pool: Some(PumpPoolQuoteCtx {
1744 amm_global: &gc,
1745 amm_fee_config: None,
1746 pool_state: &pool_state,
1747 base_reserve,
1748 quote_reserve,
1749 }),
1750 })
1751 .expect("auto routed")
1752 .expect("sell auto");
1753 assert_eq!(sell_direct, sell_auto);
1754 }
1755
1756 #[test]
1757 fn quote_trade_returns_none_when_complete_without_pump_pool() {
1758 let sdk = PumpSdk::new();
1759 let g = fixture_global();
1760 let mut bc = fixture_bonding_curve(fake_pubkey(7));
1761 bc.complete = true;
1762 assert!(sdk
1763 .quote_trade(TradeQuoteParams {
1764 is_buy: true,
1765 base_amount: 1,
1766 slippage_bps: 0,
1767 base_mint_supply: g.token_total_supply,
1768 pump_global: &g,
1769 pump_fee_config: None,
1770 bonding_curve: &bc,
1771 pump_pool: None,
1772 })
1773 .is_none());
1774 }
1775
1776 #[test]
1777 fn trade_tx_buy_amm_wraps_and_unwraps_with_cashback() {
1778 let sdk = PumpSdk::new();
1779 let mint = fake_pubkey(0xD1);
1780 let user = fake_pubkey(0xD2);
1781 let coin_creator = fake_pubkey(0xD3);
1782 let fee_recipient = fake_pubkey(0xD4);
1783 let buyback_fee_recipient = fake_pubkey(0xD5);
1784 let pool = fake_pubkey(0xD6);
1785 let max_sol = 2_500_000u64;
1786 let amm_global = amm_global_with_fees(fee_recipient, buyback_fee_recipient);
1787 let pool_state = Pool {
1788 base_mint: mint,
1789 quote_mint: constants::NATIVE_MINT,
1790 coin_creator,
1791 is_cashback_coin: true,
1792 ..Default::default()
1793 };
1794
1795 let ixs = sdk
1796 .trade_tx_instructions_with_venue(TradeTxWithVenueParams {
1797 mint,
1798 base_token_program: constants::SPL_TOKEN_2022_PROGRAM_ID,
1799 quote_token_program: constants::SPL_TOKEN_PROGRAM_ID,
1800 user,
1801 is_buy: true,
1802 venue: TradeVenue::Amm {
1803 pool,
1804 amm_global: &amm_global,
1805 pool_state: &pool_state,
1806 },
1807 base_amount: 1_000,
1808 sol_amount_threshold: max_sol,
1809 })
1810 .expect("trade_tx_instructions");
1811
1812 assert_eq!(ixs.len(), 6);
1813 assert_eq!(ixs[0].program_id, constants::SPL_ATA_PROGRAM_ID);
1814 assert_eq!(ixs[1].program_id, constants::SPL_ATA_PROGRAM_ID);
1815 assert_eq!(parse_system_transfer_lamports(&ixs[2]), max_sol);
1816 assert_eq!(ixs[3].program_id, constants::SPL_TOKEN_PROGRAM_ID);
1817 assert_eq!(ixs[4].program_id, crate::pump_amm::ID);
1818 assert_eq!(
1819 &ixs[4].data[..8],
1820 <amm_client::args::Buy as Discriminator>::DISCRIMINATOR,
1821 );
1822 assert_eq!(ixs[5].program_id, constants::SPL_TOKEN_PROGRAM_ID);
1823
1824 let pubkeys: Vec<Pubkey> = ixs[4].accounts.iter().map(|m| m.pubkey).collect();
1825 let user_vol_accum = pda::pump_amm::user_volume_accumulator(&user).0;
1826 assert!(pubkeys.contains(
1827 &pda::associated_token(
1828 &user_vol_accum,
1829 &constants::SPL_TOKEN_PROGRAM_ID,
1830 &constants::NATIVE_MINT,
1831 )
1832 .0
1833 ));
1834 assert!(pubkeys.contains(&pda::pump_amm::pool_v2(&mint).0));
1835 }
1836
1837 #[test]
1838 fn trade_tx_sell_amm_unwraps_proceeds_no_wrap() {
1839 let sdk = PumpSdk::new();
1840 let mint = fake_pubkey(0xE1);
1841 let user = fake_pubkey(0xE2);
1842 let coin_creator = fake_pubkey(0xE3);
1843 let fee_recipient = fake_pubkey(0xE4);
1844 let buyback_fee_recipient = fake_pubkey(0xE5);
1845 let pool = fake_pubkey(0xE6);
1846 let amm_global = amm_global_with_fees(fee_recipient, buyback_fee_recipient);
1847 let pool_state = Pool {
1848 base_mint: mint,
1849 quote_mint: constants::NATIVE_MINT,
1850 coin_creator,
1851 is_cashback_coin: false,
1852 ..Default::default()
1853 };
1854
1855 let ixs = sdk
1856 .trade_tx_instructions_with_venue(TradeTxWithVenueParams {
1857 mint,
1858 base_token_program: constants::SPL_TOKEN_2022_PROGRAM_ID,
1859 quote_token_program: constants::SPL_TOKEN_PROGRAM_ID,
1860 user,
1861 is_buy: false,
1862 venue: TradeVenue::Amm {
1863 pool,
1864 amm_global: &amm_global,
1865 pool_state: &pool_state,
1866 },
1867 base_amount: 5_000,
1868 sol_amount_threshold: 100,
1869 })
1870 .expect("trade_tx_instructions");
1871
1872 assert_eq!(ixs.len(), 3);
1873 assert_eq!(ixs[0].program_id, constants::SPL_ATA_PROGRAM_ID);
1874 let wsol_ata = user_wsol_ata(&user);
1875 assert!(ixs[0].accounts.iter().any(|m| m.pubkey == wsol_ata));
1876 assert_eq!(ixs[1].program_id, crate::pump_amm::ID);
1877 assert_eq!(
1878 &ixs[1].data[..8],
1879 <amm_client::args::Sell as Discriminator>::DISCRIMINATOR,
1880 );
1881 assert_eq!(ixs[2].program_id, constants::SPL_TOKEN_PROGRAM_ID);
1882 assert!(ixs[2].accounts.iter().any(|m| m.pubkey == wsol_ata));
1883 }
1884
1885 #[test]
1886 fn create_coin_instructions_emits_create_then_buy_v2() {
1887 let sdk = PumpSdk::new();
1888 let mint = fake_pubkey(0xF1);
1889 let user = fake_pubkey(0xF2);
1890 let creator = fake_pubkey(0xF3);
1891 let fee_recipient = fake_pubkey(0xF4);
1892 let buyback_fee_recipient = fake_pubkey(0xF5);
1893 let max_sol = 1_234_567u64;
1894 let global = pump_global_with_fees(fee_recipient, buyback_fee_recipient);
1895
1896 let ixs = sdk
1897 .create_coin_instructions(CreateCoinParams {
1898 mint,
1899 user,
1900 creator,
1901 name: "Test".into(),
1902 symbol: "TST".into(),
1903 uri: "https://example.com/metadata.json".into(),
1904 mayhem_mode: false,
1905 cashback: false,
1906 global: &global,
1907 token_amount: 1_000,
1908 max_sol_cost: max_sol,
1909 tokenized_agent_buyback_bps: None,
1910 })
1911 .expect("create_coin_instructions");
1912
1913 assert_eq!(ixs.len(), 4);
1914 assert_eq!(
1915 &ixs[0].data[..8],
1916 <client::args::CreateV2 as Discriminator>::DISCRIMINATOR,
1917 );
1918 for ata_ix in &ixs[1..3] {
1919 assert_eq!(ata_ix.program_id, constants::SPL_ATA_PROGRAM_ID);
1920 assert_eq!(ata_ix.data, vec![1]);
1921 }
1922 assert_eq!(ixs[3].program_id, crate::pump::ID);
1923 assert_eq!(
1924 &ixs[3].data[..8],
1925 <client::args::BuyV2 as Discriminator>::DISCRIMINATOR,
1926 );
1927 let buy_metas: Vec<Pubkey> = ixs[3].accounts.iter().map(|m| m.pubkey).collect();
1928 assert!(buy_metas.contains(&constants::SPL_TOKEN_2022_PROGRAM_ID));
1929 assert!(buy_metas.contains(&constants::NATIVE_MINT));
1930 assert!(buy_metas.contains(&pda::pump::sharing_config(&mint).0));
1931 }
1932
1933 #[test]
1934 fn create_coin_instructions_appends_agent_initialize_when_some() {
1935 let sdk = PumpSdk::new();
1936 let mint = fake_pubkey(0xA1);
1937 let user = fake_pubkey(0xA2);
1938 let creator = fake_pubkey(0xA3);
1939 let fee_recipient = fake_pubkey(0xA4);
1940 let buyback_fee_recipient = fake_pubkey(0xA5);
1941 let global = pump_global_with_fees(fee_recipient, buyback_fee_recipient);
1942
1943 let buyback_bps = 5000u16;
1944 let with_agent = sdk
1945 .create_coin_instructions(CreateCoinParams {
1946 mint,
1947 user,
1948 creator,
1949 name: "Test".into(),
1950 symbol: "TST".into(),
1951 uri: "https://example.com/metadata.json".into(),
1952 mayhem_mode: false,
1953 cashback: false,
1954 global: &global,
1955 token_amount: 1_000,
1956 max_sol_cost: 500_000,
1957 tokenized_agent_buyback_bps: Some(buyback_bps),
1958 })
1959 .expect("create_coin_instructions Some");
1960 let without_agent = sdk
1961 .create_coin_instructions(CreateCoinParams {
1962 mint,
1963 user,
1964 creator,
1965 name: "Test".into(),
1966 symbol: "TST".into(),
1967 uri: "https://example.com/metadata.json".into(),
1968 mayhem_mode: false,
1969 cashback: false,
1970 global: &global,
1971 token_amount: 1_000,
1972 max_sol_cost: 500_000,
1973 tokenized_agent_buyback_bps: None,
1974 })
1975 .expect("create_coin_instructions None");
1976
1977 assert_eq!(
1978 with_agent.len(),
1979 without_agent.len() + 1,
1980 "Some(bps) appends exactly one extra ix"
1981 );
1982 let agent_ix = with_agent.last().expect("agent ix present");
1983 assert_eq!(agent_ix.program_id, crate::pump_agent_payments::ID);
1984 assert_eq!(
1985 &agent_ix.data[..8],
1986 <agent_client::args::AgentInitialize as Discriminator>::DISCRIMINATOR,
1987 );
1988 assert_eq!(&agent_ix.data[8..40], creator.as_ref());
1990 assert_eq!(
1991 u16::from_le_bytes([agent_ix.data[40], agent_ix.data[41]]),
1992 buyback_bps,
1993 );
1994 let agent_metas: Vec<Pubkey> = agent_ix.accounts.iter().map(|m| m.pubkey).collect();
1995 assert!(agent_metas.contains(&user), "user is the signer");
1996 assert!(agent_metas.contains(&pda::pump::bonding_curve(&mint).0));
1997 assert!(agent_metas.contains(&pda::pump_agent_payments::global_config().0));
1998 assert!(agent_metas.contains(&mint));
1999 assert!(agent_metas.contains(&pda::pump_agent_payments::token_agent_payments(&mint).0));
2000 assert!(agent_metas.contains(&pda::pump_agent_payments::event_authority().0));
2001 assert!(agent_metas.contains(&crate::pump_agent_payments::ID));
2002 assert!(agent_metas.contains(&pda::pump::sharing_config(&mint).0));
2003 }
2004
2005 #[test]
2006 fn program_ids_match_idl() {
2007 assert_eq!(
2008 crate::pump::ID,
2009 pubkey!("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P")
2010 );
2011 assert_eq!(
2012 crate::pump_amm::ID,
2013 pubkey!("pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA")
2014 );
2015 }
2016
2017 fn fixture_global() -> Global {
2018 Global {
2019 fee_basis_points: 100,
2020 creator_fee_basis_points: 50,
2021 token_total_supply: 1_000_000_000_000_000,
2022 initial_real_token_reserves: 793_100_000_000_000,
2023 pool_migration_fee: 6_900_000_000,
2024 ..Default::default()
2025 }
2026 }
2027
2028 fn fixture_bonding_curve(creator: Pubkey) -> BondingCurve {
2029 BondingCurve {
2030 virtual_token_reserves: 1_073_000_000_000_000,
2031 virtual_quote_reserves: 30_000_000_000,
2032 real_token_reserves: 793_100_000_000_000,
2033 real_quote_reserves: 0,
2034 token_total_supply: 1_000_000_000_000_000,
2035 complete: false,
2036 creator,
2037 is_mayhem_mode: false,
2038 is_cashback_coin: false,
2039 quote_mint: constants::NATIVE_MINT,
2040 }
2041 }
2042
2043 fn fixture_amm_global_config() -> GlobalConfig {
2044 GlobalConfig {
2045 lp_fee_basis_points: 20,
2046 protocol_fee_basis_points: 5,
2047 coin_creator_fee_basis_points: 5,
2048 ..Default::default()
2049 }
2050 }
2051
2052 fn fixture_pool(coin_creator: Pubkey) -> Pool {
2053 Pool {
2054 base_mint: fake_pubkey(0xAA),
2055 creator: fake_pubkey(0xBB),
2056 coin_creator,
2057 quote_mint: constants::NATIVE_MINT,
2058 ..Default::default()
2059 }
2060 }
2061
2062 #[test]
2063 fn bc_buy_sol_in_matches_ts() {
2064 let sdk = PumpSdk::new();
2065 let g = fixture_global();
2066 let bc = fixture_bonding_curve(fake_pubkey(7));
2067 let q = sdk
2068 .buy_quote_bonding_curve_sol_in(
2069 &g,
2070 None,
2071 &bc,
2072 g.token_total_supply,
2073 1_000_000_000, 100, )
2076 .unwrap();
2077 assert_eq!(q.amount, 34_117_646_995_895);
2078 assert_eq!(q.min_out, 33_776_470_525_936);
2079 assert_eq!(q.input_amount_used, 1_000_000_000);
2080 assert_eq!(q.max_input, 1_000_000_000);
2081 }
2082
2083 #[test]
2084 fn bc_buy_token_out_matches_ts() {
2085 let sdk = PumpSdk::new();
2086 let g = fixture_global();
2087 let bc = fixture_bonding_curve(fake_pubkey(7));
2088 let q = sdk
2089 .buy_quote_bonding_curve_token_out(
2090 &g,
2091 None,
2092 &bc,
2093 g.token_total_supply,
2094 100_000_000_000_000,
2095 200, )
2097 .unwrap();
2098 assert_eq!(q.amount, 3_129_496_404);
2099 assert_eq!(q.max_input, 3_192_086_332);
2100 assert_eq!(q.min_out, 100_000_000_000_000);
2101 assert_eq!(q.input_amount_used, 100_000_000_000_000);
2102 }
2103
2104 #[test]
2105 fn bc_sell_with_creator_matches_ts() {
2106 let sdk = PumpSdk::new();
2107 let g = fixture_global();
2108 let bc = fixture_bonding_curve(fake_pubkey(7));
2109 let q = sdk
2110 .sell_quote_bonding_curve(
2111 &g,
2112 None,
2113 &bc,
2114 g.token_total_supply,
2115 50_000_000_000_000,
2116 50, )
2118 .unwrap();
2119 assert_eq!(q.amount, 1_315_672_305);
2120 assert_eq!(q.min_out, 1_309_093_943);
2121 }
2122
2123 #[test]
2124 fn bc_sell_default_creator_skips_creator_fee() {
2125 let sdk = PumpSdk::new();
2126 let g = fixture_global();
2127 let bc = fixture_bonding_curve(Pubkey::default());
2128 let q = sdk
2129 .sell_quote_bonding_curve(&g, None, &bc, g.token_total_supply, 50_000_000_000_000, 0)
2130 .unwrap();
2131 assert_eq!(q.amount, 1_322_350_845);
2132 }
2133
2134 #[test]
2135 fn amm_buy_sol_in_matches_ts() {
2136 let sdk = PumpSdk::new();
2137 let gc = fixture_amm_global_config();
2138 let pool = fixture_pool(fake_pubkey(0xCC));
2139 let q = sdk
2140 .buy_quote_amm_sol_in(
2141 &gc,
2142 None,
2143 AmmQuoteSource::Pool {
2144 pool: &pool,
2145 base_reserve: 200_000_000_000_000,
2146 quote_reserve: 30_000_000_000,
2147 base_mint_supply: 1_000_000_000_000_000,
2148 },
2149 1_000_000_000,
2150 100,
2151 )
2152 .unwrap();
2153 assert_eq!(q.amount, 6_432_936_635_069);
2154 assert_eq!(q.min_out, 6_368_607_268_718);
2155 assert_eq!(q.max_input, 1_010_000_000);
2156 }
2157
2158 #[test]
2159 fn amm_buy_token_out_matches_ts() {
2160 let sdk = PumpSdk::new();
2161 let gc = fixture_amm_global_config();
2162 let pool = fixture_pool(fake_pubkey(0xCC));
2163 let q = sdk
2164 .buy_quote_amm_token_out(
2165 &gc,
2166 None,
2167 AmmQuoteSource::Pool {
2168 pool: &pool,
2169 base_reserve: 200_000_000_000_000,
2170 quote_reserve: 30_000_000_000,
2171 base_mint_supply: 1_000_000_000_000_000,
2172 },
2173 1_000_000_000_000,
2174 100,
2175 )
2176 .unwrap();
2177 assert_eq!(q.amount, 151_206_031);
2178 assert_eq!(q.max_input, 152_718_091);
2179 }
2180
2181 #[test]
2182 fn amm_sell_with_coin_creator_matches_ts() {
2183 let sdk = PumpSdk::new();
2184 let gc = fixture_amm_global_config();
2185 let pool = fixture_pool(fake_pubkey(0xCC));
2186 let q = sdk
2187 .sell_quote_amm(
2188 &gc,
2189 None,
2190 AmmQuoteSource::Pool {
2191 pool: &pool,
2192 base_reserve: 200_000_000_000_000,
2193 quote_reserve: 30_000_000_000,
2194 base_mint_supply: 1_000_000_000_000_000,
2195 },
2196 1_000_000_000_000,
2197 100,
2198 )
2199 .unwrap();
2200 assert_eq!(q.amount, 148_805_969);
2201 assert_eq!(q.min_out, 147_317_909);
2202 }
2203
2204 #[test]
2205 fn amm_sell_default_coin_creator_skips_creator_fee() {
2206 let sdk = PumpSdk::new();
2207 let gc = fixture_amm_global_config();
2208 let pool = fixture_pool(Pubkey::default());
2209 let q = sdk
2210 .sell_quote_amm(
2211 &gc,
2212 None,
2213 AmmQuoteSource::Pool {
2214 pool: &pool,
2215 base_reserve: 200_000_000_000_000,
2216 quote_reserve: 30_000_000_000,
2217 base_mint_supply: 1_000_000_000_000_000,
2218 },
2219 1_000_000_000_000,
2220 0,
2221 )
2222 .unwrap();
2223 assert_eq!(q.amount, 148_880_596);
2224 }
2225
2226 #[test]
2227 fn zero_input_returns_zero() {
2228 let sdk = PumpSdk::new();
2229 let g = fixture_global();
2230 let bc = fixture_bonding_curve(fake_pubkey(7));
2231 let q = sdk
2232 .buy_quote_bonding_curve_sol_in(&g, None, &bc, g.token_total_supply, 0, 100)
2233 .unwrap();
2234 assert_eq!(q.amount, 0);
2235 let q = sdk
2236 .sell_quote_bonding_curve(&g, None, &bc, g.token_total_supply, 0, 100)
2237 .unwrap();
2238 assert_eq!(q.amount, 0);
2239 }
2240
2241 #[test]
2242 fn migrated_curve_returns_zero() {
2243 let sdk = PumpSdk::new();
2244 let g = fixture_global();
2245 let mut bc = fixture_bonding_curve(fake_pubkey(7));
2246 bc.virtual_token_reserves = 0;
2247 let q = sdk
2248 .buy_quote_bonding_curve_sol_in(&g, None, &bc, g.token_total_supply, 1_000_000_000, 0)
2249 .unwrap();
2250 assert_eq!(q.amount, 0);
2251 }
2252
2253 #[test]
2254 fn amm_empty_reserves_errors() {
2255 let sdk = PumpSdk::new();
2256 let gc = fixture_amm_global_config();
2257 let pool = fixture_pool(fake_pubkey(0xCC));
2258 let err = sdk
2259 .buy_quote_amm_sol_in(
2260 &gc,
2261 None,
2262 AmmQuoteSource::Pool {
2263 pool: &pool,
2264 base_reserve: 0,
2265 quote_reserve: 1_000,
2266 base_mint_supply: 1_000,
2267 },
2268 1,
2269 0,
2270 )
2271 .unwrap_err();
2272 assert_eq!(err, QuoteError::EmptyReserves);
2273 let err = sdk
2274 .sell_quote_amm(
2275 &gc,
2276 None,
2277 AmmQuoteSource::Pool {
2278 pool: &pool,
2279 base_reserve: 1_000,
2280 quote_reserve: 0,
2281 base_mint_supply: 1_000,
2282 },
2283 1,
2284 0,
2285 )
2286 .unwrap_err();
2287 assert_eq!(err, QuoteError::EmptyReserves);
2288 }
2289
2290 #[test]
2291 fn amm_buy_token_out_oversize_errors() {
2292 let sdk = PumpSdk::new();
2293 let gc = fixture_amm_global_config();
2294 let pool = fixture_pool(fake_pubkey(0xCC));
2295 let err = sdk
2296 .buy_quote_amm_token_out(
2297 &gc,
2298 None,
2299 AmmQuoteSource::Pool {
2300 pool: &pool,
2301 base_reserve: 1_000,
2302 quote_reserve: 1_000,
2303 base_mint_supply: 1_000_000,
2304 },
2305 1_000,
2306 0,
2307 )
2308 .unwrap_err();
2309 assert_eq!(err, QuoteError::BaseOutExceedsReserve);
2310 let err = sdk
2311 .buy_quote_amm_token_out(
2312 &gc,
2313 None,
2314 AmmQuoteSource::Pool {
2315 pool: &pool,
2316 base_reserve: 1_000,
2317 quote_reserve: 1_000,
2318 base_mint_supply: 1_000_000,
2319 },
2320 5_000,
2321 0,
2322 )
2323 .unwrap_err();
2324 assert_eq!(err, QuoteError::BaseOutExceedsReserve);
2325 }
2326
2327 #[test]
2328 fn bc_token_out_buy_at_real_reserves_errors() {
2329 let sdk = PumpSdk::new();
2330 let g = fixture_global();
2331 let mut bc = fixture_bonding_curve(fake_pubkey(7));
2332 bc.real_token_reserves = bc.virtual_token_reserves;
2333 let err = sdk
2334 .buy_quote_bonding_curve_token_out(
2335 &g,
2336 None,
2337 &bc,
2338 g.token_total_supply,
2339 bc.virtual_token_reserves,
2340 0,
2341 )
2342 .unwrap_err();
2343 assert_eq!(err, QuoteError::DepletedBondingCurve);
2344 }
2345
2346 #[test]
2347 fn amm_quote_bonding_curve_complete_underflow_errors() {
2348 let sdk = PumpSdk::new();
2349 let g = fixture_global();
2350 let gc = fixture_amm_global_config();
2351 let bc = fixture_bonding_curve(fake_pubkey(7)); let base_mint = fake_pubkey(0xAA);
2353 let err = sdk
2354 .buy_quote_amm_sol_in(
2355 &gc,
2356 None,
2357 AmmQuoteSource::BondingCurveComplete {
2358 global: &g,
2359 bonding_curve: &bc,
2360 base_mint: &base_mint,
2361 base_mint_supply: g.token_total_supply,
2362 },
2363 1_000_000_000,
2364 0,
2365 )
2366 .unwrap_err();
2367 assert_eq!(err, QuoteError::EmptyReserves);
2368 }
2369
2370 #[test]
2371 fn amm_quote_bonding_curve_complete_matches_formula() {
2372 let sdk = PumpSdk::new();
2373 let g = fixture_global();
2374 let gc = fixture_amm_global_config();
2375 let mut bc = fixture_bonding_curve(fake_pubkey(7));
2376 bc.real_quote_reserves = 100_000_000_000;
2377 let base_mint = fake_pubkey(0xAA);
2378
2379 let q = sdk
2380 .buy_quote_amm_sol_in(
2381 &gc,
2382 None,
2383 AmmQuoteSource::BondingCurveComplete {
2384 global: &g,
2385 bonding_curve: &bc,
2386 base_mint: &base_mint,
2387 base_mint_supply: g.token_total_supply,
2388 },
2389 1_000_000_000,
2390 0,
2391 )
2392 .unwrap();
2393
2394 let base_reserve = g.token_total_supply - g.initial_real_token_reserves;
2395 let quote_reserve = bc.real_quote_reserves - g.pool_migration_fee;
2396 let pool_creator = pda::pump::pool_authority(&base_mint).0;
2397 let ctx = AmmContext {
2398 global_config: &gc,
2399 fee_config: None,
2400 base_mint: &base_mint,
2401 pool_creator: &pool_creator,
2402 coin_creator: &bc.creator,
2403 base_reserve,
2404 quote_reserve,
2405 base_mint_supply: g.token_total_supply,
2406 };
2407 let expected = amm_math::buy_quote_input(&ctx, 1_000_000_000)
2408 .unwrap()
2409 .base_amount_out;
2410 assert_eq!(q.amount, expected);
2411 }
2412}