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