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