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 ..Default::default()
454 }))
455 }
456}
457
458#[inline(always)]
467fn parse_trade_event_optimized(
468 data: &[u8],
469 signature: Signature,
470 slot: u64,
471 tx_index: u64,
472 block_time_us: Option<i64>,
473 grpc_recv_us: i64,
474 is_created_buy: bool,
475) -> Option<DexEvent> {
476 unsafe {
477 if data.len() < 32 + 8 + 8 + 1 + 32 + 8 + 8 + 8 + 8 + 8 + 32 + 8 + 8 + 32 + 8 + 8 {
479 return None;
480 }
481
482 let mut offset = 0;
483
484 let mint = read_pubkey_unchecked(data, offset);
485 offset += 32;
486
487 let sol_amount = read_u64_unchecked(data, offset);
488 offset += 8;
489
490 let token_amount = read_u64_unchecked(data, offset);
491 offset += 8;
492
493 let is_buy = read_bool_unchecked(data, offset);
494 offset += 1;
495
496 let user = read_pubkey_unchecked(data, offset);
497 offset += 32;
498
499 let timestamp = read_i64_unchecked(data, offset);
500 offset += 8;
501
502 let virtual_sol_reserves = read_u64_unchecked(data, offset);
503 offset += 8;
504
505 let virtual_token_reserves = read_u64_unchecked(data, offset);
506 offset += 8;
507
508 let real_sol_reserves = read_u64_unchecked(data, offset);
509 offset += 8;
510
511 let real_token_reserves = read_u64_unchecked(data, offset);
512 offset += 8;
513
514 let fee_recipient = read_pubkey_unchecked(data, offset);
515 offset += 32;
516
517 let fee_basis_points = read_u64_unchecked(data, offset);
518 offset += 8;
519
520 let fee = read_u64_unchecked(data, offset);
521 offset += 8;
522
523 let creator = read_pubkey_unchecked(data, offset);
524 offset += 32;
525
526 let creator_fee_basis_points = read_u64_unchecked(data, offset);
527 offset += 8;
528
529 let creator_fee = read_u64_unchecked(data, offset);
530 offset += 8;
531
532 let track_volume =
534 if offset < data.len() { read_bool_unchecked(data, offset) } else { false };
535 offset += 1;
536
537 let total_unclaimed_tokens =
538 if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
539 offset += 8;
540
541 let total_claimed_tokens =
542 if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
543 offset += 8;
544
545 let current_sol_volume =
546 if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
547 offset += 8;
548
549 let last_update_timestamp =
550 if offset + 8 <= data.len() { read_i64_unchecked(data, offset) } else { 0 };
551 offset += 8;
552
553 let ix_name = if offset + 4 <= data.len() {
556 if let Some((s, len)) = read_str_unchecked(data, offset) {
557 offset += len;
558 s.to_string()
559 } else {
560 String::new()
561 }
562 } else {
563 String::new()
564 };
565 let ix_kind = normalize_pumpfun_ix_name(&ix_name);
566
567 let mayhem_mode =
569 if offset < data.len() { read_bool_unchecked(data, offset) } else { false };
570 offset += 1;
571 let cashback_fee_basis_points =
572 if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
573 offset += 8;
574 let cashback = if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
575 offset += 8;
576 let (
577 buyback_fee_basis_points,
578 buyback_fee,
579 shareholders,
580 quote_mint,
581 quote_amount,
582 virtual_quote_reserves,
583 real_quote_reserves,
584 ) = read_trade_event_extensions(data, &mut offset)?;
585
586 let metadata = EventMetadata {
587 signature,
588 slot,
589 tx_index,
590 block_time_us: block_time_us.unwrap_or(0),
591 grpc_recv_us,
592 recent_blockhash: None,
593 };
594
595 let trade_event = PumpFunTradeEvent {
596 metadata,
597 mint,
598 sol_amount,
599 token_amount,
600 is_buy,
601 is_created_buy,
602 user,
603 timestamp,
604 virtual_sol_reserves,
605 virtual_token_reserves,
606 real_sol_reserves,
607 real_token_reserves,
608 fee_recipient,
609 fee_basis_points,
610 fee,
611 creator,
612 creator_fee_basis_points,
613 creator_fee,
614 track_volume,
615 total_unclaimed_tokens,
616 total_claimed_tokens,
617 current_sol_volume,
618 last_update_timestamp,
619 ix_name: ix_name.clone(),
620 mayhem_mode,
621 cashback_fee_basis_points,
622 cashback,
623 buyback_fee_basis_points,
624 buyback_fee,
625 shareholders,
626 quote_mint,
627 quote_amount,
628 virtual_quote_reserves,
629 real_quote_reserves,
630 is_cashback_coin: cashback_fee_basis_points > 0,
631 amount: 0,
632 max_sol_cost: 0,
633 min_sol_output: 0,
634 spendable_sol_in: 0,
635 spendable_quote_in: 0,
636 min_tokens_out: 0,
637 global: Pubkey::default(),
638 bonding_curve: Pubkey::default(),
639 associated_bonding_curve: Pubkey::default(),
640 associated_user: Pubkey::default(),
641 system_program: Pubkey::default(),
642 creator_vault: Pubkey::default(),
643 event_authority: Pubkey::default(),
644 program: Pubkey::default(),
645 global_volume_accumulator: Pubkey::default(),
646 user_volume_accumulator: Pubkey::default(),
647 fee_config: Pubkey::default(),
648 fee_program: Pubkey::default(),
649 token_program: Pubkey::default(),
650 account: None,
651 ..Default::default()
652 };
653
654 match ix_kind {
656 "buy" => Some(DexEvent::PumpFunBuy(trade_event)),
657 "sell" => Some(DexEvent::PumpFunSell(trade_event)),
658 "buy_exact_sol_in" => Some(DexEvent::PumpFunBuyExactSolIn(trade_event)),
659 "buy_exact_quote_in" => Some(DexEvent::PumpFunBuy(trade_event)),
660 _ => Some(DexEvent::PumpFunTrade(trade_event)), }
662 }
663}
664
665#[inline(always)]
667fn parse_migrate_event_optimized(
668 data: &[u8],
669 signature: Signature,
670 slot: u64,
671 tx_index: u64,
672 block_time_us: Option<i64>,
673 grpc_recv_us: i64,
674) -> Option<DexEvent> {
675 unsafe {
676 if data.len() < 32 + 32 + 8 + 8 + 8 + 32 + 8 + 32 {
678 return None;
679 }
680
681 let mut offset = 0;
682
683 let user = read_pubkey_unchecked(data, offset);
684 offset += 32;
685
686 let mint = read_pubkey_unchecked(data, offset);
687 offset += 32;
688
689 let mint_amount = read_u64_unchecked(data, offset);
690 offset += 8;
691
692 let sol_amount = read_u64_unchecked(data, offset);
693 offset += 8;
694
695 let pool_migration_fee = read_u64_unchecked(data, offset);
696 offset += 8;
697
698 let bonding_curve = read_pubkey_unchecked(data, offset);
699 offset += 32;
700
701 let timestamp = read_i64_unchecked(data, offset);
702 offset += 8;
703
704 let pool = read_pubkey_unchecked(data, offset);
705
706 let metadata = EventMetadata {
707 signature,
708 slot,
709 tx_index,
710 block_time_us: block_time_us.unwrap_or(0),
711 grpc_recv_us,
712 recent_blockhash: None,
713 };
714
715 Some(DexEvent::PumpFunMigrate(PumpFunMigrateEvent {
716 metadata,
717 user,
718 mint,
719 mint_amount,
720 sol_amount,
721 pool_migration_fee,
722 bonding_curve,
723 timestamp,
724 pool,
725 }))
726 }
727}
728
729#[inline(always)]
730fn parse_migrate_bonding_curve_creator_event_optimized(
731 data: &[u8],
732 signature: Signature,
733 slot: u64,
734 tx_index: u64,
735 block_time_us: Option<i64>,
736 grpc_recv_us: i64,
737) -> Option<DexEvent> {
738 let metadata = EventMetadata {
739 signature,
740 slot,
741 tx_index,
742 block_time_us: block_time_us.unwrap_or(0),
743 grpc_recv_us,
744 recent_blockhash: None,
745 };
746 parse_migrate_bonding_curve_creator_from_data(data, metadata)
747}
748
749#[inline(always)]
750fn parse_create_fee_sharing_config_event_optimized(
751 data: &[u8],
752 signature: Signature,
753 slot: u64,
754 tx_index: u64,
755 block_time_us: Option<i64>,
756 grpc_recv_us: i64,
757) -> Option<DexEvent> {
758 let metadata = EventMetadata {
759 signature,
760 slot,
761 tx_index,
762 block_time_us: block_time_us.unwrap_or(0),
763 grpc_recv_us,
764 recent_blockhash: None,
765 };
766 crate::logs::pump_fees::parse_create_fee_sharing_config_from_data(data, metadata)
767}
768
769#[inline(always)]
777pub fn get_event_type_fast(log: &str) -> Option<u64> {
778 extract_discriminator_simd(log)
779}
780
781#[inline(always)]
783pub fn is_event_type(log: &str, discriminator: u64) -> bool {
784 extract_discriminator_simd(log) == Some(discriminator)
785}
786
787#[inline(always)]
803pub fn parse_trade_from_data(
804 data: &[u8],
805 metadata: EventMetadata,
806 is_created_buy: bool,
807) -> Option<DexEvent> {
808 unsafe {
809 if data.len() < 32 + 8 + 8 + 1 + 32 + 8 + 8 + 8 + 8 + 8 + 32 + 8 + 8 + 32 + 8 + 8 {
811 return None;
812 }
813
814 let mut offset = 0;
815
816 let mint = read_pubkey_unchecked(data, offset);
817 offset += 32;
818
819 let sol_amount = read_u64_unchecked(data, offset);
820 offset += 8;
821
822 let token_amount = read_u64_unchecked(data, offset);
823 offset += 8;
824
825 let is_buy = read_bool_unchecked(data, offset);
826 offset += 1;
827
828 let user = read_pubkey_unchecked(data, offset);
829 offset += 32;
830
831 let timestamp = read_i64_unchecked(data, offset);
832 offset += 8;
833
834 let virtual_sol_reserves = read_u64_unchecked(data, offset);
835 offset += 8;
836
837 let virtual_token_reserves = read_u64_unchecked(data, offset);
838 offset += 8;
839
840 let real_sol_reserves = read_u64_unchecked(data, offset);
841 offset += 8;
842
843 let real_token_reserves = read_u64_unchecked(data, offset);
844 offset += 8;
845
846 let fee_recipient = read_pubkey_unchecked(data, offset);
847 offset += 32;
848
849 let fee_basis_points = read_u64_unchecked(data, offset);
850 offset += 8;
851
852 let fee = read_u64_unchecked(data, offset);
853 offset += 8;
854
855 let creator = read_pubkey_unchecked(data, offset);
856 offset += 32;
857
858 let creator_fee_basis_points = read_u64_unchecked(data, offset);
859 offset += 8;
860
861 let creator_fee = read_u64_unchecked(data, offset);
862 offset += 8;
863
864 let track_volume =
866 if offset < data.len() { read_bool_unchecked(data, offset) } else { false };
867 offset += 1;
868
869 let total_unclaimed_tokens =
870 if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
871 offset += 8;
872
873 let total_claimed_tokens =
874 if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
875 offset += 8;
876
877 let current_sol_volume =
878 if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
879 offset += 8;
880
881 let last_update_timestamp =
882 if offset + 8 <= data.len() { read_i64_unchecked(data, offset) } else { 0 };
883 offset += 8;
884
885 let ix_name = if offset + 4 <= data.len() {
886 if let Some((s, len)) = read_str_unchecked(data, offset) {
887 offset += len;
888 s.to_string()
889 } else {
890 String::new()
891 }
892 } else {
893 String::new()
894 };
895 let ix_kind = normalize_pumpfun_ix_name(&ix_name);
896
897 let mayhem_mode =
899 if offset < data.len() { read_bool_unchecked(data, offset) } else { false };
900 offset += 1;
901 let cashback_fee_basis_points =
902 if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
903 offset += 8;
904 let cashback = if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
905 offset += 8;
906 let (
907 buyback_fee_basis_points,
908 buyback_fee,
909 shareholders,
910 quote_mint,
911 quote_amount,
912 virtual_quote_reserves,
913 real_quote_reserves,
914 ) = read_trade_event_extensions(data, &mut offset)?;
915
916 let trade_event = PumpFunTradeEvent {
917 metadata,
918 mint,
919 sol_amount,
920 token_amount,
921 is_buy,
922 is_created_buy,
923 user,
924 timestamp,
925 virtual_sol_reserves,
926 virtual_token_reserves,
927 real_sol_reserves,
928 real_token_reserves,
929 fee_recipient,
930 fee_basis_points,
931 fee,
932 creator,
933 creator_fee_basis_points,
934 creator_fee,
935 track_volume,
936 total_unclaimed_tokens,
937 total_claimed_tokens,
938 current_sol_volume,
939 last_update_timestamp,
940 ix_name: ix_name.clone(),
941 mayhem_mode,
942 cashback_fee_basis_points,
943 cashback,
944 buyback_fee_basis_points,
945 buyback_fee,
946 shareholders,
947 quote_mint,
948 quote_amount,
949 virtual_quote_reserves,
950 real_quote_reserves,
951 is_cashback_coin: cashback_fee_basis_points > 0,
952 amount: 0,
953 max_sol_cost: 0,
954 min_sol_output: 0,
955 spendable_sol_in: 0,
956 spendable_quote_in: 0,
957 min_tokens_out: 0,
958 global: Pubkey::default(),
959 bonding_curve: Pubkey::default(),
960 associated_bonding_curve: Pubkey::default(),
961 associated_user: Pubkey::default(),
962 system_program: Pubkey::default(),
963 creator_vault: Pubkey::default(),
964 event_authority: Pubkey::default(),
965 program: Pubkey::default(),
966 global_volume_accumulator: Pubkey::default(),
967 user_volume_accumulator: Pubkey::default(),
968 fee_config: Pubkey::default(),
969 fee_program: Pubkey::default(),
970 token_program: Pubkey::default(),
971 account: None,
972 ..Default::default()
973 };
974
975 match ix_kind {
977 "buy" => Some(DexEvent::PumpFunBuy(trade_event)),
978 "sell" => Some(DexEvent::PumpFunSell(trade_event)),
979 "buy_exact_sol_in" => Some(DexEvent::PumpFunBuyExactSolIn(trade_event)),
980 "buy_exact_quote_in" => Some(DexEvent::PumpFunBuy(trade_event)),
981 _ => Some(DexEvent::PumpFunTrade(trade_event)),
982 }
983 }
984}
985
986#[inline(always)]
990pub fn parse_buy_from_data(
991 data: &[u8],
992 metadata: EventMetadata,
993 is_created_buy: bool,
994) -> Option<DexEvent> {
995 let event = parse_trade_from_data(data, metadata, is_created_buy)?;
996 match &event {
997 DexEvent::PumpFunBuy(_) => Some(event),
998 _ => None,
999 }
1000}
1001
1002#[inline(always)]
1006pub fn parse_sell_from_data(
1007 data: &[u8],
1008 metadata: EventMetadata,
1009 is_created_buy: bool,
1010) -> Option<DexEvent> {
1011 let event = parse_trade_from_data(data, metadata, is_created_buy)?;
1012 match &event {
1013 DexEvent::PumpFunSell(_) => Some(event),
1014 _ => None,
1015 }
1016}
1017
1018#[inline(always)]
1022pub fn parse_buy_exact_sol_in_from_data(
1023 data: &[u8],
1024 metadata: EventMetadata,
1025 is_created_buy: bool,
1026) -> Option<DexEvent> {
1027 let event = parse_trade_from_data(data, metadata, is_created_buy)?;
1028 match &event {
1029 DexEvent::PumpFunBuyExactSolIn(_) => Some(event),
1030 _ => None,
1031 }
1032}
1033
1034#[inline(always)]
1036pub fn parse_create_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
1037 unsafe {
1038 let mut offset = 0;
1039
1040 let (name, name_len) = read_str_unchecked(data, offset)?;
1041 offset += name_len;
1042
1043 let (symbol, symbol_len) = read_str_unchecked(data, offset)?;
1044 offset += symbol_len;
1045
1046 let (uri, uri_len) = read_str_unchecked(data, offset)?;
1047 offset += uri_len;
1048
1049 if data.len() < offset + 32 + 32 + 32 + 32 + 8 + 8 + 8 + 8 + 8 + 32 + 1 {
1050 return None;
1051 }
1052
1053 let mint = read_pubkey_unchecked(data, offset);
1054 offset += 32;
1055
1056 let bonding_curve = read_pubkey_unchecked(data, offset);
1057 offset += 32;
1058
1059 let user = read_pubkey_unchecked(data, offset);
1060 offset += 32;
1061
1062 let creator = read_pubkey_unchecked(data, offset);
1063 offset += 32;
1064
1065 let timestamp = read_i64_unchecked(data, offset);
1066 offset += 8;
1067
1068 let virtual_token_reserves = read_u64_unchecked(data, offset);
1069 offset += 8;
1070
1071 let virtual_sol_reserves = read_u64_unchecked(data, offset);
1072 offset += 8;
1073
1074 let real_token_reserves = read_u64_unchecked(data, offset);
1075 offset += 8;
1076
1077 let token_total_supply = read_u64_unchecked(data, offset);
1078 offset += 8;
1079
1080 let token_program = if offset + 32 <= data.len() {
1081 read_pubkey_unchecked(data, offset)
1082 } else {
1083 Pubkey::default()
1084 };
1085 offset += 32;
1086
1087 let is_mayhem_mode =
1088 if offset < data.len() { read_bool_unchecked(data, offset) } else { false };
1089 offset += 1;
1090 let is_cashback_enabled =
1091 if offset < data.len() { read_bool_unchecked(data, offset) } else { false };
1092 offset += 1;
1093 let quote_mint = normalize_pumpfun_quote_mint(if offset + 32 <= data.len() {
1094 read_pubkey_unchecked(data, offset)
1095 } else {
1096 Pubkey::default()
1097 });
1098 offset += 32;
1099 let virtual_quote_reserves =
1100 if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
1101
1102 Some(DexEvent::PumpFunCreate(PumpFunCreateTokenEvent {
1103 metadata,
1104 name: name.to_string(),
1105 symbol: symbol.to_string(),
1106 uri: uri.to_string(),
1107 mint,
1108 bonding_curve,
1109 user,
1110 creator,
1111 timestamp,
1112 virtual_token_reserves,
1113 virtual_sol_reserves,
1114 real_token_reserves,
1115 token_total_supply,
1116 token_program,
1117 is_mayhem_mode,
1118 is_cashback_enabled,
1119 quote_mint,
1120 virtual_quote_reserves,
1121 ..Default::default()
1122 }))
1123 }
1124}
1125
1126#[inline(always)]
1128pub fn parse_migrate_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
1129 unsafe {
1130 if data.len() < 32 + 32 + 8 + 8 + 8 + 32 + 8 + 32 {
1131 return None;
1132 }
1133
1134 let mut offset = 0;
1135
1136 let user = read_pubkey_unchecked(data, offset);
1137 offset += 32;
1138
1139 let mint = read_pubkey_unchecked(data, offset);
1140 offset += 32;
1141
1142 let mint_amount = read_u64_unchecked(data, offset);
1143 offset += 8;
1144
1145 let sol_amount = read_u64_unchecked(data, offset);
1146 offset += 8;
1147
1148 let pool_migration_fee = read_u64_unchecked(data, offset);
1149 offset += 8;
1150
1151 let bonding_curve = read_pubkey_unchecked(data, offset);
1152 offset += 32;
1153
1154 let timestamp = read_i64_unchecked(data, offset);
1155 offset += 8;
1156
1157 let pool = read_pubkey_unchecked(data, offset);
1158
1159 Some(DexEvent::PumpFunMigrate(PumpFunMigrateEvent {
1160 metadata,
1161 user,
1162 mint,
1163 mint_amount,
1164 sol_amount,
1165 pool_migration_fee,
1166 bonding_curve,
1167 timestamp,
1168 pool,
1169 }))
1170 }
1171}
1172
1173#[inline(always)]
1175pub fn parse_migrate_bonding_curve_creator_from_data(
1176 data: &[u8],
1177 metadata: EventMetadata,
1178) -> Option<DexEvent> {
1179 unsafe {
1180 const NEED: usize = 8 + 32 * 5;
1181 if data.len() < NEED {
1182 return None;
1183 }
1184
1185 let mut offset = 0usize;
1186 let timestamp = read_i64_unchecked(data, offset);
1187 offset += 8;
1188 let mint = read_pubkey_unchecked(data, offset);
1189 offset += 32;
1190 let bonding_curve = read_pubkey_unchecked(data, offset);
1191 offset += 32;
1192 let sharing_config = read_pubkey_unchecked(data, offset);
1193 offset += 32;
1194 let old_creator = read_pubkey_unchecked(data, offset);
1195 offset += 32;
1196 let new_creator = read_pubkey_unchecked(data, offset);
1197
1198 Some(DexEvent::PumpFunMigrateBondingCurveCreator(PumpFunMigrateBondingCurveCreatorEvent {
1199 metadata,
1200 timestamp,
1201 mint,
1202 bonding_curve,
1203 sharing_config,
1204 old_creator,
1205 new_creator,
1206 }))
1207 }
1208}
1209
1210#[inline]
1212pub fn parse_create_fee_sharing_config_from_data(
1213 data: &[u8],
1214 metadata: EventMetadata,
1215) -> Option<DexEvent> {
1216 crate::logs::pump_fees::parse_create_fee_sharing_config_from_data(data, metadata)
1217}
1218
1219#[inline(always)]
1220fn read_i64_at(data: &[u8], o: &mut usize) -> Option<i64> {
1221 if data.len() < *o + 8 {
1222 return None;
1223 }
1224 let v = i64::from_le_bytes(data[*o..*o + 8].try_into().ok()?);
1225 *o += 8;
1226 Some(v)
1227}
1228
1229#[inline(always)]
1230fn read_u16_at(data: &[u8], o: &mut usize) -> Option<u16> {
1231 if data.len() < *o + 2 {
1232 return None;
1233 }
1234 let v = u16::from_le_bytes(data[*o..*o + 2].try_into().ok()?);
1235 *o += 2;
1236 Some(v)
1237}
1238
1239#[inline(always)]
1240fn read_u32_at(data: &[u8], o: &mut usize) -> Option<u32> {
1241 if data.len() < *o + 4 {
1242 return None;
1243 }
1244 let v = u32::from_le_bytes(data[*o..*o + 4].try_into().ok()?);
1245 *o += 4;
1246 Some(v)
1247}
1248
1249#[inline(always)]
1250fn read_pubkey_at(data: &[u8], o: &mut usize) -> Option<Pubkey> {
1251 if data.len() < *o + 32 {
1252 return None;
1253 }
1254 let pk = Pubkey::new_from_array(data[*o..*o + 32].try_into().ok()?);
1255 *o += 32;
1256 Some(pk)
1257}
1258
1259#[cfg(feature = "perf-stats")]
1264pub fn get_perf_stats() -> (usize, usize) {
1265 let count = PARSE_COUNT.load(Ordering::Relaxed);
1266 let total_ns = PARSE_TIME_NS.load(Ordering::Relaxed);
1267 (count, total_ns)
1268}
1269
1270#[cfg(feature = "perf-stats")]
1271pub fn reset_perf_stats() {
1272 PARSE_COUNT.store(0, Ordering::Relaxed);
1273 PARSE_TIME_NS.store(0, Ordering::Relaxed);
1274}
1275
1276#[cfg(test)]
1277mod tests {
1278 use super::*;
1279 use crate::core::events::{DexEvent, EventMetadata};
1280
1281 #[test]
1282 fn test_discriminator_simd() {
1283 let log = "Program data: G3Kp5Dfe605nAAAAAAAAAAA=";
1285 let disc = extract_discriminator_simd(log);
1286 assert!(disc.is_some());
1287 }
1288
1289 #[test]
1290 fn test_parse_performance() {
1291 let log = "Program data: G3Kp5Dfe605nAAAAAAAAAAA=";
1293 let sig = Signature::default();
1294
1295 let start = std::time::Instant::now();
1296 for _ in 0..1000 {
1297 let _ = parse_log(log, sig, 0, 0, Some(0), 0, false);
1298 }
1299 let elapsed = start.elapsed();
1300
1301 println!("Average parse time: {} ns", elapsed.as_nanos() / 1000);
1302 }
1303
1304 #[test]
1305 fn migrate_bonding_curve_creator_roundtrip_from_data() {
1306 let ts: i64 = 1_777_920_719;
1307 let mint = Pubkey::new_unique();
1308 let bonding_curve = Pubkey::new_unique();
1309 let sharing_config = Pubkey::new_unique();
1310 let old_creator = Pubkey::new_unique();
1311 let new_creator = Pubkey::new_unique();
1312
1313 let mut buf = Vec::with_capacity(200);
1314 buf.extend_from_slice(&ts.to_le_bytes());
1315 buf.extend_from_slice(mint.as_ref());
1316 buf.extend_from_slice(bonding_curve.as_ref());
1317 buf.extend_from_slice(sharing_config.as_ref());
1318 buf.extend_from_slice(old_creator.as_ref());
1319 buf.extend_from_slice(new_creator.as_ref());
1320
1321 let metadata = EventMetadata {
1322 signature: Signature::default(),
1323 slot: 0,
1324 tx_index: 0,
1325 block_time_us: 0,
1326 grpc_recv_us: 0,
1327 recent_blockhash: None,
1328 };
1329
1330 let ev = parse_migrate_bonding_curve_creator_from_data(&buf, metadata).expect("parse");
1331 match ev {
1332 DexEvent::PumpFunMigrateBondingCurveCreator(e) => {
1333 assert_eq!(e.timestamp, ts);
1334 assert_eq!(e.mint, mint);
1335 assert_eq!(e.bonding_curve, bonding_curve);
1336 assert_eq!(e.sharing_config, sharing_config);
1337 assert_eq!(e.old_creator, old_creator);
1338 assert_eq!(e.new_creator, new_creator);
1339 }
1340 _ => panic!("wrong variant"),
1341 }
1342 }
1343}