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