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