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