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