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