1use super::program_ids;
6use super::utils::*;
7use crate::core::events::*;
8use solana_sdk::{pubkey::Pubkey, signature::Signature};
9
10pub mod discriminators {
12 pub const BUY: [u8; 8] = [102, 6, 61, 18, 1, 218, 235, 234];
14 pub const SELL: [u8; 8] = [51, 230, 133, 164, 1, 127, 131, 173];
16 pub const CREATE: [u8; 8] = [24, 30, 200, 40, 5, 28, 7, 119];
18 pub const CREATE_V2: [u8; 8] = [214, 144, 76, 236, 95, 139, 49, 180];
20 pub const BUY_EXACT_SOL_IN: [u8; 8] = [56, 252, 116, 8, 158, 223, 205, 95];
22 pub const MIGRATE_EVENT_LOG: [u8; 8] = [189, 233, 93, 185, 92, 148, 234, 148];
24 pub const MIGRATE_BONDING_CURVE_CREATOR: [u8; 8] = [87, 124, 52, 191, 52, 38, 214, 232];
26 pub const BUY_V2: [u8; 8] = [184, 23, 238, 97, 103, 197, 211, 61];
28 pub const SELL_V2: [u8; 8] = [93, 246, 130, 60, 231, 233, 64, 178];
30 pub const BUY_EXACT_QUOTE_IN_V2: [u8; 8] = [194, 171, 28, 70, 104, 77, 91, 47];
32}
33
34pub const PROGRAM_ID_PUBKEY: Pubkey = program_ids::PUMPFUN_PROGRAM_ID;
36
37pub fn parse_instruction(
42 instruction_data: &[u8],
43 accounts: &[Pubkey],
44 signature: Signature,
45 slot: u64,
46 tx_index: u64,
47 block_time_us: Option<i64>,
48 grpc_recv_us: i64,
49) -> Option<DexEvent> {
50 if instruction_data.len() < 8 {
51 return None;
52 }
53 let outer_disc: [u8; 8] = instruction_data[0..8].try_into().ok()?;
54 let data = &instruction_data[8..];
55
56 if outer_disc == discriminators::CREATE_V2 {
58 return parse_create_v2_instruction(
59 data,
60 accounts,
61 signature,
62 slot,
63 tx_index,
64 block_time_us,
65 grpc_recv_us,
66 );
67 }
68 if outer_disc == discriminators::CREATE {
69 return parse_create_instruction(
70 data,
71 accounts,
72 signature,
73 slot,
74 tx_index,
75 block_time_us,
76 grpc_recv_us,
77 );
78 }
79 if outer_disc == discriminators::BUY {
80 return parse_buy_instruction(
81 data,
82 accounts,
83 signature,
84 slot,
85 tx_index,
86 block_time_us,
87 grpc_recv_us,
88 "buy",
89 false,
90 );
91 }
92 if outer_disc == discriminators::BUY_EXACT_SOL_IN {
93 return parse_buy_instruction(
94 data,
95 accounts,
96 signature,
97 slot,
98 tx_index,
99 block_time_us,
100 grpc_recv_us,
101 "buy_exact_sol_in",
102 true,
103 );
104 }
105 if outer_disc == discriminators::SELL {
106 return parse_sell_instruction(
107 data,
108 accounts,
109 signature,
110 slot,
111 tx_index,
112 block_time_us,
113 grpc_recv_us,
114 "sell",
115 false,
116 );
117 }
118 if outer_disc == discriminators::BUY_V2 {
119 return parse_buy_v2_instruction(
120 data,
121 accounts,
122 signature,
123 slot,
124 tx_index,
125 block_time_us,
126 grpc_recv_us,
127 "buy_v2",
128 false,
129 );
130 }
131 if outer_disc == discriminators::BUY_EXACT_QUOTE_IN_V2 {
132 return parse_buy_v2_instruction(
133 data,
134 accounts,
135 signature,
136 slot,
137 tx_index,
138 block_time_us,
139 grpc_recv_us,
140 "buy_exact_quote_in_v2",
141 true,
142 );
143 }
144 if outer_disc == discriminators::SELL_V2 {
145 return parse_sell_v2_instruction(
146 data,
147 accounts,
148 signature,
149 slot,
150 tx_index,
151 block_time_us,
152 grpc_recv_us,
153 "sell_v2",
154 );
155 }
156
157 if instruction_data.len() >= 16 {
159 let cpi_disc: [u8; 8] = instruction_data[8..16].try_into().ok()?;
160 if cpi_disc == discriminators::MIGRATE_EVENT_LOG {
161 return parse_migrate_log_instruction(
162 &instruction_data[16..],
163 accounts,
164 signature,
165 slot,
166 tx_index,
167 block_time_us,
168 grpc_recv_us,
169 );
170 }
171 }
172 None
173}
174
175fn parse_buy_instruction(
185 data: &[u8],
186 accounts: &[Pubkey],
187 signature: Signature,
188 slot: u64,
189 tx_index: u64,
190 block_time_us: Option<i64>,
191 grpc_recv_us: i64,
192 ix_name: &'static str,
193 exact_quote_in: bool,
194) -> Option<DexEvent> {
195 const LEGACY_BUY_ACCOUNTS: usize = 16;
196 if accounts.len() < LEGACY_BUY_ACCOUNTS {
197 return None;
198 }
199
200 let (first_arg, second_arg) = if data.len() >= 16 {
202 (read_u64_le(data, 0).unwrap_or(0), read_u64_le(data, 8).unwrap_or(0))
203 } else {
204 (0, 0)
205 };
206 let track_volume = data.get(16).copied().map(|b| b != 0).unwrap_or(false);
207 let (
208 token_amount,
209 sol_amount,
210 amount,
211 max_sol_cost,
212 spendable_sol_in,
213 spendable_quote_in,
214 min_tokens_out,
215 ) = if exact_quote_in {
216 (second_arg, first_arg, second_arg, first_arg, first_arg, 0, second_arg)
217 } else {
218 (first_arg, second_arg, first_arg, second_arg, 0, 0, 0)
219 };
220 let bonding_curve_v2 = get_account(accounts, 16).unwrap_or_default();
221 let buyback_fee_recipient = get_account(accounts, 17).unwrap_or_default();
222 let account =
223 if buyback_fee_recipient != Pubkey::default() { Some(buyback_fee_recipient) } else { None };
224 let fee_program = get_account(accounts, 15).unwrap_or_default();
225 let mint = get_account(accounts, 2)?;
226 let metadata =
227 create_metadata(signature, slot, tx_index, block_time_us.unwrap_or_default(), grpc_recv_us);
228
229 let trade_event = PumpFunTradeEvent {
230 metadata,
231 mint,
232 is_buy: true,
233 global: get_account(accounts, 0).unwrap_or_default(),
234 fee_recipient: get_account(accounts, 1).unwrap_or_default(),
235 bonding_curve: get_account(accounts, 3).unwrap_or_default(),
236 bonding_curve_v2,
237 associated_bonding_curve: get_account(accounts, 4).unwrap_or_default(),
238 associated_user: get_account(accounts, 5).unwrap_or_default(),
239 user: get_account(accounts, 6).unwrap_or_default(),
240 system_program: get_account(accounts, 7).unwrap_or_default(),
241 token_program: get_account(accounts, 8).unwrap_or_default(),
242 creator_vault: get_account(accounts, 9).unwrap_or_default(),
243 event_authority: get_account(accounts, 10).unwrap_or_default(),
244 program: get_account(accounts, 11).unwrap_or_default(),
245 global_volume_accumulator: get_account(accounts, 12).unwrap_or_default(),
246 user_volume_accumulator: get_account(accounts, 13).unwrap_or_default(),
247 fee_config: get_account(accounts, 14).unwrap_or_default(),
248 fee_program,
249 buyback_fee_recipient,
250 account,
251 sol_amount,
252 token_amount,
253 amount,
254 max_sol_cost,
255 spendable_sol_in,
256 spendable_quote_in,
257 min_tokens_out,
258 track_volume,
259 ix_name: ix_name.to_string(),
260 ..Default::default()
261 };
262
263 if exact_quote_in {
264 Some(DexEvent::PumpFunBuyExactSolIn(trade_event))
265 } else {
266 Some(DexEvent::PumpFunBuy(trade_event))
267 }
268}
269
270fn parse_sell_instruction(
280 data: &[u8],
281 accounts: &[Pubkey],
282 signature: Signature,
283 slot: u64,
284 tx_index: u64,
285 block_time_us: Option<i64>,
286 grpc_recv_us: i64,
287 ix_name: &'static str,
288 v2_accounts: bool,
289) -> Option<DexEvent> {
290 let min_accounts = if v2_accounts { 26 } else { 14 };
291 if accounts.len() < min_accounts {
292 return None;
293 }
294
295 let (amount, min_sol_output) = if data.len() >= 16 {
297 (read_u64_le(data, 0).unwrap_or(0), read_u64_le(data, 8).unwrap_or(0))
298 } else {
299 (0, 0)
300 };
301 let token_amount = amount;
302 let sol_amount = min_sol_output;
303
304 let (
305 global_idx,
306 mint_idx,
307 bonding_curve_idx,
308 associated_bonding_curve_idx,
309 associated_user_idx,
310 user_idx,
311 system_program_idx,
312 fee_recipient_idx,
313 token_program_idx,
314 creator_vault_idx,
315 event_authority_idx,
316 program_idx,
317 user_volume_accumulator_idx,
318 fee_config_idx,
319 fee_program_idx,
320 ) = if v2_accounts {
321 (0, 1, 10, 11, 14, 13, 23, 6, 3, 16, 24, 25, 19, 21, 22)
322 } else {
323 (0, 2, 3, 4, 5, 6, 7, 1, 9, 8, 10, 11, usize::MAX, 12, 13)
324 };
325 let mint = get_account(accounts, mint_idx)?;
326 let (legacy_user_volume_accumulator, legacy_bonding_curve_v2, legacy_buyback_fee_recipient) =
327 if v2_accounts {
328 (Pubkey::default(), Pubkey::default(), Pubkey::default())
329 } else if accounts.len() >= 17 {
330 (
331 get_account(accounts, 14).unwrap_or_default(),
332 get_account(accounts, 15).unwrap_or_default(),
333 get_account(accounts, 16).unwrap_or_default(),
334 )
335 } else if accounts.len() >= 16 {
336 (
337 Pubkey::default(),
338 get_account(accounts, 14).unwrap_or_default(),
339 get_account(accounts, 15).unwrap_or_default(),
340 )
341 } else {
342 (Pubkey::default(), get_account(accounts, 14).unwrap_or_default(), Pubkey::default())
343 };
344 let account = if legacy_buyback_fee_recipient != Pubkey::default() {
345 Some(legacy_buyback_fee_recipient)
346 } else {
347 None
348 };
349 let metadata =
350 create_metadata(signature, slot, tx_index, block_time_us.unwrap_or_default(), grpc_recv_us);
351
352 Some(DexEvent::PumpFunSell(PumpFunTradeEvent {
353 metadata,
354 mint,
355 quote_mint: if v2_accounts {
356 get_account(accounts, 2).unwrap_or_default()
357 } else {
358 Pubkey::default()
359 },
360 is_buy: false,
361 global: get_account(accounts, global_idx).unwrap_or_default(),
362 bonding_curve: get_account(accounts, bonding_curve_idx).unwrap_or_default(),
363 bonding_curve_v2: legacy_bonding_curve_v2,
364 associated_bonding_curve: get_account(accounts, associated_bonding_curve_idx)
365 .unwrap_or_default(),
366 associated_user: get_account(accounts, associated_user_idx).unwrap_or_default(),
367 user: get_account(accounts, user_idx).unwrap_or_default(),
368 system_program: get_account(accounts, system_program_idx).unwrap_or_default(),
369 fee_recipient: get_account(accounts, fee_recipient_idx).unwrap_or_default(),
370 token_program: get_account(accounts, token_program_idx).unwrap_or_default(),
371 quote_token_program: if v2_accounts {
372 get_account(accounts, 4).unwrap_or_default()
373 } else {
374 Pubkey::default()
375 },
376 associated_token_program: if v2_accounts {
377 get_account(accounts, 5).unwrap_or_default()
378 } else {
379 Pubkey::default()
380 },
381 creator_vault: get_account(accounts, creator_vault_idx).unwrap_or_default(),
382 associated_quote_fee_recipient: if v2_accounts {
383 get_account(accounts, 7).unwrap_or_default()
384 } else {
385 Pubkey::default()
386 },
387 associated_quote_buyback_fee_recipient: if v2_accounts {
388 get_account(accounts, 9).unwrap_or_default()
389 } else {
390 Pubkey::default()
391 },
392 associated_quote_bonding_curve: if v2_accounts {
393 get_account(accounts, 12).unwrap_or_default()
394 } else {
395 Pubkey::default()
396 },
397 associated_quote_user: if v2_accounts {
398 get_account(accounts, 15).unwrap_or_default()
399 } else {
400 Pubkey::default()
401 },
402 associated_creator_vault: if v2_accounts {
403 get_account(accounts, 17).unwrap_or_default()
404 } else {
405 Pubkey::default()
406 },
407 sharing_config: if v2_accounts {
408 get_account(accounts, 18).unwrap_or_default()
409 } else {
410 Pubkey::default()
411 },
412 event_authority: get_account(accounts, event_authority_idx).unwrap_or_default(),
413 program: get_account(accounts, program_idx).unwrap_or_default(),
414 user_volume_accumulator: if v2_accounts {
415 get_account(accounts, user_volume_accumulator_idx).unwrap_or_default()
416 } else {
417 legacy_user_volume_accumulator
418 },
419 associated_user_volume_accumulator: if v2_accounts {
420 get_account(accounts, 20).unwrap_or_default()
421 } else {
422 Pubkey::default()
423 },
424 fee_config: get_account(accounts, fee_config_idx).unwrap_or_default(),
425 fee_program: get_account(accounts, fee_program_idx).unwrap_or_default(),
426 buyback_fee_recipient: if v2_accounts {
427 get_account(accounts, 8).unwrap_or_default()
428 } else {
429 legacy_buyback_fee_recipient
430 },
431 account,
432 sol_amount,
433 token_amount,
434 amount,
435 min_sol_output,
436 ix_name: ix_name.to_string(),
437 ..Default::default()
438 }))
439}
440
441fn parse_buy_v2_instruction(
442 data: &[u8],
443 accounts: &[Pubkey],
444 signature: Signature,
445 slot: u64,
446 tx_index: u64,
447 block_time_us: Option<i64>,
448 grpc_recv_us: i64,
449 ix_name: &'static str,
450 exact_quote_in: bool,
451) -> Option<DexEvent> {
452 const MIN_ACC: usize = 27;
453 if accounts.len() < MIN_ACC {
454 return None;
455 }
456
457 let (first_arg, second_arg) = if data.len() >= 16 {
459 (read_u64_le(data, 0).unwrap_or(0), read_u64_le(data, 8).unwrap_or(0))
460 } else {
461 (0, 0)
462 };
463 let (
464 token_amount,
465 sol_amount,
466 amount,
467 max_sol_cost,
468 quote_amount,
469 spendable_quote_in,
470 min_tokens_out,
471 ) = if exact_quote_in {
472 (second_arg, first_arg, second_arg, 0, first_arg, first_arg, second_arg)
473 } else {
474 (first_arg, second_arg, first_arg, second_arg, 0, 0, 0)
475 };
476
477 let metadata =
478 create_metadata(signature, slot, tx_index, block_time_us.unwrap_or_default(), grpc_recv_us);
479 let trade_event = PumpFunTradeEvent {
480 metadata,
481 mint: accounts[1],
482 quote_mint: accounts[2],
483 is_buy: true,
484 global: accounts[0],
485 bonding_curve: accounts[10],
486 associated_bonding_curve: accounts[11],
487 associated_user: accounts[14],
488 user: accounts[13],
489 system_program: accounts[24],
490 quote_token_program: accounts[4],
491 associated_token_program: accounts[5],
492 sol_amount,
493 token_amount,
494 amount,
495 max_sol_cost,
496 quote_amount,
497 spendable_sol_in: 0,
498 spendable_quote_in,
499 min_tokens_out,
500 fee_recipient: accounts[6],
501 token_program: accounts[3],
502 creator_vault: accounts[16],
503 associated_quote_fee_recipient: accounts[7],
504 buyback_fee_recipient: accounts[8],
505 associated_quote_buyback_fee_recipient: accounts[9],
506 associated_quote_bonding_curve: accounts[12],
507 associated_quote_user: accounts[15],
508 associated_creator_vault: accounts[17],
509 sharing_config: accounts[18],
510 event_authority: accounts[25],
511 program: accounts[26],
512 global_volume_accumulator: accounts[19],
513 user_volume_accumulator: accounts[20],
514 associated_user_volume_accumulator: accounts[21],
515 fee_config: accounts[22],
516 fee_program: accounts[23],
517 ix_name: ix_name.to_string(),
518 ..Default::default()
519 };
520
521 if exact_quote_in {
522 Some(DexEvent::PumpFunBuyExactSolIn(trade_event))
523 } else {
524 Some(DexEvent::PumpFunBuy(trade_event))
525 }
526}
527
528fn parse_sell_v2_instruction(
529 data: &[u8],
530 accounts: &[Pubkey],
531 signature: Signature,
532 slot: u64,
533 tx_index: u64,
534 block_time_us: Option<i64>,
535 grpc_recv_us: i64,
536 ix_name: &'static str,
537) -> Option<DexEvent> {
538 parse_sell_instruction(
539 data,
540 accounts,
541 signature,
542 slot,
543 tx_index,
544 block_time_us,
545 grpc_recv_us,
546 ix_name,
547 true,
548 )
549}
550
551fn parse_create_instruction(
557 data: &[u8],
558 accounts: &[Pubkey],
559 signature: Signature,
560 slot: u64,
561 tx_index: u64,
562 block_time_us: Option<i64>,
563 grpc_recv_us: i64,
564) -> Option<DexEvent> {
565 if accounts.len() < 8 {
566 return None;
567 }
568
569 let mut offset = 0;
570
571 let name = if let Some((s, len)) = read_str_unchecked(data, offset) {
574 offset += len;
575 s.to_string()
576 } else {
577 String::new()
578 };
579
580 let symbol = if let Some((s, len)) = read_str_unchecked(data, offset) {
581 offset += len;
582 s.to_string()
583 } else {
584 String::new()
585 };
586
587 let uri = if let Some((s, len)) = read_str_unchecked(data, offset) {
588 offset += len;
589 s.to_string()
590 } else {
591 String::new()
592 };
593
594 if data.len() < offset + 32 + 32 + 32 + 32 {
596 return None;
597 }
598
599 let mint = read_pubkey(data, offset).unwrap_or_default();
600 offset += 32;
601
602 let bonding_curve = read_pubkey(data, offset).unwrap_or_default();
603 offset += 32;
604
605 let user = read_pubkey(data, offset).unwrap_or_default();
606 offset += 32;
607
608 let creator = read_pubkey(data, offset).unwrap_or_default();
609
610 let metadata =
611 create_metadata(signature, slot, tx_index, block_time_us.unwrap_or_default(), grpc_recv_us);
612
613 Some(DexEvent::PumpFunCreate(PumpFunCreateTokenEvent {
614 metadata,
615 name,
616 symbol,
617 uri,
618 mint,
619 bonding_curve,
620 user,
621 creator,
622 ..Default::default()
623 }))
624}
625
626fn parse_create_v2_instruction(
635 data: &[u8],
636 accounts: &[Pubkey],
637 signature: Signature,
638 slot: u64,
639 tx_index: u64,
640 block_time_us: Option<i64>,
641 grpc_recv_us: i64,
642) -> Option<DexEvent> {
643 const CREATE_V2_MIN_ACCOUNTS: usize = 16;
644 if accounts.len() < CREATE_V2_MIN_ACCOUNTS {
645 return None;
646 }
647 let acc = &accounts[0..CREATE_V2_MIN_ACCOUNTS];
648
649 let mut offset = 0usize;
651 let name = if let Some((s, len)) = read_str_unchecked(data, offset) {
652 offset += len;
653 s.to_string()
654 } else {
655 String::new()
656 };
657 let symbol = if let Some((s, len)) = read_str_unchecked(data, offset) {
658 offset += len;
659 s.to_string()
660 } else {
661 String::new()
662 };
663 let uri = if let Some((s, len)) = read_str_unchecked(data, offset) {
664 offset += len;
665 s.to_string()
666 } else {
667 String::new()
668 };
669 if data.len() < offset + 32 + 1 {
670 return None;
671 }
672 let creator = read_pubkey(data, offset)?;
673 offset += 32;
674 let is_mayhem_mode = read_bool(data, offset)?;
675 offset += 1;
676 let is_cashback_enabled = read_option_bool_idl(data, offset).unwrap_or(false);
677
678 let mint = acc[0];
679 let bonding_curve = acc[2];
680 let user = acc[5];
681
682 let metadata =
683 create_metadata(signature, slot, tx_index, block_time_us.unwrap_or_default(), grpc_recv_us);
684
685 Some(DexEvent::PumpFunCreateV2(PumpFunCreateV2TokenEvent {
686 metadata,
687 name,
688 symbol,
689 uri,
690 mint,
691 bonding_curve,
692 user,
693 creator,
694 mint_authority: acc[1],
695 associated_bonding_curve: acc[3],
696 global: acc[4],
697 system_program: acc[6],
698 token_program: acc[7],
699 associated_token_program: acc[8],
700 mayhem_program_id: acc[9],
701 global_params: acc[10],
702 sol_vault: acc[11],
703 mayhem_state: acc[12],
704 mayhem_token_vault: acc[13],
705 event_authority: acc[14],
706 program: acc[15],
707 is_mayhem_mode,
708 is_cashback_enabled,
709 ..Default::default()
710 }))
711}
712
713#[allow(unused_variables)]
715fn parse_migrate_log_instruction(
716 data: &[u8],
717 accounts: &[Pubkey],
718 signature: Signature,
719 slot: u64,
720 tx_index: u64,
721 block_time_us: Option<i64>,
722 rpc_recv_us: i64,
723) -> Option<DexEvent> {
724 let mut offset = 0;
725
726 let user = read_pubkey(data, offset)?;
728 offset += 32;
729
730 let mint = read_pubkey(data, offset)?;
732 offset += 32;
733
734 let mint_amount = read_u64_le(data, offset)?;
736 offset += 8;
737
738 let sol_amount = read_u64_le(data, offset)?;
740 offset += 8;
741
742 let pool_migration_fee = read_u64_le(data, offset)?;
744 offset += 8;
745
746 let bonding_curve = read_pubkey(data, offset)?;
748 offset += 32;
749
750 let timestamp = read_u64_le(data, offset)? as i64;
752 offset += 8;
753
754 let pool = read_pubkey(data, offset)?;
756
757 let metadata =
758 create_metadata(signature, slot, tx_index, block_time_us.unwrap_or_default(), rpc_recv_us);
759
760 Some(DexEvent::PumpFunMigrate(PumpFunMigrateEvent {
761 metadata,
762 user,
763 mint,
764 mint_amount,
765 sol_amount,
766 pool_migration_fee,
767 bonding_curve,
768 timestamp,
769 pool,
770 }))
771}
772
773#[cfg(test)]
774mod tests {
775 use super::*;
776
777 fn instruction_data(discriminator: [u8; 8], first: u64, second: u64) -> Vec<u8> {
778 let mut data = Vec::with_capacity(24);
779 data.extend_from_slice(&discriminator);
780 data.extend_from_slice(&first.to_le_bytes());
781 data.extend_from_slice(&second.to_le_bytes());
782 data
783 }
784
785 fn accounts(n: usize) -> Vec<Pubkey> {
786 (0..n).map(|_| Pubkey::new_unique()).collect()
787 }
788
789 #[test]
790 fn pumpfun_buy_instruction_exposes_raw_args() {
791 let data = instruction_data(discriminators::BUY, 123, 456);
792 let acc = accounts(18);
793 let event =
794 parse_instruction(&data, &acc, Signature::default(), 1, 0, None, 99).expect("event");
795
796 match event {
797 DexEvent::PumpFunBuy(t) => {
798 assert_eq!(t.amount, 123);
799 assert_eq!(t.max_sol_cost, 456);
800 assert_eq!(t.min_sol_output, 0);
801 assert_eq!(t.spendable_sol_in, 0);
802 assert_eq!(t.min_tokens_out, 0);
803 assert_eq!(t.token_amount, 123);
804 assert_eq!(t.sol_amount, 456);
805 assert_eq!(t.bonding_curve_v2, acc[16]);
806 assert_eq!(t.buyback_fee_recipient, acc[17]);
807 assert_eq!(t.ix_name, "buy");
808 }
809 other => panic!("expected PumpFunBuy, got {other:?}"),
810 }
811 }
812
813 #[test]
814 fn pumpfun_legacy_trade_rejects_short_account_lists() {
815 let buy_data = instruction_data(discriminators::BUY, 123, 456);
816 assert!(parse_instruction(&buy_data, &accounts(15), Signature::default(), 1, 0, None, 99)
817 .is_none());
818
819 let sell_data = instruction_data(discriminators::SELL, 321, 654);
820 assert!(parse_instruction(&sell_data, &accounts(13), Signature::default(), 1, 0, None, 99)
821 .is_none());
822 }
823
824 #[test]
825 fn pumpfun_sell_instruction_exposes_raw_args() {
826 let data = instruction_data(discriminators::SELL, 321, 654);
827 let acc = accounts(16);
828 let event =
829 parse_instruction(&data, &acc, Signature::default(), 1, 0, None, 99).expect("event");
830
831 match event {
832 DexEvent::PumpFunSell(t) => {
833 assert_eq!(t.amount, 321);
834 assert_eq!(t.max_sol_cost, 0);
835 assert_eq!(t.min_sol_output, 654);
836 assert_eq!(t.spendable_sol_in, 0);
837 assert_eq!(t.min_tokens_out, 0);
838 assert_eq!(t.token_amount, 321);
839 assert_eq!(t.sol_amount, 654);
840 assert_eq!(t.user_volume_accumulator, Pubkey::default());
841 assert_eq!(t.bonding_curve_v2, acc[14]);
842 assert_eq!(t.buyback_fee_recipient, acc[15]);
843 assert_eq!(t.ix_name, "sell");
844 }
845 other => panic!("expected PumpFunSell, got {other:?}"),
846 }
847 }
848
849 #[test]
850 fn pumpfun_cashback_sell_uses_17_account_layout() {
851 let data = instruction_data(discriminators::SELL, 321, 654);
852 let acc = accounts(17);
853 let event =
854 parse_instruction(&data, &acc, Signature::default(), 1, 0, None, 99).expect("event");
855
856 match event {
857 DexEvent::PumpFunSell(t) => {
858 assert_eq!(t.user_volume_accumulator, acc[14]);
859 assert_eq!(t.bonding_curve_v2, acc[15]);
860 assert_eq!(t.buyback_fee_recipient, acc[16]);
861 }
862 other => panic!("expected PumpFunSell, got {other:?}"),
863 }
864 }
865
866 #[test]
867 fn pumpfun_buy_exact_sol_in_exposes_exact_args() {
868 let data = instruction_data(discriminators::BUY_EXACT_SOL_IN, 1_111, 2_222);
869 let acc = accounts(18);
870 let event =
871 parse_instruction(&data, &acc, Signature::default(), 1, 0, None, 99).expect("event");
872
873 match event {
874 DexEvent::PumpFunBuyExactSolIn(t) => {
875 assert_eq!(t.spendable_sol_in, 1_111);
876 assert_eq!(t.spendable_quote_in, 0);
877 assert_eq!(t.min_tokens_out, 2_222);
878 assert_eq!(t.sol_amount, 1_111);
879 assert_eq!(t.token_amount, 2_222);
880 assert_eq!(t.global, acc[0]);
881 assert_eq!(t.associated_user, acc[5]);
882 assert_eq!(t.event_authority, acc[10]);
883 assert_eq!(t.fee_program, acc[15]);
884 assert_eq!(t.bonding_curve_v2, acc[16]);
885 assert_eq!(t.buyback_fee_recipient, acc[17]);
886 assert_eq!(t.ix_name, "buy_exact_sol_in");
887 }
888 other => panic!("expected PumpFunBuyExactSolIn, got {other:?}"),
889 }
890 }
891
892 #[test]
893 fn pumpfun_v2_instruction_args_use_v2_account_layout() {
894 let data = instruction_data(discriminators::BUY_V2, 777, 888);
895 let acc = accounts(27);
896 let event =
897 parse_instruction(&data, &acc, Signature::default(), 1, 0, None, 99).expect("event");
898
899 match event {
900 DexEvent::PumpFunBuy(t) => {
901 assert_eq!(t.amount, 777);
902 assert_eq!(t.max_sol_cost, 888);
903 assert_eq!(t.mint, acc[1]);
904 assert_eq!(t.quote_mint, acc[2]);
905 assert_eq!(t.bonding_curve, acc[10]);
906 assert_eq!(t.associated_bonding_curve, acc[11]);
907 assert_eq!(t.associated_quote_bonding_curve, acc[12]);
908 assert_eq!(t.user, acc[13]);
909 assert_eq!(t.associated_quote_user, acc[15]);
910 assert_eq!(t.quote_token_program, acc[4]);
911 assert_eq!(t.associated_token_program, acc[5]);
912 assert_eq!(t.associated_quote_fee_recipient, acc[7]);
913 assert_eq!(t.buyback_fee_recipient, acc[8]);
914 assert_eq!(t.associated_quote_buyback_fee_recipient, acc[9]);
915 assert_eq!(t.associated_creator_vault, acc[17]);
916 assert_eq!(t.sharing_config, acc[18]);
917 assert_eq!(t.global_volume_accumulator, acc[19]);
918 assert_eq!(t.associated_user_volume_accumulator, acc[21]);
919 assert_eq!(t.ix_name, "buy_v2");
920 }
921 other => panic!("expected PumpFunBuy, got {other:?}"),
922 }
923 }
924
925 #[test]
926 fn pumpfun_buy_exact_quote_in_v2_uses_quote_amount_fields() {
927 let data = instruction_data(discriminators::BUY_EXACT_QUOTE_IN_V2, 777, 888);
928 let acc = accounts(27);
929 let event =
930 parse_instruction(&data, &acc, Signature::default(), 1, 0, None, 99).expect("event");
931
932 match event {
933 DexEvent::PumpFunBuyExactSolIn(t) => {
934 assert_eq!(t.ix_name, "buy_exact_quote_in_v2");
935 assert_eq!(t.amount, 888);
936 assert_eq!(t.max_sol_cost, 0);
937 assert_eq!(t.quote_amount, 777);
938 assert_eq!(t.spendable_quote_in, 777);
939 assert_eq!(t.min_tokens_out, 888);
940 assert_eq!(t.quote_mint, acc[2]);
941 }
942 other => panic!("expected PumpFunBuyExactSolIn, got {other:?}"),
943 }
944 }
945}