1use crate::core::events::*;
4use base64::{engine::general_purpose, Engine as _};
5use memchr::memmem;
6use solana_sdk::signature::Signature;
7
8#[inline(always)]
10pub fn parse_pumpfun_trade(
11 log: &str,
12 signature: Signature,
13 slot: u64,
14 tx_index: u64,
15 block_time_us: Option<i64>,
16 grpc_recv_us: i64,
17 is_created_buy: bool,
18) -> Option<DexEvent> {
19 const MAX_DECODE_SIZE: usize = 4096;
22 let mut decode_buf: [u8; MAX_DECODE_SIZE] = [0u8; MAX_DECODE_SIZE];
23
24 let log_bytes = log.as_bytes();
26 let pos = memmem::find(log_bytes, b"Program data: ")?;
27 let data_part = log[pos + 14..].trim();
28
29 if data_part.len() < 12 {
32 return None;
33 }
34
35 let disc_decoded_len = general_purpose::STANDARD
37 .decode_slice(&data_part.as_bytes()[..12], &mut decode_buf[..9])
38 .ok()?;
39
40 if disc_decoded_len < 8 {
41 return None;
42 }
43
44 const TRADE_DISCRIMINATOR: [u8; 8] = [189, 219, 127, 211, 78, 230, 97, 238];
46
47 if decode_buf[..8] != TRADE_DISCRIMINATOR {
48 return None;
49 }
50
51 let decoded_len =
53 general_purpose::STANDARD.decode_slice(data_part.as_bytes(), &mut decode_buf).ok()?;
54
55 if decoded_len < 8 {
56 return None;
57 }
58
59 let metadata = EventMetadata {
60 signature,
61 slot,
62 tx_index,
63 block_time_us: block_time_us.unwrap_or(0),
64 grpc_recv_us,
65 recent_blockhash: None,
66 };
67
68 crate::logs::pump::parse_trade_from_data(&decode_buf[8..decoded_len], metadata, is_created_buy)
69}
70
71#[cfg(test)]
72mod tests {
73 use super::*;
74 use solana_sdk::pubkey::Pubkey;
75
76 fn push_u64(out: &mut Vec<u8>, value: u64) {
77 out.extend_from_slice(&value.to_le_bytes());
78 }
79
80 fn push_i64(out: &mut Vec<u8>, value: i64) {
81 out.extend_from_slice(&value.to_le_bytes());
82 }
83
84 fn push_pubkey(out: &mut Vec<u8>, value: Pubkey) {
85 out.extend_from_slice(value.as_ref());
86 }
87
88 fn trade_log_with_latest_tail(quote_mint: Pubkey, shareholder: Pubkey) -> String {
89 let mut data = Vec::new();
90 data.extend_from_slice(&[189, 219, 127, 211, 78, 230, 97, 238]);
91 push_pubkey(&mut data, Pubkey::new_unique()); push_u64(&mut data, 1_000); push_u64(&mut data, 2_000); data.push(1); push_pubkey(&mut data, Pubkey::new_unique()); push_i64(&mut data, 123); push_u64(&mut data, 10); push_u64(&mut data, 20); push_u64(&mut data, 30); push_u64(&mut data, 40); push_pubkey(&mut data, Pubkey::new_unique()); push_u64(&mut data, 50); push_u64(&mut data, 60); push_pubkey(&mut data, Pubkey::new_unique()); push_u64(&mut data, 70); push_u64(&mut data, 80); data.push(1); push_u64(&mut data, 90); push_u64(&mut data, 100); push_u64(&mut data, 110); push_i64(&mut data, 120); data.extend_from_slice(&(6u32).to_le_bytes());
113 data.extend_from_slice(b"buy_v2");
114 data.push(1); push_u64(&mut data, 130); push_u64(&mut data, 140); push_u64(&mut data, 150); push_u64(&mut data, 160); data.extend_from_slice(&(1u32).to_le_bytes()); push_pubkey(&mut data, shareholder);
121 data.extend_from_slice(&(250u16).to_le_bytes());
122 push_pubkey(&mut data, quote_mint);
123 push_u64(&mut data, 170); push_u64(&mut data, 180); push_u64(&mut data, 190); format!("Program data: {}", general_purpose::STANDARD.encode(data))
128 }
129
130 #[test]
131 fn public_zero_copy_trade_parser_keeps_latest_tail_fields() {
132 let quote_mint = Pubkey::new_unique();
133 let shareholder = Pubkey::new_unique();
134 let log = trade_log_with_latest_tail(quote_mint, shareholder);
135 let event = parse_pumpfun_trade(&log, Signature::default(), 1, 0, Some(2), 3, true)
136 .expect("trade log");
137
138 match event {
139 DexEvent::PumpFunBuy(t) => {
140 assert_eq!(t.sol_amount, 1_000);
141 assert_eq!(t.token_amount, 2_000);
142 assert_eq!(t.ix_name, "buy_v2");
143 assert_eq!(t.buyback_fee_basis_points, 150);
144 assert_eq!(t.buyback_fee, 160);
145 assert_eq!(t.shareholders.len(), 1);
146 assert_eq!(t.shareholders[0].address, shareholder);
147 assert_eq!(t.shareholders[0].share_bps, 250);
148 assert_eq!(t.quote_mint, quote_mint);
149 assert_eq!(t.quote_amount, 170);
150 assert_eq!(t.virtual_quote_reserves, 180);
151 assert_eq!(t.real_quote_reserves, 190);
152 assert!(t.is_created_buy);
153 }
154 other => panic!("expected buy event, got {other:?}"),
155 }
156 }
157}