1#![allow(dead_code)]
3#![allow(unused_imports)]
4#![allow(unused_variables)]
5
6use crate::core::events::*;
7use solana_sdk::{pubkey::Pubkey, signature::Signature};
8
9use memchr::memmem;
10use once_cell::sync::Lazy;
11
12#[cfg(feature = "perf-stats")]
13use std::sync::atomic::{AtomicUsize, Ordering};
14
15#[cfg(feature = "perf-stats")]
16pub static PARSE_COUNT: AtomicUsize = AtomicUsize::new(0);
17#[cfg(feature = "perf-stats")]
18pub static PARSE_TIME_NS: AtomicUsize = AtomicUsize::new(0);
19
20pub const CREATE_EVENT: u64 = u64::from_le_bytes([27, 114, 169, 77, 222, 235, 99, 118]);
23pub const TRADE_EVENT: u64 = u64::from_le_bytes([189, 219, 127, 211, 78, 230, 97, 238]);
24pub const MIGRATE_EVENT: u64 = u64::from_le_bytes([189, 233, 93, 185, 92, 148, 234, 148]);
25pub const CREATE_FEE_SHARING_CONFIG_EVENT: u64 = crate::logs::pump_fees::discriminant_u64(
27 &crate::logs::pump_fees::CREATE_FEE_SHARING_CONFIG_EVENT_DISC,
28);
29pub const MIGRATE_BONDING_CURVE_CREATOR_EVENT: u64 =
31 u64::from_le_bytes([155, 167, 104, 220, 213, 108, 243, 3]);
32
33#[inline]
34pub fn normalize_pumpfun_ix_name(ix_name: &str) -> &str {
35 match ix_name {
36 "buy_v2" => "buy",
37 "sell_v2" => "sell",
38 "buy_exact_quote_in_v2" => "buy_exact_quote_in",
39 other => other,
40 }
41}
42
43#[inline(always)]
46pub unsafe fn read_u64_unchecked(data: &[u8], offset: usize) -> u64 {
50 let ptr = data.as_ptr().add(offset) as *const u64;
51 u64::from_le(ptr.read_unaligned())
52}
53
54#[inline(always)]
55pub unsafe fn read_i64_unchecked(data: &[u8], offset: usize) -> i64 {
59 let ptr = data.as_ptr().add(offset) as *const i64;
60 i64::from_le(ptr.read_unaligned())
61}
62
63#[inline(always)]
64pub unsafe fn read_bool_unchecked(data: &[u8], offset: usize) -> bool {
68 *data.get_unchecked(offset) == 1
69}
70
71#[inline(always)]
72pub unsafe fn read_pubkey_unchecked(data: &[u8], offset: usize) -> Pubkey {
76 #[cfg(target_arch = "x86_64")]
77 {
78 use std::arch::x86_64::_mm_prefetch;
79 use std::arch::x86_64::_MM_HINT_T0;
80 if offset + 64 < data.len() {
81 _mm_prefetch((data.as_ptr().add(offset + 32)) as *const i8, _MM_HINT_T0);
82 }
83 }
84
85 let ptr = data.as_ptr().add(offset);
86 let mut bytes = [0u8; 32];
87 std::ptr::copy_nonoverlapping(ptr, bytes.as_mut_ptr(), 32);
88 Pubkey::new_from_array(bytes)
89}
90
91#[inline(always)]
92pub unsafe fn read_str_unchecked(data: &[u8], offset: usize) -> Option<(&str, usize)> {
97 if data.len() < offset + 4 {
98 return None;
99 }
100
101 let len = read_u32_unchecked(data, offset) as usize;
102 if data.len() < offset + 4 + len {
103 return None;
104 }
105
106 let string_bytes = &data[offset + 4..offset + 4 + len];
107 let s = std::str::from_utf8_unchecked(string_bytes);
108 Some((s, 4 + len))
109}
110
111#[inline(always)]
112pub unsafe fn read_u32_unchecked(data: &[u8], offset: usize) -> u32 {
116 let ptr = data.as_ptr().add(offset) as *const u32;
117 u32::from_le(ptr.read_unaligned())
118}
119
120#[inline(always)]
121pub unsafe fn read_u16_unchecked(data: &[u8], offset: usize) -> u16 {
125 let ptr = data.as_ptr().add(offset) as *const u16;
126 u16::from_le(ptr.read_unaligned())
127}
128
129const MAX_TRADE_SHAREHOLDERS: usize = 64;
130type TradeEventExtensions = (u64, u64, Vec<PumpFeesShareholder>, Pubkey, u64, u64, u64, u64, u64);
131
132#[inline(always)]
133unsafe fn read_optional_u64(data: &[u8], offset: &mut usize) -> u64 {
134 if *offset + 8 <= data.len() {
135 let v = read_u64_unchecked(data, *offset);
136 *offset += 8;
137 v
138 } else {
139 0
140 }
141}
142
143#[inline(always)]
144unsafe fn read_optional_pubkey(data: &[u8], offset: &mut usize) -> Pubkey {
145 if *offset + 32 <= data.len() {
146 let v = read_pubkey_unchecked(data, *offset);
147 *offset += 32;
148 v
149 } else {
150 Pubkey::default()
151 }
152}
153
154#[inline(always)]
155unsafe fn read_trade_shareholders(
156 data: &[u8],
157 offset: &mut usize,
158) -> Option<Vec<PumpFeesShareholder>> {
159 if *offset + 4 > data.len() {
160 return Some(Vec::new());
161 }
162 let n = read_u32_unchecked(data, *offset) as usize;
163 if n > MAX_TRADE_SHAREHOLDERS {
164 return None;
165 }
166 let bytes = 4usize.checked_add(n.checked_mul(34)?)?;
167 if *offset + bytes > data.len() {
168 return None;
169 }
170 *offset += 4;
171 let mut out = Vec::with_capacity(n);
172 for _ in 0..n {
173 let address = read_pubkey_unchecked(data, *offset);
174 *offset += 32;
175 let share_bps = read_u16_unchecked(data, *offset);
176 *offset += 2;
177 out.push(PumpFeesShareholder { address, share_bps });
178 }
179 Some(out)
180}
181
182#[inline(always)]
183pub(crate) unsafe fn read_trade_event_extensions(
184 data: &[u8],
185 offset: &mut usize,
186) -> Option<TradeEventExtensions> {
187 let buyback_fee_basis_points = read_optional_u64(data, offset);
188 let buyback_fee = read_optional_u64(data, offset);
189 let shareholders = read_trade_shareholders(data, offset)?;
190 let quote_mint = normalize_pumpfun_quote_mint(read_optional_pubkey(data, offset));
191 let quote_amount = read_optional_u64(data, offset);
192 let virtual_quote_reserves = read_optional_u64(data, offset);
193 let real_quote_reserves = read_optional_u64(data, offset);
194 let holder_rewards_bps = read_optional_u64(data, offset);
195 let holder_rewards = read_optional_u64(data, offset);
196 Some((
197 buyback_fee_basis_points,
198 buyback_fee,
199 shareholders,
200 quote_mint,
201 quote_amount,
202 virtual_quote_reserves,
203 real_quote_reserves,
204 holder_rewards_bps,
205 holder_rewards,
206 ))
207}
208
209static BASE64_FINDER: Lazy<memmem::Finder> = Lazy::new(|| memmem::Finder::new(b"Program data: "));
212const PROGRAM_DATA_TAG_LEN: usize = 14;
214
215#[inline(always)]
216pub fn extract_program_data_zero_copy<'a>(
217 log: &'a str,
218 buf: &'a mut [u8; 2048],
219) -> Option<&'a [u8]> {
220 let log_bytes = log.as_bytes();
221 let pos = BASE64_FINDER.find(log_bytes)?;
222
223 let data_part = &log[pos + PROGRAM_DATA_TAG_LEN..];
224 let trimmed = data_part.trim();
225
226 if trimmed.len() > 2700 {
227 return None;
228 }
229
230 use base64_simd::AsOut;
231 let decoded_slice =
232 base64_simd::STANDARD.decode(trimmed.as_bytes(), buf.as_mut().as_out()).ok()?;
233
234 Some(decoded_slice)
235}
236
237#[inline(always)]
238pub fn extract_discriminator_simd(log: &str) -> Option<u64> {
239 let log_bytes = log.as_bytes();
240 let pos = BASE64_FINDER.find(log_bytes)?;
241
242 let data_part = &log[pos + PROGRAM_DATA_TAG_LEN..];
243 let trimmed = data_part.trim();
244
245 if trimmed.len() < 16 {
246 return None;
247 }
248
249 use base64_simd::AsOut;
250 let mut buf = [0u8; 12];
251 let prefix = trimmed.as_bytes().get(..16)?;
252 base64_simd::STANDARD.decode(prefix, buf.as_mut().as_out()).ok()?;
253
254 unsafe {
255 let ptr = buf.as_ptr() as *const u64;
256 Some(ptr.read_unaligned())
257 }
258}
259
260#[inline(always)]
265pub fn parse_log(
266 log: &str,
267 signature: Signature,
268 slot: u64,
269 tx_index: u64,
270 block_time_us: Option<i64>,
271 grpc_recv_us: i64,
272 is_created_buy: bool,
273) -> Option<DexEvent> {
274 #[cfg(feature = "perf-stats")]
275 let start = std::time::Instant::now();
276
277 let mut buf = [0u8; 2048];
279 let program_data = extract_program_data_zero_copy(log, &mut buf)?;
280
281 if program_data.len() < 8 {
282 return None;
283 }
284
285 let discriminator = unsafe { read_u64_unchecked(program_data, 0) };
287 let data = &program_data[8..];
288
289 let result = match discriminator {
290 CREATE_EVENT => parse_create_event_optimized(
291 data,
292 signature,
293 slot,
294 tx_index,
295 block_time_us,
296 grpc_recv_us,
297 ),
298 TRADE_EVENT => parse_trade_event_optimized(
299 data,
300 signature,
301 slot,
302 tx_index,
303 block_time_us,
304 grpc_recv_us,
305 is_created_buy,
306 ),
307 MIGRATE_EVENT => parse_migrate_event_optimized(
308 data,
309 signature,
310 slot,
311 tx_index,
312 block_time_us,
313 grpc_recv_us,
314 ),
315 CREATE_FEE_SHARING_CONFIG_EVENT => parse_create_fee_sharing_config_event_optimized(
316 data,
317 signature,
318 slot,
319 tx_index,
320 block_time_us,
321 grpc_recv_us,
322 ),
323 MIGRATE_BONDING_CURVE_CREATOR_EVENT => parse_migrate_bonding_curve_creator_event_optimized(
324 data,
325 signature,
326 slot,
327 tx_index,
328 block_time_us,
329 grpc_recv_us,
330 ),
331 _ => None,
332 };
333
334 #[cfg(feature = "perf-stats")]
335 {
336 PARSE_COUNT.fetch_add(1, Ordering::Relaxed);
337 PARSE_TIME_NS.fetch_add(start.elapsed().as_nanos() as usize, Ordering::Relaxed);
338 }
339
340 result
341}
342
343#[inline(always)]
350fn parse_create_event_optimized(
351 data: &[u8],
352 signature: Signature,
353 slot: u64,
354 tx_index: u64,
355 block_time_us: Option<i64>,
356 grpc_recv_us: i64,
357) -> Option<DexEvent> {
358 unsafe {
359 let mut offset = 0;
360
361 let (name, name_len) = read_str_unchecked(data, offset)?;
363 offset += name_len;
364
365 let (symbol, symbol_len) = read_str_unchecked(data, offset)?;
366 offset += symbol_len;
367
368 let (uri, uri_len) = read_str_unchecked(data, offset)?;
369 offset += uri_len;
370
371 if data.len() - offset == 96 {
373 return Some(DexEvent::PumpFunCreate(PumpFunCreateTokenEvent {
374 metadata: EventMetadata {
375 signature,
376 slot,
377 tx_index,
378 block_time_us: block_time_us.unwrap_or(0),
379 grpc_recv_us,
380 recent_blockhash: None,
381 },
382 name: name.to_string(),
383 symbol: symbol.to_string(),
384 uri: uri.to_string(),
385 mint: read_pubkey_unchecked(data, offset),
386 bonding_curve: read_pubkey_unchecked(data, offset + 32),
387 user: read_pubkey_unchecked(data, offset + 64),
388 ix_name: "create".into(),
389 quote_mint: PUMPFUN_SOLSCAN_SOL_QUOTE_MINT,
390 ..Default::default()
391 }));
392 }
393 if data.len() < offset + 32 + 32 + 32 + 32 + 8 + 8 + 8 + 8 + 8 + 32 + 1 {
394 return None;
395 }
396
397 let mint = read_pubkey_unchecked(data, offset);
399 offset += 32;
400
401 let bonding_curve = read_pubkey_unchecked(data, offset);
402 offset += 32;
403
404 let user = read_pubkey_unchecked(data, offset);
405 offset += 32;
406
407 let creator = read_pubkey_unchecked(data, offset);
408 offset += 32;
409
410 let timestamp = read_i64_unchecked(data, offset);
412 offset += 8;
413
414 let virtual_token_reserves = read_u64_unchecked(data, offset);
415 offset += 8;
416
417 let virtual_sol_reserves = read_u64_unchecked(data, offset);
418 offset += 8;
419
420 let real_token_reserves = read_u64_unchecked(data, offset);
421 offset += 8;
422
423 let token_total_supply = read_u64_unchecked(data, offset);
424 offset += 8;
425
426 let token_program = if offset + 32 <= data.len() {
427 read_pubkey_unchecked(data, offset)
428 } else {
429 Pubkey::default()
430 };
431 offset += 32;
432
433 let is_mayhem_mode =
434 if offset < data.len() { read_bool_unchecked(data, offset) } else { false };
435 offset += 1;
436 let is_cashback_enabled =
437 if offset < data.len() { read_bool_unchecked(data, offset) } else { false };
438 offset += 1;
439 let quote_mint = normalize_pumpfun_quote_mint(if offset + 32 <= data.len() {
440 read_pubkey_unchecked(data, offset)
441 } else {
442 Pubkey::default()
443 });
444 offset += 32;
445 let virtual_quote_reserves =
446 if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
447 offset += 8;
448 let creator_fee_bps =
449 if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
450 offset += 8;
451 let is_holder_reward =
452 if offset < data.len() { read_bool_unchecked(data, offset) } else { false };
453
454 let metadata = EventMetadata {
455 signature,
456 slot,
457 tx_index,
458 block_time_us: block_time_us.unwrap_or(0),
459 grpc_recv_us,
460 recent_blockhash: None,
461 };
462
463 Some(DexEvent::PumpFunCreate(PumpFunCreateTokenEvent {
466 metadata,
467 name: name.to_string(),
468 symbol: symbol.to_string(),
469 uri: uri.to_string(),
470 mint,
471 bonding_curve,
472 user,
473 creator,
474 timestamp,
475 virtual_token_reserves,
476 virtual_sol_reserves,
477 real_token_reserves,
478 token_total_supply,
479 token_program,
480 is_mayhem_mode,
481 is_cashback_enabled,
482 quote_mint,
483 virtual_quote_reserves,
484 creator_fee_bps,
485 is_holder_reward,
486 ix_name: "create".to_string(),
487 ..Default::default()
488 }))
489 }
490}
491
492#[inline(always)]
501fn parse_trade_event_optimized(
502 data: &[u8],
503 signature: Signature,
504 slot: u64,
505 tx_index: u64,
506 block_time_us: Option<i64>,
507 grpc_recv_us: i64,
508 is_created_buy: bool,
509) -> Option<DexEvent> {
510 unsafe {
511 if data.len() < 32 + 8 + 8 + 1 + 32 + 8 + 8 + 8 + 8 + 8 + 32 + 8 + 8 + 32 + 8 + 8 {
513 return None;
514 }
515
516 let mut offset = 0;
517
518 let mint = read_pubkey_unchecked(data, offset);
519 offset += 32;
520
521 let sol_amount = read_u64_unchecked(data, offset);
522 offset += 8;
523
524 let token_amount = read_u64_unchecked(data, offset);
525 offset += 8;
526
527 let is_buy = read_bool_unchecked(data, offset);
528 offset += 1;
529
530 let user = read_pubkey_unchecked(data, offset);
531 offset += 32;
532
533 let timestamp = read_i64_unchecked(data, offset);
534 offset += 8;
535
536 let virtual_sol_reserves = read_u64_unchecked(data, offset);
537 offset += 8;
538
539 let virtual_token_reserves = read_u64_unchecked(data, offset);
540 offset += 8;
541
542 let real_sol_reserves = read_u64_unchecked(data, offset);
543 offset += 8;
544
545 let real_token_reserves = read_u64_unchecked(data, offset);
546 offset += 8;
547
548 let fee_recipient = read_pubkey_unchecked(data, offset);
549 offset += 32;
550
551 let fee_basis_points = read_u64_unchecked(data, offset);
552 offset += 8;
553
554 let fee = read_u64_unchecked(data, offset);
555 offset += 8;
556
557 let creator = read_pubkey_unchecked(data, offset);
558 offset += 32;
559
560 let creator_fee_basis_points = read_u64_unchecked(data, offset);
561 offset += 8;
562
563 let creator_fee = read_u64_unchecked(data, offset);
564 offset += 8;
565
566 let track_volume =
568 if offset < data.len() { read_bool_unchecked(data, offset) } else { false };
569 offset += 1;
570
571 let total_unclaimed_tokens =
572 if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
573 offset += 8;
574
575 let total_claimed_tokens =
576 if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
577 offset += 8;
578
579 let current_sol_volume =
580 if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
581 offset += 8;
582
583 let last_update_timestamp =
584 if offset + 8 <= data.len() { read_i64_unchecked(data, offset) } else { 0 };
585 offset += 8;
586
587 let ix_name = if offset + 4 <= data.len() {
590 if let Some((s, len)) = read_str_unchecked(data, offset) {
591 offset += len;
592 s.to_string()
593 } else {
594 String::new()
595 }
596 } else {
597 String::new()
598 };
599 let ix_kind = normalize_pumpfun_ix_name(&ix_name);
600
601 let mayhem_mode =
603 if offset < data.len() { read_bool_unchecked(data, offset) } else { false };
604 offset += 1;
605 let cashback_fee_basis_points =
606 if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
607 offset += 8;
608 let cashback = if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
609 offset += 8;
610 let (
611 buyback_fee_basis_points,
612 buyback_fee,
613 shareholders,
614 quote_mint,
615 quote_amount,
616 virtual_quote_reserves,
617 real_quote_reserves,
618 holder_rewards_bps,
619 holder_rewards,
620 ) = read_trade_event_extensions(data, &mut offset)?;
621
622 let metadata = EventMetadata {
623 signature,
624 slot,
625 tx_index,
626 block_time_us: block_time_us.unwrap_or(0),
627 grpc_recv_us,
628 recent_blockhash: None,
629 };
630
631 let trade_event = PumpFunTradeEvent {
632 metadata,
633 mint,
634 sol_amount,
635 token_amount,
636 is_buy,
637 is_created_buy,
638 user,
639 timestamp,
640 virtual_sol_reserves,
641 virtual_token_reserves,
642 real_sol_reserves,
643 real_token_reserves,
644 fee_recipient,
645 fee_basis_points,
646 fee,
647 creator,
648 creator_fee_basis_points,
649 creator_fee,
650 track_volume,
651 total_unclaimed_tokens,
652 total_claimed_tokens,
653 current_sol_volume,
654 last_update_timestamp,
655 ix_name: ix_name.clone(),
656 mayhem_mode,
657 cashback_fee_basis_points,
658 cashback,
659 buyback_fee_basis_points,
660 buyback_fee,
661 shareholders,
662 quote_mint,
663 quote_amount,
664 virtual_quote_reserves,
665 real_quote_reserves,
666 holder_rewards_bps,
667 holder_rewards,
668 is_cashback_coin: cashback_fee_basis_points > 0,
669 amount: 0,
670 max_sol_cost: 0,
671 min_sol_output: 0,
672 spendable_sol_in: 0,
673 spendable_quote_in: 0,
674 min_tokens_out: 0,
675 global: Pubkey::default(),
676 bonding_curve: Pubkey::default(),
677 associated_bonding_curve: Pubkey::default(),
678 associated_user: Pubkey::default(),
679 system_program: Pubkey::default(),
680 creator_vault: Pubkey::default(),
681 event_authority: Pubkey::default(),
682 program: Pubkey::default(),
683 global_volume_accumulator: Pubkey::default(),
684 user_volume_accumulator: Pubkey::default(),
685 fee_config: Pubkey::default(),
686 fee_program: Pubkey::default(),
687 token_program: Pubkey::default(),
688 account: None,
689 ..Default::default()
690 };
691
692 match ix_kind {
694 "buy" => Some(DexEvent::PumpFunBuy(trade_event)),
695 "sell" => Some(DexEvent::PumpFunSell(trade_event)),
696 "buy_exact_sol_in" => Some(DexEvent::PumpFunBuyExactSolIn(trade_event)),
697 "buy_exact_quote_in" => Some(DexEvent::PumpFunBuy(trade_event)),
698 _ => Some(DexEvent::PumpFunTrade(trade_event)), }
700 }
701}
702
703#[inline(always)]
705fn parse_migrate_event_optimized(
706 data: &[u8],
707 signature: Signature,
708 slot: u64,
709 tx_index: u64,
710 block_time_us: Option<i64>,
711 grpc_recv_us: i64,
712) -> Option<DexEvent> {
713 unsafe {
714 if data.len() < 32 + 32 + 8 + 8 + 8 + 32 + 8 + 32 {
716 return None;
717 }
718
719 let mut offset = 0;
720
721 let user = read_pubkey_unchecked(data, offset);
722 offset += 32;
723
724 let mint = read_pubkey_unchecked(data, offset);
725 offset += 32;
726
727 let mint_amount = read_u64_unchecked(data, offset);
728 offset += 8;
729
730 let sol_amount = read_u64_unchecked(data, offset);
731 offset += 8;
732
733 let pool_migration_fee = read_u64_unchecked(data, offset);
734 offset += 8;
735
736 let bonding_curve = read_pubkey_unchecked(data, offset);
737 offset += 32;
738
739 let timestamp = read_i64_unchecked(data, offset);
740 offset += 8;
741
742 let pool = read_pubkey_unchecked(data, offset);
743
744 let metadata = EventMetadata {
745 signature,
746 slot,
747 tx_index,
748 block_time_us: block_time_us.unwrap_or(0),
749 grpc_recv_us,
750 recent_blockhash: None,
751 };
752
753 Some(DexEvent::PumpFunMigrate(PumpFunMigrateEvent {
754 metadata,
755 user,
756 mint,
757 mint_amount,
758 sol_amount,
759 pool_migration_fee,
760 bonding_curve,
761 timestamp,
762 pool,
763 }))
764 }
765}
766
767#[inline(always)]
768fn parse_migrate_bonding_curve_creator_event_optimized(
769 data: &[u8],
770 signature: Signature,
771 slot: u64,
772 tx_index: u64,
773 block_time_us: Option<i64>,
774 grpc_recv_us: i64,
775) -> Option<DexEvent> {
776 let metadata = EventMetadata {
777 signature,
778 slot,
779 tx_index,
780 block_time_us: block_time_us.unwrap_or(0),
781 grpc_recv_us,
782 recent_blockhash: None,
783 };
784 parse_migrate_bonding_curve_creator_from_data(data, metadata)
785}
786
787#[inline(always)]
788fn parse_create_fee_sharing_config_event_optimized(
789 data: &[u8],
790 signature: Signature,
791 slot: u64,
792 tx_index: u64,
793 block_time_us: Option<i64>,
794 grpc_recv_us: i64,
795) -> Option<DexEvent> {
796 let metadata = EventMetadata {
797 signature,
798 slot,
799 tx_index,
800 block_time_us: block_time_us.unwrap_or(0),
801 grpc_recv_us,
802 recent_blockhash: None,
803 };
804 crate::logs::pump_fees::parse_create_fee_sharing_config_from_data(data, metadata)
805}
806
807#[inline(always)]
815pub fn get_event_type_fast(log: &str) -> Option<u64> {
816 extract_discriminator_simd(log)
817}
818
819#[inline(always)]
821pub fn is_event_type(log: &str, discriminator: u64) -> bool {
822 extract_discriminator_simd(log) == Some(discriminator)
823}
824
825#[inline(always)]
841pub fn parse_trade_from_data(
842 data: &[u8],
843 metadata: EventMetadata,
844 is_created_buy: bool,
845) -> Option<DexEvent> {
846 unsafe {
847 if data.len() < 32 + 8 + 8 + 1 + 32 + 8 + 8 + 8 + 8 + 8 + 32 + 8 + 8 + 32 + 8 + 8 {
849 return None;
850 }
851
852 let mut offset = 0;
853
854 let mint = read_pubkey_unchecked(data, offset);
855 offset += 32;
856
857 let sol_amount = read_u64_unchecked(data, offset);
858 offset += 8;
859
860 let token_amount = read_u64_unchecked(data, offset);
861 offset += 8;
862
863 let is_buy = read_bool_unchecked(data, offset);
864 offset += 1;
865
866 let user = read_pubkey_unchecked(data, offset);
867 offset += 32;
868
869 let timestamp = read_i64_unchecked(data, offset);
870 offset += 8;
871
872 let virtual_sol_reserves = read_u64_unchecked(data, offset);
873 offset += 8;
874
875 let virtual_token_reserves = read_u64_unchecked(data, offset);
876 offset += 8;
877
878 let real_sol_reserves = read_u64_unchecked(data, offset);
879 offset += 8;
880
881 let real_token_reserves = read_u64_unchecked(data, offset);
882 offset += 8;
883
884 let fee_recipient = read_pubkey_unchecked(data, offset);
885 offset += 32;
886
887 let fee_basis_points = read_u64_unchecked(data, offset);
888 offset += 8;
889
890 let fee = read_u64_unchecked(data, offset);
891 offset += 8;
892
893 let creator = read_pubkey_unchecked(data, offset);
894 offset += 32;
895
896 let creator_fee_basis_points = read_u64_unchecked(data, offset);
897 offset += 8;
898
899 let creator_fee = read_u64_unchecked(data, offset);
900 offset += 8;
901
902 let track_volume =
904 if offset < data.len() { read_bool_unchecked(data, offset) } else { false };
905 offset += 1;
906
907 let total_unclaimed_tokens =
908 if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
909 offset += 8;
910
911 let total_claimed_tokens =
912 if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
913 offset += 8;
914
915 let current_sol_volume =
916 if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
917 offset += 8;
918
919 let last_update_timestamp =
920 if offset + 8 <= data.len() { read_i64_unchecked(data, offset) } else { 0 };
921 offset += 8;
922
923 let ix_name = if offset + 4 <= data.len() {
924 if let Some((s, len)) = read_str_unchecked(data, offset) {
925 offset += len;
926 s.to_string()
927 } else {
928 String::new()
929 }
930 } else {
931 String::new()
932 };
933 let ix_kind = normalize_pumpfun_ix_name(&ix_name);
934
935 let mayhem_mode =
937 if offset < data.len() { read_bool_unchecked(data, offset) } else { false };
938 offset += 1;
939 let cashback_fee_basis_points =
940 if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
941 offset += 8;
942 let cashback = if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
943 offset += 8;
944 let (
945 buyback_fee_basis_points,
946 buyback_fee,
947 shareholders,
948 quote_mint,
949 quote_amount,
950 virtual_quote_reserves,
951 real_quote_reserves,
952 holder_rewards_bps,
953 holder_rewards,
954 ) = read_trade_event_extensions(data, &mut offset)?;
955
956 let trade_event = PumpFunTradeEvent {
957 metadata,
958 mint,
959 sol_amount,
960 token_amount,
961 is_buy,
962 is_created_buy,
963 user,
964 timestamp,
965 virtual_sol_reserves,
966 virtual_token_reserves,
967 real_sol_reserves,
968 real_token_reserves,
969 fee_recipient,
970 fee_basis_points,
971 fee,
972 creator,
973 creator_fee_basis_points,
974 creator_fee,
975 track_volume,
976 total_unclaimed_tokens,
977 total_claimed_tokens,
978 current_sol_volume,
979 last_update_timestamp,
980 ix_name: ix_name.clone(),
981 mayhem_mode,
982 cashback_fee_basis_points,
983 cashback,
984 buyback_fee_basis_points,
985 buyback_fee,
986 shareholders,
987 quote_mint,
988 quote_amount,
989 virtual_quote_reserves,
990 real_quote_reserves,
991 holder_rewards_bps,
992 holder_rewards,
993 is_cashback_coin: cashback_fee_basis_points > 0,
994 amount: 0,
995 max_sol_cost: 0,
996 min_sol_output: 0,
997 spendable_sol_in: 0,
998 spendable_quote_in: 0,
999 min_tokens_out: 0,
1000 global: Pubkey::default(),
1001 bonding_curve: Pubkey::default(),
1002 associated_bonding_curve: Pubkey::default(),
1003 associated_user: Pubkey::default(),
1004 system_program: Pubkey::default(),
1005 creator_vault: Pubkey::default(),
1006 event_authority: Pubkey::default(),
1007 program: Pubkey::default(),
1008 global_volume_accumulator: Pubkey::default(),
1009 user_volume_accumulator: Pubkey::default(),
1010 fee_config: Pubkey::default(),
1011 fee_program: Pubkey::default(),
1012 token_program: Pubkey::default(),
1013 account: None,
1014 ..Default::default()
1015 };
1016
1017 match ix_kind {
1019 "buy" => Some(DexEvent::PumpFunBuy(trade_event)),
1020 "sell" => Some(DexEvent::PumpFunSell(trade_event)),
1021 "buy_exact_sol_in" => Some(DexEvent::PumpFunBuyExactSolIn(trade_event)),
1022 "buy_exact_quote_in" => Some(DexEvent::PumpFunBuy(trade_event)),
1023 _ => Some(DexEvent::PumpFunTrade(trade_event)),
1024 }
1025 }
1026}
1027
1028#[inline(always)]
1032pub fn parse_buy_from_data(
1033 data: &[u8],
1034 metadata: EventMetadata,
1035 is_created_buy: bool,
1036) -> Option<DexEvent> {
1037 let event = parse_trade_from_data(data, metadata, is_created_buy)?;
1038 match &event {
1039 DexEvent::PumpFunBuy(_) => Some(event),
1040 _ => None,
1041 }
1042}
1043
1044#[inline(always)]
1048pub fn parse_sell_from_data(
1049 data: &[u8],
1050 metadata: EventMetadata,
1051 is_created_buy: bool,
1052) -> Option<DexEvent> {
1053 let event = parse_trade_from_data(data, metadata, is_created_buy)?;
1054 match &event {
1055 DexEvent::PumpFunSell(_) => Some(event),
1056 _ => None,
1057 }
1058}
1059
1060#[inline(always)]
1064pub fn parse_buy_exact_sol_in_from_data(
1065 data: &[u8],
1066 metadata: EventMetadata,
1067 is_created_buy: bool,
1068) -> Option<DexEvent> {
1069 let event = parse_trade_from_data(data, metadata, is_created_buy)?;
1070 match &event {
1071 DexEvent::PumpFunBuyExactSolIn(_) => Some(event),
1072 _ => None,
1073 }
1074}
1075
1076#[inline(always)]
1078pub fn parse_create_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
1079 unsafe {
1080 let mut offset = 0;
1081
1082 let (name, name_len) = read_str_unchecked(data, offset)?;
1083 offset += name_len;
1084
1085 let (symbol, symbol_len) = read_str_unchecked(data, offset)?;
1086 offset += symbol_len;
1087
1088 let (uri, uri_len) = read_str_unchecked(data, offset)?;
1089 offset += uri_len;
1090
1091 if data.len() - offset == 96 {
1092 return Some(DexEvent::PumpFunCreate(PumpFunCreateTokenEvent {
1093 metadata,
1094 name: name.to_string(),
1095 symbol: symbol.to_string(),
1096 uri: uri.to_string(),
1097 mint: read_pubkey_unchecked(data, offset),
1098 bonding_curve: read_pubkey_unchecked(data, offset + 32),
1099 user: read_pubkey_unchecked(data, offset + 64),
1100 ix_name: "create".into(),
1101 quote_mint: PUMPFUN_SOLSCAN_SOL_QUOTE_MINT,
1102 ..Default::default()
1103 }));
1104 }
1105 if data.len() < offset + 32 + 32 + 32 + 32 + 8 + 8 + 8 + 8 + 8 + 32 + 1 {
1106 return None;
1107 }
1108
1109 let mint = read_pubkey_unchecked(data, offset);
1110 offset += 32;
1111
1112 let bonding_curve = read_pubkey_unchecked(data, offset);
1113 offset += 32;
1114
1115 let user = read_pubkey_unchecked(data, offset);
1116 offset += 32;
1117
1118 let creator = read_pubkey_unchecked(data, offset);
1119 offset += 32;
1120
1121 let timestamp = read_i64_unchecked(data, offset);
1122 offset += 8;
1123
1124 let virtual_token_reserves = read_u64_unchecked(data, offset);
1125 offset += 8;
1126
1127 let virtual_sol_reserves = read_u64_unchecked(data, offset);
1128 offset += 8;
1129
1130 let real_token_reserves = read_u64_unchecked(data, offset);
1131 offset += 8;
1132
1133 let token_total_supply = read_u64_unchecked(data, offset);
1134 offset += 8;
1135
1136 let token_program = if offset + 32 <= data.len() {
1137 read_pubkey_unchecked(data, offset)
1138 } else {
1139 Pubkey::default()
1140 };
1141 offset += 32;
1142
1143 let is_mayhem_mode =
1144 if offset < data.len() { read_bool_unchecked(data, offset) } else { false };
1145 offset += 1;
1146 let is_cashback_enabled =
1147 if offset < data.len() { read_bool_unchecked(data, offset) } else { false };
1148 offset += 1;
1149 let quote_mint = normalize_pumpfun_quote_mint(if offset + 32 <= data.len() {
1150 read_pubkey_unchecked(data, offset)
1151 } else {
1152 Pubkey::default()
1153 });
1154 offset += 32;
1155 let virtual_quote_reserves =
1156 if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
1157 offset += 8;
1158 let creator_fee_bps =
1159 if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
1160 offset += 8;
1161 let is_holder_reward =
1162 if offset < data.len() { read_bool_unchecked(data, offset) } else { false };
1163
1164 Some(DexEvent::PumpFunCreate(PumpFunCreateTokenEvent {
1165 metadata,
1166 name: name.to_string(),
1167 symbol: symbol.to_string(),
1168 uri: uri.to_string(),
1169 mint,
1170 bonding_curve,
1171 user,
1172 creator,
1173 timestamp,
1174 virtual_token_reserves,
1175 virtual_sol_reserves,
1176 real_token_reserves,
1177 token_total_supply,
1178 token_program,
1179 is_mayhem_mode,
1180 is_cashback_enabled,
1181 quote_mint,
1182 virtual_quote_reserves,
1183 creator_fee_bps,
1184 is_holder_reward,
1185 ix_name: "create".to_string(),
1186 ..Default::default()
1187 }))
1188 }
1189}
1190
1191#[inline(always)]
1193pub fn parse_migrate_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
1194 unsafe {
1195 if data.len() < 32 + 32 + 8 + 8 + 8 + 32 + 8 + 32 {
1196 return None;
1197 }
1198
1199 let mut offset = 0;
1200
1201 let user = read_pubkey_unchecked(data, offset);
1202 offset += 32;
1203
1204 let mint = read_pubkey_unchecked(data, offset);
1205 offset += 32;
1206
1207 let mint_amount = read_u64_unchecked(data, offset);
1208 offset += 8;
1209
1210 let sol_amount = read_u64_unchecked(data, offset);
1211 offset += 8;
1212
1213 let pool_migration_fee = read_u64_unchecked(data, offset);
1214 offset += 8;
1215
1216 let bonding_curve = read_pubkey_unchecked(data, offset);
1217 offset += 32;
1218
1219 let timestamp = read_i64_unchecked(data, offset);
1220 offset += 8;
1221
1222 let pool = read_pubkey_unchecked(data, offset);
1223
1224 Some(DexEvent::PumpFunMigrate(PumpFunMigrateEvent {
1225 metadata,
1226 user,
1227 mint,
1228 mint_amount,
1229 sol_amount,
1230 pool_migration_fee,
1231 bonding_curve,
1232 timestamp,
1233 pool,
1234 }))
1235 }
1236}
1237
1238#[inline(always)]
1240pub fn parse_migrate_bonding_curve_creator_from_data(
1241 data: &[u8],
1242 metadata: EventMetadata,
1243) -> Option<DexEvent> {
1244 unsafe {
1245 const NEED: usize = 8 + 32 * 5;
1246 if data.len() < NEED {
1247 return None;
1248 }
1249
1250 let mut offset = 0usize;
1251 let timestamp = read_i64_unchecked(data, offset);
1252 offset += 8;
1253 let mint = read_pubkey_unchecked(data, offset);
1254 offset += 32;
1255 let bonding_curve = read_pubkey_unchecked(data, offset);
1256 offset += 32;
1257 let sharing_config = read_pubkey_unchecked(data, offset);
1258 offset += 32;
1259 let old_creator = read_pubkey_unchecked(data, offset);
1260 offset += 32;
1261 let new_creator = read_pubkey_unchecked(data, offset);
1262
1263 Some(DexEvent::PumpFunMigrateBondingCurveCreator(PumpFunMigrateBondingCurveCreatorEvent {
1264 metadata,
1265 timestamp,
1266 mint,
1267 bonding_curve,
1268 sharing_config,
1269 old_creator,
1270 new_creator,
1271 }))
1272 }
1273}
1274
1275#[inline]
1277pub fn parse_create_fee_sharing_config_from_data(
1278 data: &[u8],
1279 metadata: EventMetadata,
1280) -> Option<DexEvent> {
1281 crate::logs::pump_fees::parse_create_fee_sharing_config_from_data(data, metadata)
1282}
1283
1284#[inline(always)]
1285fn read_i64_at(data: &[u8], o: &mut usize) -> Option<i64> {
1286 if data.len() < *o + 8 {
1287 return None;
1288 }
1289 let v = i64::from_le_bytes(data[*o..*o + 8].try_into().ok()?);
1290 *o += 8;
1291 Some(v)
1292}
1293
1294#[inline(always)]
1295fn read_u16_at(data: &[u8], o: &mut usize) -> Option<u16> {
1296 if data.len() < *o + 2 {
1297 return None;
1298 }
1299 let v = u16::from_le_bytes(data[*o..*o + 2].try_into().ok()?);
1300 *o += 2;
1301 Some(v)
1302}
1303
1304#[inline(always)]
1305fn read_u32_at(data: &[u8], o: &mut usize) -> Option<u32> {
1306 if data.len() < *o + 4 {
1307 return None;
1308 }
1309 let v = u32::from_le_bytes(data[*o..*o + 4].try_into().ok()?);
1310 *o += 4;
1311 Some(v)
1312}
1313
1314#[inline(always)]
1315fn read_pubkey_at(data: &[u8], o: &mut usize) -> Option<Pubkey> {
1316 if data.len() < *o + 32 {
1317 return None;
1318 }
1319 let pk = Pubkey::new_from_array(data[*o..*o + 32].try_into().ok()?);
1320 *o += 32;
1321 Some(pk)
1322}
1323
1324#[cfg(feature = "perf-stats")]
1329pub fn get_perf_stats() -> (usize, usize) {
1330 let count = PARSE_COUNT.load(Ordering::Relaxed);
1331 let total_ns = PARSE_TIME_NS.load(Ordering::Relaxed);
1332 (count, total_ns)
1333}
1334
1335#[cfg(feature = "perf-stats")]
1336pub fn reset_perf_stats() {
1337 PARSE_COUNT.store(0, Ordering::Relaxed);
1338 PARSE_TIME_NS.store(0, Ordering::Relaxed);
1339}
1340
1341#[cfg(test)]
1342mod tests {
1343 use super::*;
1344 use crate::core::events::{DexEvent, EventMetadata};
1345
1346 #[test]
1347 fn test_discriminator_simd() {
1348 let log = "Program data: G3Kp5Dfe605nAAAAAAAAAAA=";
1350 let disc = extract_discriminator_simd(log);
1351 assert!(disc.is_some());
1352 }
1353
1354 #[test]
1355 fn test_parse_performance() {
1356 let log = "Program data: G3Kp5Dfe605nAAAAAAAAAAA=";
1358 let sig = Signature::default();
1359
1360 let start = std::time::Instant::now();
1361 for _ in 0..1000 {
1362 let _ = parse_log(log, sig, 0, 0, Some(0), 0, false);
1363 }
1364 let elapsed = start.elapsed();
1365
1366 println!("Average parse time: {} ns", elapsed.as_nanos() / 1000);
1367 }
1368
1369 #[test]
1370 fn migrate_bonding_curve_creator_roundtrip_from_data() {
1371 let ts: i64 = 1_777_920_719;
1372 let mint = Pubkey::new_unique();
1373 let bonding_curve = Pubkey::new_unique();
1374 let sharing_config = Pubkey::new_unique();
1375 let old_creator = Pubkey::new_unique();
1376 let new_creator = Pubkey::new_unique();
1377
1378 let mut buf = Vec::with_capacity(200);
1379 buf.extend_from_slice(&ts.to_le_bytes());
1380 buf.extend_from_slice(mint.as_ref());
1381 buf.extend_from_slice(bonding_curve.as_ref());
1382 buf.extend_from_slice(sharing_config.as_ref());
1383 buf.extend_from_slice(old_creator.as_ref());
1384 buf.extend_from_slice(new_creator.as_ref());
1385
1386 let metadata = EventMetadata {
1387 signature: Signature::default(),
1388 slot: 0,
1389 tx_index: 0,
1390 block_time_us: 0,
1391 grpc_recv_us: 0,
1392 recent_blockhash: None,
1393 };
1394
1395 let ev = parse_migrate_bonding_curve_creator_from_data(&buf, metadata).expect("parse");
1396 match ev {
1397 DexEvent::PumpFunMigrateBondingCurveCreator(e) => {
1398 assert_eq!(e.timestamp, ts);
1399 assert_eq!(e.mint, mint);
1400 assert_eq!(e.bonding_curve, bonding_curve);
1401 assert_eq!(e.sharing_config, sharing_config);
1402 assert_eq!(e.old_creator, old_creator);
1403 assert_eq!(e.new_creator, new_creator);
1404 }
1405 _ => panic!("wrong variant"),
1406 }
1407 }
1408}