1use super::perf_hints::{likely, unlikely};
11use crate::core::events::{DexEvent, EventMetadata};
12use crate::grpc::types::{EventType, EventTypeFilter};
13use crate::instr::program_ids;
14use memchr::memmem;
15use once_cell::sync::Lazy;
16use solana_sdk::pubkey::Pubkey;
17use solana_sdk::signature::Signature;
18
19static PUMPFUN_FINDER: Lazy<memmem::Finder> =
21 Lazy::new(|| memmem::Finder::new(b"6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"));
22static RAYDIUM_AMM_FINDER: Lazy<memmem::Finder> =
23 Lazy::new(|| memmem::Finder::new(b"675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8"));
24static RAYDIUM_CLMM_FINDER: Lazy<memmem::Finder> =
25 Lazy::new(|| memmem::Finder::new(b"CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK"));
26static RAYDIUM_CPMM_FINDER: Lazy<memmem::Finder> =
27 Lazy::new(|| memmem::Finder::new(b"CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C"));
28static RAYDIUM_LAUNCHLAB_FINDER: Lazy<memmem::Finder> =
29 Lazy::new(|| memmem::Finder::new(b"LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj"));
30static PROGRAM_FINDER: Lazy<memmem::Finder> = Lazy::new(|| memmem::Finder::new(b"Program"));
31static PROGRAM_DATA_FINDER: Lazy<memmem::Finder> =
32 Lazy::new(|| memmem::Finder::new(b"Program data: "));
33static PUMPFUN_CREATE_FINDER: Lazy<memmem::Finder> =
34 Lazy::new(|| memmem::Finder::new(b"Program data: G3KpTd7rY3Y"));
35static WHIRL_FINDER: Lazy<memmem::Finder> = Lazy::new(|| memmem::Finder::new(b"whirL"));
36static METEORA_FINDER: Lazy<memmem::Finder> = Lazy::new(|| memmem::Finder::new(b"meteora"));
37static METEORA_LB_FINDER: Lazy<memmem::Finder> = Lazy::new(|| memmem::Finder::new(b"LB"));
38static METEORA_DLMM_FINDER: Lazy<memmem::Finder> = Lazy::new(|| memmem::Finder::new(b"DLMM"));
39static PUMPSWAP_LOWER_FINDER: Lazy<memmem::Finder> = Lazy::new(|| memmem::Finder::new(b"pumpswap"));
40static PUMPSWAP_UPPER_FINDER: Lazy<memmem::Finder> = Lazy::new(|| memmem::Finder::new(b"PumpSwap"));
41
42pub mod program_id_strings {
44 pub const PUMPFUN_INVOKE: &str = "Program 6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P invoke";
45 pub const PUMPFUN_SUCCESS: &str = "Program 6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P success";
46 pub const PUMPFUN_ID: &str = "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P";
47
48 pub const RAYDIUM_LAUNCHLAB_INVOKE: &str =
49 "Program LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj invoke";
50 pub const RAYDIUM_LAUNCHLAB_SUCCESS: &str =
51 "Program LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj success";
52 pub const RAYDIUM_LAUNCHLAB_ID: &str = "LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj";
53
54 pub const RAYDIUM_CLMM_INVOKE: &str =
55 "Program CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK invoke";
56 pub const RAYDIUM_CLMM_SUCCESS: &str =
57 "Program CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK success";
58 pub const RAYDIUM_CLMM_ID: &str = "CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK";
59
60 pub const RAYDIUM_CPMM_INVOKE: &str =
61 "Program CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C invoke";
62 pub const RAYDIUM_CPMM_SUCCESS: &str =
63 "Program CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C success";
64 pub const RAYDIUM_CPMM_ID: &str = "CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C";
65
66 pub const RAYDIUM_AMM_V4_ID: &str = "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8";
67
68 pub const PROGRAM_DATA: &str = "Program data: ";
70 pub const PROGRAM_LOG: &str = "Program log: ";
71
72 pub const PUMPFUN_CREATE_DISCRIMINATOR: &str = "GB7IKAUcB3c"; }
75
76#[derive(Debug, Copy, Clone, PartialEq)]
78pub enum LogType {
79 PumpFun,
80 RaydiumLaunchlab,
81 PumpAmm,
82 RaydiumClmm,
83 RaydiumCpmm,
84 RaydiumAmm,
85 OrcaWhirlpool,
86 MeteoraAmm,
87 MeteoraDamm,
88 MeteoraDlmm,
89 Unknown,
90}
91
92#[inline(always)]
94pub fn detect_log_type(log: &str) -> LogType {
95 let log_bytes = log.as_bytes();
96
97 if log_bytes.len() < 20 {
99 return LogType::Unknown;
100 }
101
102 let has_program_data = PROGRAM_DATA_FINDER.find(log_bytes).is_some();
104
105 if unlikely(!has_program_data) {
107 return LogType::Unknown;
108 }
109
110 if likely(RAYDIUM_AMM_FINDER.find(log_bytes).is_some()) {
113 return LogType::RaydiumAmm;
114 }
115
116 if RAYDIUM_CLMM_FINDER.find(log_bytes).is_some() {
118 return LogType::RaydiumClmm;
119 }
120
121 if RAYDIUM_CPMM_FINDER.find(log_bytes).is_some() {
123 return LogType::RaydiumCpmm;
124 }
125
126 if RAYDIUM_LAUNCHLAB_FINDER.find(log_bytes).is_some() {
128 return LogType::RaydiumLaunchlab;
129 }
130
131 if WHIRL_FINDER.find(log_bytes).is_some() {
133 return LogType::OrcaWhirlpool;
134 }
135
136 if let Some(pos) = METEORA_FINDER.find(log_bytes) {
138 let rest = &log_bytes[pos..];
139 if METEORA_LB_FINDER.find(rest).is_some() {
140 return LogType::MeteoraDamm;
141 } else if METEORA_DLMM_FINDER.find(rest).is_some() {
142 return LogType::MeteoraDlmm;
143 } else {
144 return LogType::MeteoraAmm;
145 }
146 }
147
148 if PUMPSWAP_LOWER_FINDER.find(log_bytes).is_some()
150 || PUMPSWAP_UPPER_FINDER.find(log_bytes).is_some()
151 {
152 return LogType::PumpAmm;
153 }
154
155 if likely(PUMPFUN_FINDER.find(log_bytes).is_some()) {
158 return LogType::PumpFun;
159 }
160
161 if log.len() > 30 {
165 return LogType::PumpFun;
166 }
167
168 LogType::Unknown
169}
170
171mod discriminators {
175 pub const PUMPFUN_CREATE: u64 = u64::from_le_bytes([27, 114, 169, 77, 222, 235, 99, 118]);
177 pub const PUMPFUN_TRADE: u64 = u64::from_le_bytes([189, 219, 127, 211, 78, 230, 97, 238]);
178 pub const PUMPFUN_MIGRATE: u64 = u64::from_le_bytes([189, 233, 93, 185, 92, 148, 234, 148]);
179 pub const PUMPFUN_MIGRATE_BONDING_CURVE_CREATOR: u64 =
180 u64::from_le_bytes([155, 167, 104, 220, 213, 108, 243, 3]);
181 pub const RAYDIUM_LAUNCHLAB_POOL_CREATE: u64 =
185 u64::from_le_bytes([151, 215, 226, 9, 118, 161, 115, 174]);
186 pub const RAYDIUM_LAUNCHLAB_TRADE: u64 =
187 u64::from_le_bytes([189, 219, 127, 211, 78, 230, 97, 238]);
188 pub const PUMP_FEES_CREATE_FEE_SHARING_CONFIG: u64 =
190 u64::from_le_bytes([133, 105, 170, 200, 184, 116, 251, 88]);
191 pub const PUMP_FEES_INITIALIZE_FEE_CONFIG: u64 =
192 u64::from_le_bytes([89, 138, 244, 230, 10, 56, 226, 126]);
193 pub const PUMP_FEES_RESET_FEE_SHARING_CONFIG: u64 =
194 u64::from_le_bytes([203, 204, 151, 226, 120, 55, 214, 243]);
195 pub const PUMP_FEES_REVOKE_FEE_SHARING_AUTHORITY: u64 =
196 u64::from_le_bytes([114, 23, 101, 60, 14, 190, 153, 62]);
197 pub const PUMP_FEES_TRANSFER_FEE_SHARING_AUTHORITY: u64 =
198 u64::from_le_bytes([124, 143, 198, 245, 77, 184, 8, 236]);
199 pub const PUMP_FEES_UPDATE_ADMIN: u64 =
200 u64::from_le_bytes([225, 152, 171, 87, 246, 63, 66, 234]);
201 pub const PUMP_FEES_UPDATE_FEE_CONFIG: u64 =
202 u64::from_le_bytes([90, 23, 65, 35, 62, 244, 188, 208]);
203 pub const PUMP_FEES_UPDATE_FEE_SHARES: u64 =
204 u64::from_le_bytes([21, 186, 196, 184, 91, 228, 225, 203]);
205 pub const PUMP_FEES_UPSERT_FEE_TIERS: u64 =
206 u64::from_le_bytes([171, 89, 169, 187, 122, 186, 33, 204]);
207
208 pub const PUMPSWAP_BUY: u64 = u64::from_le_bytes([103, 244, 82, 31, 44, 245, 119, 119]);
210 pub const PUMPSWAP_SELL: u64 = u64::from_le_bytes([62, 47, 55, 10, 165, 3, 220, 42]);
211 pub const PUMPSWAP_CREATE_POOL: u64 =
212 u64::from_le_bytes([177, 49, 12, 210, 160, 118, 167, 116]);
213 pub const PUMPSWAP_ADD_LIQUIDITY: u64 =
214 u64::from_le_bytes([120, 248, 61, 83, 31, 142, 107, 144]);
215 pub const PUMPSWAP_REMOVE_LIQUIDITY: u64 =
216 u64::from_le_bytes([22, 9, 133, 26, 160, 44, 71, 192]);
217
218 pub const RAYDIUM_CLMM_SWAP: u64 = u64::from_le_bytes([64, 198, 205, 232, 38, 8, 113, 226]);
220 pub const RAYDIUM_CLMM_INCREASE_LIQUIDITY: u64 =
221 u64::from_le_bytes([49, 79, 105, 212, 32, 34, 30, 84]);
222 pub const RAYDIUM_CLMM_DECREASE_LIQUIDITY: u64 =
223 u64::from_le_bytes([58, 222, 86, 58, 68, 50, 85, 56]);
224 pub const RAYDIUM_CLMM_LIQUIDITY_CHANGE: u64 =
225 u64::from_le_bytes([126, 240, 175, 206, 158, 88, 153, 107]);
226 pub const RAYDIUM_CLMM_CONFIG_CHANGE: u64 =
227 u64::from_le_bytes([247, 189, 7, 119, 106, 112, 95, 151]);
228 pub const RAYDIUM_CLMM_CREATE_PERSONAL_POSITION: u64 =
229 u64::from_le_bytes([100, 30, 87, 249, 196, 223, 154, 206]);
230 pub const RAYDIUM_CLMM_LIQUIDITY_CALCULATE: u64 =
231 u64::from_le_bytes([237, 112, 148, 230, 57, 84, 180, 162]);
232 pub const RAYDIUM_CLMM_OPEN_LIMIT_ORDER: u64 =
233 u64::from_le_bytes([106, 24, 71, 85, 57, 169, 158, 216]);
234 pub const RAYDIUM_CLMM_INCREASE_LIMIT_ORDER: u64 =
235 u64::from_le_bytes([11, 120, 13, 204, 199, 87, 19, 200]);
236 pub const RAYDIUM_CLMM_DECREASE_LIMIT_ORDER: u64 =
237 u64::from_le_bytes([70, 48, 40, 221, 219, 237, 212, 163]);
238 pub const RAYDIUM_CLMM_SETTLE_LIMIT_ORDER: u64 =
239 u64::from_le_bytes([88, 119, 77, 164, 125, 124, 10, 194]);
240 pub const RAYDIUM_CLMM_UPDATE_REWARD_INFOS: u64 =
241 u64::from_le_bytes([109, 127, 186, 78, 114, 65, 37, 236]);
242 pub const RAYDIUM_CLMM_CREATE_POOL: u64 = u64::from_le_bytes([25, 94, 75, 47, 112, 99, 53, 63]);
243 pub const RAYDIUM_CLMM_COLLECT_PERSONAL_FEE: u64 =
244 u64::from_le_bytes([166, 174, 105, 192, 81, 161, 83, 105]);
245 pub const RAYDIUM_CLMM_COLLECT_PROTOCOL_FEE: u64 =
246 u64::from_le_bytes([206, 87, 17, 79, 45, 41, 213, 61]);
247
248 pub const RAYDIUM_CPMM_SWAP_BASE_IN: u64 =
250 u64::from_le_bytes([143, 190, 90, 218, 196, 30, 51, 222]);
251 pub const RAYDIUM_CPMM_SWAP_EVENT: u64 =
252 u64::from_le_bytes([64, 198, 205, 232, 38, 8, 113, 226]);
253 pub const RAYDIUM_CPMM_SWAP_BASE_OUT: u64 =
254 u64::from_le_bytes([55, 217, 98, 86, 163, 74, 180, 173]);
255 pub const RAYDIUM_CPMM_CREATE_POOL: u64 =
256 u64::from_le_bytes([233, 146, 209, 142, 207, 104, 64, 188]);
257 pub const RAYDIUM_CPMM_DEPOSIT: u64 =
258 u64::from_le_bytes([242, 35, 198, 137, 82, 225, 242, 182]);
259 pub const RAYDIUM_CPMM_WITHDRAW: u64 =
260 u64::from_le_bytes([183, 18, 70, 156, 148, 109, 161, 34]);
261
262 pub const RAYDIUM_AMM_SWAP_BASE_IN: u64 = u64::from_le_bytes([0, 0, 0, 0, 0, 0, 0, 9]);
264 pub const RAYDIUM_AMM_SWAP_BASE_OUT: u64 = u64::from_le_bytes([0, 0, 0, 0, 0, 0, 0, 11]);
265 pub const RAYDIUM_AMM_DEPOSIT: u64 = u64::from_le_bytes([0, 0, 0, 0, 0, 0, 0, 3]);
266 pub const RAYDIUM_AMM_WITHDRAW: u64 = u64::from_le_bytes([0, 0, 0, 0, 0, 0, 0, 4]);
267 pub const RAYDIUM_AMM_INITIALIZE2: u64 = u64::from_le_bytes([0, 0, 0, 0, 0, 0, 0, 1]);
268 pub const RAYDIUM_AMM_WITHDRAW_PNL: u64 = u64::from_le_bytes([0, 0, 0, 0, 0, 0, 0, 7]);
269
270 pub const ORCA_TRADED: u64 = u64::from_le_bytes([225, 202, 73, 175, 147, 43, 160, 150]);
272 pub const ORCA_LIQUIDITY_INCREASED: u64 =
273 u64::from_le_bytes([30, 7, 144, 181, 102, 254, 155, 161]);
274 pub const ORCA_LIQUIDITY_DECREASED: u64 =
275 u64::from_le_bytes([166, 1, 36, 71, 112, 202, 181, 171]);
276 pub const ORCA_POOL_INITIALIZED: u64 =
277 u64::from_le_bytes([100, 118, 173, 87, 12, 198, 254, 229]);
278
279 pub const METEORA_AMM_SWAP: u64 = u64::from_le_bytes([81, 108, 227, 190, 205, 208, 10, 196]);
281 pub const METEORA_AMM_ADD_LIQUIDITY: u64 =
282 u64::from_le_bytes([31, 94, 125, 90, 227, 52, 61, 186]);
283 pub const METEORA_AMM_REMOVE_LIQUIDITY: u64 =
284 u64::from_le_bytes([116, 244, 97, 232, 103, 31, 152, 58]);
285 pub const METEORA_AMM_BOOTSTRAP_LIQUIDITY: u64 =
286 u64::from_le_bytes([121, 127, 38, 136, 92, 55, 14, 247]);
287 pub const METEORA_AMM_POOL_CREATED: u64 =
288 u64::from_le_bytes([202, 44, 41, 88, 104, 220, 157, 82]);
289 pub const METEORA_AMM_SET_POOL_FEES: u64 =
290 u64::from_le_bytes([245, 26, 198, 164, 88, 18, 75, 9]);
291
292 pub const METEORA_DAMM_SWAP: u64 = u64::from_le_bytes([27, 60, 21, 213, 138, 170, 187, 147]);
294 pub const METEORA_DAMM_SWAP2: u64 = u64::from_le_bytes([189, 66, 51, 168, 38, 80, 117, 153]);
295 pub const METEORA_DAMM_ADD_LIQUIDITY: u64 =
296 u64::from_le_bytes([175, 242, 8, 157, 30, 247, 185, 169]);
297 pub const METEORA_DAMM_REMOVE_LIQUIDITY: u64 =
298 u64::from_le_bytes([87, 46, 88, 98, 175, 96, 34, 91]);
299 pub const METEORA_DAMM_LIQUIDITY_CHANGE: u64 =
300 u64::from_le_bytes([197, 171, 78, 127, 224, 211, 87, 13]);
301 pub const METEORA_DAMM_INITIALIZE_POOL: u64 =
302 u64::from_le_bytes([228, 50, 246, 85, 203, 66, 134, 37]);
303 pub const METEORA_DAMM_CREATE_POSITION: u64 =
304 u64::from_le_bytes([156, 15, 119, 198, 29, 181, 221, 55]);
305 pub const METEORA_DAMM_CLOSE_POSITION: u64 =
306 u64::from_le_bytes([20, 145, 144, 68, 143, 142, 214, 178]);
307
308 pub const METEORA_DBC_SWAP: u64 = u64::from_le_bytes([27, 60, 21, 213, 138, 170, 187, 147]);
311 pub const METEORA_DBC_INITIALIZE_POOL: u64 =
312 u64::from_le_bytes([228, 50, 246, 85, 203, 66, 134, 37]);
313 pub const METEORA_DBC_CURVE_COMPLETE: u64 =
314 u64::from_le_bytes([229, 231, 86, 84, 156, 134, 75, 24]);
315
316 pub const METEORA_DLMM_SWAP: u64 = u64::from_le_bytes([81, 108, 227, 190, 205, 208, 10, 196]);
319 pub const METEORA_DLMM_SWAP2: u64 = u64::from_le_bytes([46, 116, 82, 215, 148, 27, 84, 77]);
320 pub const METEORA_DLMM_ADD_LIQUIDITY: u64 =
321 u64::from_le_bytes([31, 94, 125, 90, 227, 52, 61, 186]);
322 pub const METEORA_DLMM_REMOVE_LIQUIDITY: u64 =
323 u64::from_le_bytes([116, 244, 97, 232, 103, 31, 152, 58]);
324 pub const METEORA_DLMM_INITIALIZE_POOL: u64 =
325 u64::from_le_bytes([185, 74, 252, 125, 27, 215, 188, 111]);
326 pub const METEORA_DLMM_INITIALIZE_BIN_ARRAY: u64 =
327 u64::from_le_bytes([11, 18, 155, 194, 33, 115, 238, 119]);
328 pub const METEORA_DLMM_CREATE_POSITION: u64 =
329 u64::from_le_bytes([144, 142, 252, 84, 157, 53, 37, 121]);
330 pub const METEORA_DLMM_CLOSE_POSITION: u64 =
331 u64::from_le_bytes([255, 196, 16, 107, 28, 202, 53, 128]);
332 pub const METEORA_DLMM_CLAIM_FEE: u64 =
333 u64::from_le_bytes([75, 122, 154, 48, 140, 74, 123, 163]);
334 pub const METEORA_DLMM_CLAIM_FEE2: u64 =
335 u64::from_le_bytes([232, 171, 242, 97, 58, 77, 35, 45]);
336}
337
338#[inline(always)]
350pub fn parse_log_optimized(
352 log: &str,
353 signature: Signature,
354 slot: u64,
355 tx_index: u64,
356 block_time_us: Option<i64>,
357 grpc_recv_us: i64,
358 event_type_filter: Option<&EventTypeFilter>,
359 is_created_buy: bool,
360 recent_blockhash: Option<&[u8]>,
361) -> Option<DexEvent> {
362 parse_log_optimized_inner(
363 log,
364 signature,
365 slot,
366 tx_index,
367 block_time_us,
368 grpc_recv_us,
369 event_type_filter,
370 is_created_buy,
371 recent_blockhash,
372 None,
373 )
374}
375
376#[inline(always)]
382pub fn parse_log_optimized_with_program_id(
383 log: &str,
384 signature: Signature,
385 slot: u64,
386 tx_index: u64,
387 block_time_us: Option<i64>,
388 grpc_recv_us: i64,
389 event_type_filter: Option<&EventTypeFilter>,
390 is_created_buy: bool,
391 recent_blockhash: Option<&[u8]>,
392 program_id: Option<&Pubkey>,
393) -> Option<DexEvent> {
394 parse_log_optimized_inner(
395 log,
396 signature,
397 slot,
398 tx_index,
399 block_time_us,
400 grpc_recv_us,
401 event_type_filter,
402 is_created_buy,
403 recent_blockhash,
404 program_id,
405 )
406}
407
408#[inline(always)]
409fn decode_base64_discriminator(trimmed: &str) -> Option<u64> {
410 let bytes = trimmed.as_bytes();
411 if bytes.len() < 12 {
412 return None;
413 }
414
415 let mut discriminator_buf = [0u8; 9];
416 let decoded_len = {
417 use base64_simd::AsOut;
418 let decoded =
419 base64_simd::STANDARD.decode(&bytes[..12], discriminator_buf.as_mut().as_out()).ok()?;
420 decoded.len()
421 };
422 if decoded_len < 8 {
423 return None;
424 }
425
426 Some(unsafe { (discriminator_buf.as_ptr() as *const u64).read_unaligned() })
427}
428
429#[inline(always)]
430fn filter_includes_known_program(program_id: &Pubkey, filter: &EventTypeFilter) -> bool {
431 match *program_id {
432 program_ids::PUMPFUN_PROGRAM_ID => filter.includes_pumpfun(),
433 program_ids::PUMP_FEES_PROGRAM_ID => filter.includes_pump_fees(),
434 program_ids::PUMPSWAP_PROGRAM_ID => filter.includes_pumpswap(),
435 program_ids::RAYDIUM_LAUNCHLAB_PROGRAM_ID => filter.includes_raydium_launchlab(),
436 program_ids::RAYDIUM_CLMM_PROGRAM_ID => filter.includes_raydium_clmm(),
437 program_ids::RAYDIUM_CPMM_PROGRAM_ID => filter.includes_raydium_cpmm(),
438 program_ids::RAYDIUM_AMM_V4_PROGRAM_ID => filter.includes_raydium_amm_v4(),
439 program_ids::ORCA_WHIRLPOOL_PROGRAM_ID => filter.includes_orca_whirlpool(),
440 program_ids::METEORA_POOLS_PROGRAM_ID => filter.includes_meteora_pools(),
441 program_ids::METEORA_DAMM_V2_PROGRAM_ID => filter.includes_meteora_damm_v2(),
442 program_ids::METEORA_DLMM_PROGRAM_ID => filter.includes_meteora_dlmm(),
443 program_ids::METEORA_DBC_PROGRAM_ID => filter.includes_meteora_dbc(),
444 _ => true,
445 }
446}
447
448#[inline(always)]
449fn filter_wants_supported_logs(filter: &EventTypeFilter) -> bool {
450 filter.includes_pumpfun()
451 || filter.includes_pump_fees()
452 || filter.includes_pumpswap()
453 || filter.includes_raydium_launchlab()
454 || filter.includes_raydium_clmm()
455 || filter.includes_raydium_cpmm()
456 || filter.includes_raydium_amm_v4()
457 || filter.includes_orca_whirlpool()
458 || filter.includes_meteora_pools()
459 || filter.includes_meteora_damm_v2()
460 || filter.includes_meteora_dlmm()
461 || filter.includes_meteora_dbc()
462}
463
464#[inline(always)]
465fn filter_wants_pumpfun_trade_event(filter: &EventTypeFilter) -> bool {
466 filter.should_include(EventType::PumpFunTrade)
467 || filter.should_include(EventType::PumpFunBuy)
468 || filter.should_include(EventType::PumpFunSell)
469 || filter.should_include(EventType::PumpFunBuyExactSolIn)
470}
471
472#[inline(always)]
473fn filter_wants_launchlab_trade_event(filter: &EventTypeFilter) -> bool {
474 filter.should_include(EventType::RaydiumLaunchlabTrade)
475}
476
477#[inline(always)]
478fn unscoped_filter_allows_discriminator(discriminator: u64, filter: &EventTypeFilter) -> bool {
479 match discriminator {
480 discriminators::PUMPFUN_TRADE => {
482 filter_wants_pumpfun_trade_event(filter)
483 || filter.should_include(EventType::RaydiumLaunchlabTrade)
484 }
485 discriminators::RAYDIUM_CPMM_SWAP_BASE_IN => {
488 filter.should_include(EventType::RaydiumCpmmSwap)
489 || filter.should_include(EventType::MeteoraDlmmSwap)
490 }
491 _ => discriminator_to_event_type(discriminator)
492 .map(|event_type| filter.should_include(event_type))
493 .unwrap_or_else(|| filter_wants_supported_logs(filter)),
494 }
495}
496
497#[inline(always)]
498fn filter_allows_discriminator(
499 program_id: Option<&Pubkey>,
500 discriminator: u64,
501 event_type_filter: Option<&EventTypeFilter>,
502) -> bool {
503 let Some(filter) = event_type_filter else {
504 return true;
505 };
506
507 if let Some(program_id) = program_id {
508 if *program_id == program_ids::PUMPFUN_PROGRAM_ID
509 && discriminator == discriminators::PUMPFUN_TRADE
510 {
511 return filter_wants_pumpfun_trade_event(filter);
512 }
513 if let Some(event_type) =
514 program_scoped_discriminator_to_event_type(program_id, discriminator)
515 {
516 return filter.should_include(event_type);
517 }
518 return filter_includes_known_program(program_id, filter);
519 }
520
521 unscoped_filter_allows_discriminator(discriminator, filter)
522}
523
524#[inline(always)]
525fn apply_event_type_filter(
526 event: DexEvent,
527 event_type_filter: Option<&EventTypeFilter>,
528) -> Option<DexEvent> {
529 if let Some(filter) = event_type_filter {
530 if !filter.should_include_dex_event(&event) {
531 return None;
532 }
533 }
534 Some(event)
535}
536
537#[inline(always)]
538fn parse_unscoped_pumpfun_launchlab_trade(
539 data: &[u8],
540 metadata: EventMetadata,
541 event_type_filter: Option<&EventTypeFilter>,
542 is_created_buy: bool,
543) -> Option<DexEvent> {
544 let wants_pumpfun = event_type_filter.map(filter_wants_pumpfun_trade_event).unwrap_or(true);
545 let wants_launchlab = event_type_filter.map(filter_wants_launchlab_trade_event).unwrap_or(true);
546
547 if wants_pumpfun {
548 if let Some(event) =
549 crate::logs::pump::parse_trade_from_data(data, metadata.clone(), is_created_buy)
550 .and_then(|event| apply_event_type_filter(event, event_type_filter))
551 {
552 return Some(event);
553 }
554 }
555
556 if wants_launchlab {
557 return crate::logs::raydium_launchlab::parse_trade_from_data(data, metadata)
558 .and_then(|event| apply_event_type_filter(event, event_type_filter));
559 }
560
561 None
562}
563
564#[inline(always)]
565fn parse_log_optimized_inner(
566 log: &str,
567 signature: Signature,
568 slot: u64,
569 tx_index: u64,
570 block_time_us: Option<i64>,
571 grpc_recv_us: i64,
572 event_type_filter: Option<&EventTypeFilter>,
573 is_created_buy: bool,
574 recent_blockhash: Option<&[u8]>,
575 program_id: Option<&Pubkey>,
576) -> Option<DexEvent> {
577 if program_id == Some(&program_ids::RAYDIUM_AMM_V4_PROGRAM_ID) && log.contains("ray_log: ") {
578 if event_type_filter
579 .map(|filter| !filter.should_include(EventType::RaydiumAmmV4Swap))
580 .unwrap_or(false)
581 {
582 return None;
583 }
584 let metadata = EventMetadata {
585 signature,
586 slot,
587 tx_index,
588 block_time_us: block_time_us.unwrap_or(0),
589 grpc_recv_us,
590 recent_blockhash: recent_blockhash.map(|s| bs58::encode(s).into_string()),
591 };
592 return crate::logs::raydium_amm::parse_ray_log_swap(log, metadata);
593 }
594 let log_bytes = log.as_bytes();
596 let pos = PROGRAM_DATA_FINDER.find(log_bytes)?;
597 let data_start = pos + 14; if log_bytes.len() <= data_start {
600 return None;
601 }
602
603 const STACK_DECODE_CAP: usize = 2048;
606 let data_part = &log[data_start..];
607 let trimmed = data_part.trim();
608
609 let discriminator = decode_base64_discriminator(trimmed)?;
612 if !filter_allows_discriminator(program_id, discriminator, event_type_filter) {
613 return None;
614 }
615
616 use base64_simd::AsOut;
618 let max_decoded_len = (trimmed.len() / 4).saturating_mul(3).saturating_add(3);
619 let mut stack_buf = [0u8; STACK_DECODE_CAP];
620 let heap_buf: Vec<u8>;
621 let program_data: &[u8] = if max_decoded_len <= STACK_DECODE_CAP {
622 let decoded_len = {
623 let decoded_slice = base64_simd::STANDARD
624 .decode(trimmed.as_bytes(), stack_buf.as_mut().as_out())
625 .ok()?;
626 decoded_slice.len()
627 };
628 &stack_buf[..decoded_len]
629 } else {
630 heap_buf = base64_simd::STANDARD.decode_to_vec(trimmed.as_bytes()).ok()?;
631 heap_buf.as_slice()
632 };
633
634 if program_data.len() < 8 {
635 return None;
636 }
637
638 debug_assert_eq!(discriminator, unsafe {
639 (program_data.as_ptr() as *const u64).read_unaligned()
640 });
641
642 let data = &program_data[8..]; use crate::core::events::*;
646
647 let metadata = EventMetadata {
648 signature,
649 slot,
650 tx_index,
651 block_time_us: block_time_us.unwrap_or(0),
652 grpc_recv_us,
653 recent_blockhash: recent_blockhash.map(|s| bs58::encode(s).into_string()),
654 };
655
656 if let Some(program_id) = program_id {
657 return parse_program_scoped_event(
658 program_id,
659 discriminator,
660 data,
661 metadata,
662 log,
663 signature,
664 slot,
665 tx_index,
666 block_time_us,
667 grpc_recv_us,
668 event_type_filter,
669 is_created_buy,
670 );
671 }
672
673 if likely(discriminator == discriminators::PUMPFUN_TRADE) {
681 return parse_unscoped_pumpfun_launchlab_trade(
684 data,
685 metadata,
686 event_type_filter,
687 is_created_buy,
688 );
689 }
690
691 if likely(discriminator == discriminators::RAYDIUM_CLMM_SWAP) {
692 return apply_event_type_filter(
694 crate::logs::raydium_clmm::parse_swap_from_data(data, metadata)?,
695 event_type_filter,
696 );
697 }
698
699 if likely(discriminator == discriminators::RAYDIUM_AMM_SWAP_BASE_IN) {
700 return apply_event_type_filter(
702 crate::logs::raydium_amm::parse_swap_base_in_from_data(data, metadata)?,
703 event_type_filter,
704 );
705 }
706
707 if likely(discriminator == discriminators::PUMPSWAP_BUY) {
708 return apply_event_type_filter(
710 crate::logs::pump_amm::parse_buy_from_data(data, metadata)?,
711 event_type_filter,
712 );
713 }
714
715 if discriminator == discriminators::PUMPSWAP_SELL {
716 return apply_event_type_filter(
718 crate::logs::pump_amm::parse_sell_from_data(data, metadata)?,
719 event_type_filter,
720 );
721 }
722
723 let event = match discriminator {
728 discriminators::PUMPFUN_CREATE => crate::logs::pump::parse_create_from_data(data, metadata),
733 discriminators::PUMPFUN_MIGRATE => {
734 crate::logs::pump::parse_migrate_from_data(data, metadata)
735 }
736 discriminators::PUMP_FEES_CREATE_FEE_SHARING_CONFIG => {
737 crate::logs::pump_fees::parse_create_fee_sharing_config_from_data(data, metadata)
738 }
739 discriminators::PUMP_FEES_INITIALIZE_FEE_CONFIG => {
740 crate::logs::pump_fees::parse_initialize_fee_config_from_data(data, metadata)
741 }
742 discriminators::PUMP_FEES_RESET_FEE_SHARING_CONFIG => {
743 crate::logs::pump_fees::parse_reset_fee_sharing_config_from_data(data, metadata)
744 }
745 discriminators::PUMP_FEES_REVOKE_FEE_SHARING_AUTHORITY => {
746 crate::logs::pump_fees::parse_revoke_fee_sharing_authority_from_data(data, metadata)
747 }
748 discriminators::PUMP_FEES_TRANSFER_FEE_SHARING_AUTHORITY => {
749 crate::logs::pump_fees::parse_transfer_fee_sharing_authority_from_data(data, metadata)
750 }
751 discriminators::PUMP_FEES_UPDATE_ADMIN => {
752 crate::logs::pump_fees::parse_update_admin_from_data(data, metadata)
753 }
754 discriminators::PUMP_FEES_UPDATE_FEE_CONFIG => {
755 crate::logs::pump_fees::parse_update_fee_config_from_data(data, metadata)
756 }
757 discriminators::PUMP_FEES_UPDATE_FEE_SHARES => {
758 crate::logs::pump_fees::parse_update_fee_shares_from_data(data, metadata)
759 }
760 discriminators::PUMP_FEES_UPSERT_FEE_TIERS => {
761 crate::logs::pump_fees::parse_upsert_fee_tiers_from_data(data, metadata)
762 }
763 discriminators::PUMPFUN_MIGRATE_BONDING_CURVE_CREATOR => {
764 crate::logs::pump::parse_migrate_bonding_curve_creator_from_data(data, metadata)
765 }
766 discriminators::PUMPSWAP_CREATE_POOL => {
767 crate::logs::pump_amm::parse_create_pool_from_data(data, metadata)
768 }
769 discriminators::PUMPSWAP_ADD_LIQUIDITY => {
770 crate::logs::pump_amm::parse_add_liquidity_from_data(data, metadata)
771 }
772 discriminators::PUMPSWAP_REMOVE_LIQUIDITY => {
773 crate::logs::pump_amm::parse_remove_liquidity_from_data(data, metadata)
774 }
775
776 discriminators::RAYDIUM_CLMM_INCREASE_LIQUIDITY => {
779 crate::logs::raydium_clmm::parse_increase_liquidity_from_data(data, metadata)
780 }
781 discriminators::RAYDIUM_CLMM_DECREASE_LIQUIDITY => {
782 crate::logs::raydium_clmm::parse_decrease_liquidity_from_data(data, metadata)
783 }
784 discriminators::RAYDIUM_CLMM_LIQUIDITY_CHANGE => {
785 crate::logs::raydium_clmm::parse_liquidity_change_from_data(data, metadata)
786 }
787 discriminators::RAYDIUM_CLMM_CONFIG_CHANGE => {
788 crate::logs::raydium_clmm::parse_config_change_from_data(data, metadata)
789 }
790 discriminators::RAYDIUM_CLMM_CREATE_PERSONAL_POSITION => {
791 crate::logs::raydium_clmm::parse_create_personal_position_from_data(data, metadata)
792 }
793 discriminators::RAYDIUM_CLMM_LIQUIDITY_CALCULATE => {
794 crate::logs::raydium_clmm::parse_liquidity_calculate_from_data(data, metadata)
795 }
796 discriminators::RAYDIUM_CLMM_OPEN_LIMIT_ORDER => {
797 crate::logs::raydium_clmm::parse_open_limit_order_from_data(data, metadata)
798 }
799 discriminators::RAYDIUM_CLMM_INCREASE_LIMIT_ORDER => {
800 crate::logs::raydium_clmm::parse_increase_limit_order_from_data(data, metadata)
801 }
802 discriminators::RAYDIUM_CLMM_DECREASE_LIMIT_ORDER => {
803 crate::logs::raydium_clmm::parse_decrease_limit_order_from_data(data, metadata)
804 }
805 discriminators::RAYDIUM_CLMM_SETTLE_LIMIT_ORDER => {
806 crate::logs::raydium_clmm::parse_settle_limit_order_from_data(data, metadata)
807 }
808 discriminators::RAYDIUM_CLMM_UPDATE_REWARD_INFOS => {
809 crate::logs::raydium_clmm::parse_update_reward_infos_from_data(data, metadata)
810 }
811 discriminators::RAYDIUM_CLMM_CREATE_POOL => {
812 crate::logs::raydium_clmm::parse_create_pool_from_data(data, metadata)
813 }
814 discriminators::RAYDIUM_CLMM_COLLECT_PERSONAL_FEE => {
815 crate::logs::raydium_clmm::parse_collect_personal_fee_from_data(data, metadata)
816 }
817 discriminators::RAYDIUM_CLMM_COLLECT_PROTOCOL_FEE => {
818 crate::logs::raydium_clmm::parse_collect_protocol_fee_from_data(data, metadata)
819 }
820
821 discriminators::RAYDIUM_CPMM_SWAP_BASE_IN => {
823 crate::logs::raydium_cpmm::parse_swap_base_in_from_data(data, metadata)
824 }
825 discriminators::RAYDIUM_CPMM_SWAP_BASE_OUT => {
826 crate::logs::raydium_cpmm::parse_swap_base_out_from_data(data, metadata)
827 }
828 discriminators::RAYDIUM_CPMM_DEPOSIT => {
831 crate::logs::raydium_cpmm::parse_deposit_from_data(data, metadata)
832 }
833 discriminators::RAYDIUM_CPMM_WITHDRAW => {
834 crate::logs::raydium_cpmm::parse_withdraw_from_data(data, metadata)
835 }
836
837 discriminators::RAYDIUM_AMM_SWAP_BASE_IN => {
839 crate::logs::raydium_amm::parse_swap_base_in_from_data(data, metadata)
840 }
841 discriminators::RAYDIUM_AMM_SWAP_BASE_OUT => {
842 crate::logs::raydium_amm::parse_swap_base_out_from_data(data, metadata)
843 }
844 discriminators::RAYDIUM_AMM_DEPOSIT => {
845 crate::logs::raydium_amm::parse_deposit_from_data(data, metadata)
846 }
847 discriminators::RAYDIUM_AMM_WITHDRAW => {
848 crate::logs::raydium_amm::parse_withdraw_from_data(data, metadata)
849 }
850 discriminators::RAYDIUM_AMM_INITIALIZE2 => {
851 crate::logs::raydium_amm::parse_initialize2_from_data(data, metadata)
852 }
853 discriminators::RAYDIUM_AMM_WITHDRAW_PNL => {
854 crate::logs::raydium_amm::parse_withdraw_pnl_from_data(data, metadata)
855 }
856
857 discriminators::ORCA_TRADED => {
859 crate::logs::orca_whirlpool::parse_traded_from_data(data, metadata)
860 }
861 discriminators::ORCA_LIQUIDITY_INCREASED => {
862 crate::logs::orca_whirlpool::parse_liquidity_increased_from_data(data, metadata)
863 }
864 discriminators::ORCA_LIQUIDITY_DECREASED => {
865 crate::logs::orca_whirlpool::parse_liquidity_decreased_from_data(data, metadata)
866 }
867 discriminators::ORCA_POOL_INITIALIZED => {
868 crate::logs::orca_whirlpool::parse_pool_initialized_from_data(data, metadata)
869 }
870
871 discriminators::METEORA_AMM_SWAP => {
873 crate::logs::meteora_amm::parse_swap_from_data(data, metadata)
874 }
875 discriminators::METEORA_AMM_ADD_LIQUIDITY => {
876 crate::logs::meteora_amm::parse_add_liquidity_from_data(data, metadata)
877 }
878 discriminators::METEORA_AMM_REMOVE_LIQUIDITY => {
879 crate::logs::meteora_amm::parse_remove_liquidity_from_data(data, metadata)
880 }
881 discriminators::METEORA_AMM_BOOTSTRAP_LIQUIDITY => {
882 crate::logs::meteora_amm::parse_bootstrap_liquidity_from_data(data, metadata)
883 }
884 discriminators::METEORA_AMM_POOL_CREATED => {
885 crate::logs::meteora_amm::parse_pool_created_from_data(data, metadata)
886 }
887 discriminators::METEORA_AMM_SET_POOL_FEES => {
888 crate::logs::meteora_amm::parse_set_pool_fees_from_data(data, metadata)
889 }
890
891 discriminators::METEORA_DAMM_SWAP => {
893 crate::logs::meteora_damm::parse_swap_from_data(data, metadata)
894 }
895 discriminators::METEORA_DAMM_SWAP2 => {
896 crate::logs::meteora_damm::parse_swap2_from_data(data, metadata)
897 }
898 discriminators::METEORA_DAMM_ADD_LIQUIDITY => {
899 crate::logs::meteora_damm::parse_add_liquidity_from_data(data, metadata)
900 }
901 discriminators::METEORA_DAMM_REMOVE_LIQUIDITY => {
902 crate::logs::meteora_damm::parse_remove_liquidity_from_data(data, metadata)
903 }
904 discriminators::METEORA_DAMM_LIQUIDITY_CHANGE => {
905 crate::logs::meteora_damm::parse_liquidity_change_from_data(data, metadata)
906 }
907 discriminators::METEORA_DAMM_INITIALIZE_POOL => {
908 crate::logs::meteora_damm::parse_initialize_pool_from_data(data, metadata)
909 }
910 discriminators::METEORA_DAMM_CREATE_POSITION => {
911 crate::logs::meteora_damm::parse_create_position_from_data(data, metadata)
912 }
913 discriminators::METEORA_DAMM_CLOSE_POSITION => {
914 crate::logs::meteora_damm::parse_close_position_from_data(data, metadata)
915 }
916
917 _ => {
922 if let Some(event) = crate::logs::parse_meteora_dlmm_log(
924 log,
925 signature,
926 slot,
927 tx_index,
928 block_time_us,
929 grpc_recv_us,
930 ) {
931 return apply_event_type_filter(event, event_type_filter);
932 }
933 None
934 }
935 }?;
936 apply_event_type_filter(event, event_type_filter)
937}
938
939#[inline(always)]
940fn program_scoped_discriminator_to_event_type(
941 program_id: &Pubkey,
942 discriminator: u64,
943) -> Option<EventType> {
944 match *program_id {
945 program_ids::PUMPFUN_PROGRAM_ID => match discriminator {
946 discriminators::PUMPFUN_CREATE => Some(EventType::PumpFunCreate),
947 discriminators::PUMPFUN_TRADE => Some(EventType::PumpFunTrade),
948 discriminators::PUMPFUN_MIGRATE => Some(EventType::PumpFunMigrate),
949 discriminators::PUMPFUN_MIGRATE_BONDING_CURVE_CREATOR => {
950 Some(EventType::PumpFunMigrateBondingCurveCreator)
951 }
952 _ => None,
953 },
954 program_ids::PUMP_FEES_PROGRAM_ID => match discriminator {
955 discriminators::PUMP_FEES_CREATE_FEE_SHARING_CONFIG => {
956 Some(EventType::PumpFeesCreateFeeSharingConfig)
957 }
958 discriminators::PUMP_FEES_INITIALIZE_FEE_CONFIG => {
959 Some(EventType::PumpFeesInitializeFeeConfig)
960 }
961 discriminators::PUMP_FEES_RESET_FEE_SHARING_CONFIG => {
962 Some(EventType::PumpFeesResetFeeSharingConfig)
963 }
964 discriminators::PUMP_FEES_REVOKE_FEE_SHARING_AUTHORITY => {
965 Some(EventType::PumpFeesRevokeFeeSharingAuthority)
966 }
967 discriminators::PUMP_FEES_TRANSFER_FEE_SHARING_AUTHORITY => {
968 Some(EventType::PumpFeesTransferFeeSharingAuthority)
969 }
970 discriminators::PUMP_FEES_UPDATE_ADMIN => Some(EventType::PumpFeesUpdateAdmin),
971 discriminators::PUMP_FEES_UPDATE_FEE_CONFIG => Some(EventType::PumpFeesUpdateFeeConfig),
972 discriminators::PUMP_FEES_UPDATE_FEE_SHARES => Some(EventType::PumpFeesUpdateFeeShares),
973 discriminators::PUMP_FEES_UPSERT_FEE_TIERS => Some(EventType::PumpFeesUpsertFeeTiers),
974 _ => None,
975 },
976 program_ids::PUMPSWAP_PROGRAM_ID => match discriminator {
977 discriminators::PUMPSWAP_BUY => Some(EventType::PumpSwapBuy),
978 discriminators::PUMPSWAP_SELL => Some(EventType::PumpSwapSell),
979 discriminators::PUMPSWAP_CREATE_POOL => Some(EventType::PumpSwapCreatePool),
980 discriminators::PUMPSWAP_ADD_LIQUIDITY => Some(EventType::PumpSwapLiquidityAdded),
981 discriminators::PUMPSWAP_REMOVE_LIQUIDITY => Some(EventType::PumpSwapLiquidityRemoved),
982 _ => None,
983 },
984 program_ids::RAYDIUM_LAUNCHLAB_PROGRAM_ID => match discriminator {
985 discriminators::RAYDIUM_LAUNCHLAB_TRADE => Some(EventType::RaydiumLaunchlabTrade),
986 discriminators::RAYDIUM_LAUNCHLAB_POOL_CREATE => {
987 Some(EventType::RaydiumLaunchlabPoolCreate)
988 }
989 _ => None,
990 },
991 program_ids::RAYDIUM_CLMM_PROGRAM_ID => match discriminator {
992 discriminators::RAYDIUM_CLMM_SWAP => Some(EventType::RaydiumClmmSwap),
993 discriminators::RAYDIUM_CLMM_INCREASE_LIQUIDITY => {
994 Some(EventType::RaydiumClmmIncreaseLiquidity)
995 }
996 discriminators::RAYDIUM_CLMM_DECREASE_LIQUIDITY => {
997 Some(EventType::RaydiumClmmDecreaseLiquidity)
998 }
999 discriminators::RAYDIUM_CLMM_LIQUIDITY_CHANGE => {
1000 Some(EventType::RaydiumClmmLiquidityChange)
1001 }
1002 discriminators::RAYDIUM_CLMM_CONFIG_CHANGE => Some(EventType::RaydiumClmmConfigChange),
1003 discriminators::RAYDIUM_CLMM_CREATE_PERSONAL_POSITION => {
1004 Some(EventType::RaydiumClmmCreatePersonalPosition)
1005 }
1006 discriminators::RAYDIUM_CLMM_LIQUIDITY_CALCULATE => {
1007 Some(EventType::RaydiumClmmLiquidityCalculate)
1008 }
1009 discriminators::RAYDIUM_CLMM_OPEN_LIMIT_ORDER => {
1010 Some(EventType::RaydiumClmmOpenLimitOrder)
1011 }
1012 discriminators::RAYDIUM_CLMM_INCREASE_LIMIT_ORDER => {
1013 Some(EventType::RaydiumClmmIncreaseLimitOrder)
1014 }
1015 discriminators::RAYDIUM_CLMM_DECREASE_LIMIT_ORDER => {
1016 Some(EventType::RaydiumClmmDecreaseLimitOrder)
1017 }
1018 discriminators::RAYDIUM_CLMM_SETTLE_LIMIT_ORDER => {
1019 Some(EventType::RaydiumClmmSettleLimitOrder)
1020 }
1021 discriminators::RAYDIUM_CLMM_UPDATE_REWARD_INFOS => {
1022 Some(EventType::RaydiumClmmUpdateRewardInfos)
1023 }
1024 discriminators::RAYDIUM_CLMM_CREATE_POOL => Some(EventType::RaydiumClmmCreatePool),
1025 discriminators::RAYDIUM_CLMM_COLLECT_PERSONAL_FEE
1026 | discriminators::RAYDIUM_CLMM_COLLECT_PROTOCOL_FEE => {
1027 Some(EventType::RaydiumClmmCollectFee)
1028 }
1029 _ => None,
1030 },
1031 program_ids::RAYDIUM_CPMM_PROGRAM_ID => match discriminator {
1032 discriminators::RAYDIUM_CPMM_SWAP_EVENT
1033 | discriminators::RAYDIUM_CPMM_SWAP_BASE_IN
1034 | discriminators::RAYDIUM_CPMM_SWAP_BASE_OUT => Some(EventType::RaydiumCpmmSwap),
1035 discriminators::RAYDIUM_CPMM_CREATE_POOL => Some(EventType::RaydiumCpmmInitialize),
1036 discriminators::RAYDIUM_CPMM_DEPOSIT => Some(EventType::RaydiumCpmmDeposit),
1037 discriminators::RAYDIUM_CPMM_WITHDRAW => Some(EventType::RaydiumCpmmWithdraw),
1038 _ => None,
1039 },
1040 program_ids::RAYDIUM_AMM_V4_PROGRAM_ID => match discriminator {
1041 discriminators::RAYDIUM_AMM_SWAP_BASE_IN
1042 | discriminators::RAYDIUM_AMM_SWAP_BASE_OUT => Some(EventType::RaydiumAmmV4Swap),
1043 discriminators::RAYDIUM_AMM_DEPOSIT => Some(EventType::RaydiumAmmV4Deposit),
1044 discriminators::RAYDIUM_AMM_WITHDRAW => Some(EventType::RaydiumAmmV4Withdraw),
1045 discriminators::RAYDIUM_AMM_INITIALIZE2 => Some(EventType::RaydiumAmmV4Initialize2),
1046 discriminators::RAYDIUM_AMM_WITHDRAW_PNL => Some(EventType::RaydiumAmmV4WithdrawPnl),
1047 _ => None,
1048 },
1049 program_ids::ORCA_WHIRLPOOL_PROGRAM_ID => match discriminator {
1050 discriminators::ORCA_TRADED => Some(EventType::OrcaWhirlpoolSwap),
1051 discriminators::ORCA_LIQUIDITY_INCREASED => {
1052 Some(EventType::OrcaWhirlpoolLiquidityIncreased)
1053 }
1054 discriminators::ORCA_LIQUIDITY_DECREASED => {
1055 Some(EventType::OrcaWhirlpoolLiquidityDecreased)
1056 }
1057 discriminators::ORCA_POOL_INITIALIZED => Some(EventType::OrcaWhirlpoolPoolInitialized),
1058 _ => None,
1059 },
1060 program_ids::METEORA_POOLS_PROGRAM_ID => match discriminator {
1061 discriminators::METEORA_AMM_SWAP => Some(EventType::MeteoraPoolsSwap),
1062 discriminators::METEORA_AMM_ADD_LIQUIDITY => Some(EventType::MeteoraPoolsAddLiquidity),
1063 discriminators::METEORA_AMM_REMOVE_LIQUIDITY => {
1064 Some(EventType::MeteoraPoolsRemoveLiquidity)
1065 }
1066 discriminators::METEORA_AMM_BOOTSTRAP_LIQUIDITY => {
1067 Some(EventType::MeteoraPoolsBootstrapLiquidity)
1068 }
1069 discriminators::METEORA_AMM_POOL_CREATED => Some(EventType::MeteoraPoolsPoolCreated),
1070 discriminators::METEORA_AMM_SET_POOL_FEES => Some(EventType::MeteoraPoolsSetPoolFees),
1071 _ => None,
1072 },
1073 program_ids::METEORA_DAMM_V2_PROGRAM_ID => match discriminator {
1074 discriminators::METEORA_DAMM_SWAP | discriminators::METEORA_DAMM_SWAP2 => {
1075 Some(EventType::MeteoraDammV2Swap)
1076 }
1077 discriminators::METEORA_DAMM_ADD_LIQUIDITY => {
1078 Some(EventType::MeteoraDammV2AddLiquidity)
1079 }
1080 discriminators::METEORA_DAMM_REMOVE_LIQUIDITY => {
1081 Some(EventType::MeteoraDammV2RemoveLiquidity)
1082 }
1083 discriminators::METEORA_DAMM_LIQUIDITY_CHANGE => None,
1086 discriminators::METEORA_DAMM_INITIALIZE_POOL => {
1087 Some(EventType::MeteoraDammV2InitializePool)
1088 }
1089 discriminators::METEORA_DAMM_CREATE_POSITION => {
1090 Some(EventType::MeteoraDammV2CreatePosition)
1091 }
1092 discriminators::METEORA_DAMM_CLOSE_POSITION => {
1093 Some(EventType::MeteoraDammV2ClosePosition)
1094 }
1095 _ => None,
1096 },
1097 program_ids::METEORA_DBC_PROGRAM_ID => match discriminator {
1098 discriminators::METEORA_DBC_SWAP => Some(EventType::MeteoraDbcSwap),
1099 discriminators::METEORA_DBC_INITIALIZE_POOL => {
1100 Some(EventType::MeteoraDbcInitializePool)
1101 }
1102 discriminators::METEORA_DBC_CURVE_COMPLETE => Some(EventType::MeteoraDbcCurveComplete),
1103 _ => None,
1104 },
1105 program_ids::METEORA_DLMM_PROGRAM_ID => match discriminator {
1106 discriminators::METEORA_DLMM_SWAP | discriminators::METEORA_DLMM_SWAP2 => {
1107 Some(EventType::MeteoraDlmmSwap)
1108 }
1109 discriminators::METEORA_DLMM_ADD_LIQUIDITY => Some(EventType::MeteoraDlmmAddLiquidity),
1110 discriminators::METEORA_DLMM_REMOVE_LIQUIDITY => {
1111 Some(EventType::MeteoraDlmmRemoveLiquidity)
1112 }
1113 discriminators::METEORA_DLMM_INITIALIZE_POOL => {
1114 Some(EventType::MeteoraDlmmInitializePool)
1115 }
1116 discriminators::METEORA_DLMM_INITIALIZE_BIN_ARRAY => {
1117 Some(EventType::MeteoraDlmmInitializeBinArray)
1118 }
1119 discriminators::METEORA_DLMM_CREATE_POSITION => {
1120 Some(EventType::MeteoraDlmmCreatePosition)
1121 }
1122 discriminators::METEORA_DLMM_CLOSE_POSITION => {
1123 Some(EventType::MeteoraDlmmClosePosition)
1124 }
1125 discriminators::METEORA_DLMM_CLAIM_FEE | discriminators::METEORA_DLMM_CLAIM_FEE2 => {
1126 Some(EventType::MeteoraDlmmClaimFee)
1127 }
1128 _ => None,
1129 },
1130 _ => None,
1131 }
1132}
1133
1134#[inline(always)]
1135fn parse_program_scoped_event(
1136 program_id: &Pubkey,
1137 discriminator: u64,
1138 data: &[u8],
1139 metadata: EventMetadata,
1140 log: &str,
1141 signature: Signature,
1142 slot: u64,
1143 tx_index: u64,
1144 block_time_us: Option<i64>,
1145 grpc_recv_us: i64,
1146 event_type_filter: Option<&EventTypeFilter>,
1147 is_created_buy: bool,
1148) -> Option<DexEvent> {
1149 if let Some(filter) = event_type_filter {
1150 if *program_id == program_ids::PUMPFUN_PROGRAM_ID
1151 && discriminator == discriminators::PUMPFUN_TRADE
1152 {
1153 if !filter_wants_pumpfun_trade_event(filter) {
1154 return None;
1155 }
1156 } else if let Some(event_type) =
1157 program_scoped_discriminator_to_event_type(program_id, discriminator)
1158 {
1159 if !filter.should_include(event_type) {
1160 return None;
1161 }
1162 }
1163 }
1164
1165 match *program_id {
1166 program_ids::PUMPFUN_PROGRAM_ID => {
1167 if let Some(filter) = event_type_filter {
1168 if !filter.includes_pumpfun() {
1169 return None;
1170 }
1171 }
1172 match discriminator {
1173 discriminators::PUMPFUN_TRADE => {
1174 let event =
1175 crate::logs::pump::parse_trade_from_data(data, metadata, is_created_buy)?;
1176 filter_pumpfun_trade_variant(event, event_type_filter)
1177 }
1178 discriminators::PUMPFUN_CREATE => {
1179 crate::logs::pump::parse_create_from_data(data, metadata)
1180 }
1181 discriminators::PUMPFUN_MIGRATE => {
1182 crate::logs::pump::parse_migrate_from_data(data, metadata)
1183 }
1184 discriminators::PUMPFUN_MIGRATE_BONDING_CURVE_CREATOR => {
1185 crate::logs::pump::parse_migrate_bonding_curve_creator_from_data(data, metadata)
1186 }
1187 _ => None,
1188 }
1189 }
1190 program_ids::PUMP_FEES_PROGRAM_ID => {
1191 if let Some(filter) = event_type_filter {
1192 if !filter.includes_pump_fees() {
1193 return None;
1194 }
1195 }
1196 match discriminator {
1197 discriminators::PUMP_FEES_CREATE_FEE_SHARING_CONFIG => {
1198 crate::logs::pump_fees::parse_create_fee_sharing_config_from_data(
1199 data, metadata,
1200 )
1201 }
1202 discriminators::PUMP_FEES_INITIALIZE_FEE_CONFIG => {
1203 crate::logs::pump_fees::parse_initialize_fee_config_from_data(data, metadata)
1204 }
1205 discriminators::PUMP_FEES_RESET_FEE_SHARING_CONFIG => {
1206 crate::logs::pump_fees::parse_reset_fee_sharing_config_from_data(data, metadata)
1207 }
1208 discriminators::PUMP_FEES_REVOKE_FEE_SHARING_AUTHORITY => {
1209 crate::logs::pump_fees::parse_revoke_fee_sharing_authority_from_data(
1210 data, metadata,
1211 )
1212 }
1213 discriminators::PUMP_FEES_TRANSFER_FEE_SHARING_AUTHORITY => {
1214 crate::logs::pump_fees::parse_transfer_fee_sharing_authority_from_data(
1215 data, metadata,
1216 )
1217 }
1218 discriminators::PUMP_FEES_UPDATE_ADMIN => {
1219 crate::logs::pump_fees::parse_update_admin_from_data(data, metadata)
1220 }
1221 discriminators::PUMP_FEES_UPDATE_FEE_CONFIG => {
1222 crate::logs::pump_fees::parse_update_fee_config_from_data(data, metadata)
1223 }
1224 discriminators::PUMP_FEES_UPDATE_FEE_SHARES => {
1225 crate::logs::pump_fees::parse_update_fee_shares_from_data(data, metadata)
1226 }
1227 discriminators::PUMP_FEES_UPSERT_FEE_TIERS => {
1228 crate::logs::pump_fees::parse_upsert_fee_tiers_from_data(data, metadata)
1229 }
1230 _ => None,
1231 }
1232 }
1233 program_ids::PUMPSWAP_PROGRAM_ID => {
1234 if let Some(filter) = event_type_filter {
1235 if !filter.includes_pumpswap() {
1236 return None;
1237 }
1238 }
1239 match discriminator {
1240 discriminators::PUMPSWAP_BUY => {
1241 crate::logs::pump_amm::parse_buy_from_data(data, metadata)
1242 }
1243 discriminators::PUMPSWAP_SELL => {
1244 crate::logs::pump_amm::parse_sell_from_data(data, metadata)
1245 }
1246 discriminators::PUMPSWAP_CREATE_POOL => {
1247 crate::logs::pump_amm::parse_create_pool_from_data(data, metadata)
1248 }
1249 discriminators::PUMPSWAP_ADD_LIQUIDITY => {
1250 crate::logs::pump_amm::parse_add_liquidity_from_data(data, metadata)
1251 }
1252 discriminators::PUMPSWAP_REMOVE_LIQUIDITY => {
1253 crate::logs::pump_amm::parse_remove_liquidity_from_data(data, metadata)
1254 }
1255 _ => None,
1256 }
1257 }
1258 program_ids::RAYDIUM_LAUNCHLAB_PROGRAM_ID => {
1259 if let Some(filter) = event_type_filter {
1260 if !filter.includes_raydium_launchlab() {
1261 return None;
1262 }
1263 }
1264 match discriminator {
1265 discriminators::RAYDIUM_LAUNCHLAB_TRADE => {
1266 crate::logs::raydium_launchlab::parse_trade_from_data(data, metadata)
1267 }
1268 discriminators::RAYDIUM_LAUNCHLAB_POOL_CREATE => {
1269 crate::logs::raydium_launchlab::parse_pool_create_from_data(data, metadata)
1270 }
1271 _ => None,
1272 }
1273 }
1274 program_ids::RAYDIUM_CLMM_PROGRAM_ID => {
1275 if let Some(filter) = event_type_filter {
1276 if !filter.includes_raydium_clmm() {
1277 return None;
1278 }
1279 }
1280 match discriminator {
1281 discriminators::RAYDIUM_CLMM_SWAP => {
1282 crate::logs::raydium_clmm::parse_swap_from_data(data, metadata)
1283 }
1284 discriminators::RAYDIUM_CLMM_INCREASE_LIQUIDITY => {
1285 crate::logs::raydium_clmm::parse_increase_liquidity_from_data(data, metadata)
1286 }
1287 discriminators::RAYDIUM_CLMM_DECREASE_LIQUIDITY => {
1288 crate::logs::raydium_clmm::parse_decrease_liquidity_from_data(data, metadata)
1289 }
1290 discriminators::RAYDIUM_CLMM_LIQUIDITY_CHANGE => {
1291 crate::logs::raydium_clmm::parse_liquidity_change_from_data(data, metadata)
1292 }
1293 discriminators::RAYDIUM_CLMM_CONFIG_CHANGE => {
1294 crate::logs::raydium_clmm::parse_config_change_from_data(data, metadata)
1295 }
1296 discriminators::RAYDIUM_CLMM_CREATE_PERSONAL_POSITION => {
1297 crate::logs::raydium_clmm::parse_create_personal_position_from_data(
1298 data, metadata,
1299 )
1300 }
1301 discriminators::RAYDIUM_CLMM_LIQUIDITY_CALCULATE => {
1302 crate::logs::raydium_clmm::parse_liquidity_calculate_from_data(data, metadata)
1303 }
1304 discriminators::RAYDIUM_CLMM_OPEN_LIMIT_ORDER => {
1305 crate::logs::raydium_clmm::parse_open_limit_order_from_data(data, metadata)
1306 }
1307 discriminators::RAYDIUM_CLMM_INCREASE_LIMIT_ORDER => {
1308 crate::logs::raydium_clmm::parse_increase_limit_order_from_data(data, metadata)
1309 }
1310 discriminators::RAYDIUM_CLMM_DECREASE_LIMIT_ORDER => {
1311 crate::logs::raydium_clmm::parse_decrease_limit_order_from_data(data, metadata)
1312 }
1313 discriminators::RAYDIUM_CLMM_SETTLE_LIMIT_ORDER => {
1314 crate::logs::raydium_clmm::parse_settle_limit_order_from_data(data, metadata)
1315 }
1316 discriminators::RAYDIUM_CLMM_UPDATE_REWARD_INFOS => {
1317 crate::logs::raydium_clmm::parse_update_reward_infos_from_data(data, metadata)
1318 }
1319 discriminators::RAYDIUM_CLMM_CREATE_POOL => {
1320 crate::logs::raydium_clmm::parse_create_pool_from_data(data, metadata)
1321 }
1322 discriminators::RAYDIUM_CLMM_COLLECT_PERSONAL_FEE => {
1323 crate::logs::raydium_clmm::parse_collect_personal_fee_from_data(data, metadata)
1324 }
1325 discriminators::RAYDIUM_CLMM_COLLECT_PROTOCOL_FEE => {
1326 crate::logs::raydium_clmm::parse_collect_protocol_fee_from_data(data, metadata)
1327 }
1328 _ => None,
1329 }
1330 }
1331 program_ids::RAYDIUM_CPMM_PROGRAM_ID => {
1332 if let Some(filter) = event_type_filter {
1333 if !filter.includes_raydium_cpmm() {
1334 return None;
1335 }
1336 }
1337 match discriminator {
1338 discriminators::RAYDIUM_CPMM_SWAP_EVENT => {
1339 crate::logs::raydium_cpmm::parse_swap_event_from_data(data, metadata)
1340 }
1341 discriminators::RAYDIUM_CPMM_SWAP_BASE_IN => {
1342 crate::logs::raydium_cpmm::parse_swap_base_in_from_data(data, metadata)
1343 }
1344 discriminators::RAYDIUM_CPMM_SWAP_BASE_OUT => {
1345 crate::logs::raydium_cpmm::parse_swap_base_out_from_data(data, metadata)
1346 }
1347 discriminators::RAYDIUM_CPMM_CREATE_POOL => {
1348 crate::logs::raydium_cpmm::parse_create_pool_from_data(data, metadata)
1349 }
1350 discriminators::RAYDIUM_CPMM_DEPOSIT => {
1351 crate::logs::raydium_cpmm::parse_deposit_from_data(data, metadata)
1352 }
1353 discriminators::RAYDIUM_CPMM_WITHDRAW => {
1354 crate::logs::raydium_cpmm::parse_withdraw_from_data(data, metadata)
1355 }
1356 _ => None,
1357 }
1358 }
1359 program_ids::RAYDIUM_AMM_V4_PROGRAM_ID => {
1360 if let Some(filter) = event_type_filter {
1361 if !filter.includes_raydium_amm_v4() {
1362 return None;
1363 }
1364 }
1365 match discriminator {
1366 discriminators::RAYDIUM_AMM_SWAP_BASE_IN => {
1367 crate::logs::raydium_amm::parse_swap_base_in_from_data(data, metadata)
1368 }
1369 discriminators::RAYDIUM_AMM_SWAP_BASE_OUT => {
1370 crate::logs::raydium_amm::parse_swap_base_out_from_data(data, metadata)
1371 }
1372 discriminators::RAYDIUM_AMM_DEPOSIT => {
1373 crate::logs::raydium_amm::parse_deposit_from_data(data, metadata)
1374 }
1375 discriminators::RAYDIUM_AMM_WITHDRAW => {
1376 crate::logs::raydium_amm::parse_withdraw_from_data(data, metadata)
1377 }
1378 discriminators::RAYDIUM_AMM_INITIALIZE2 => {
1379 crate::logs::raydium_amm::parse_initialize2_from_data(data, metadata)
1380 }
1381 discriminators::RAYDIUM_AMM_WITHDRAW_PNL => {
1382 crate::logs::raydium_amm::parse_withdraw_pnl_from_data(data, metadata)
1383 }
1384 _ => None,
1385 }
1386 }
1387 program_ids::ORCA_WHIRLPOOL_PROGRAM_ID => {
1388 if let Some(filter) = event_type_filter {
1389 if !filter.includes_orca_whirlpool() {
1390 return None;
1391 }
1392 }
1393 match discriminator {
1394 discriminators::ORCA_TRADED => {
1395 crate::logs::orca_whirlpool::parse_traded_from_data(data, metadata)
1396 }
1397 discriminators::ORCA_LIQUIDITY_INCREASED => {
1398 crate::logs::orca_whirlpool::parse_liquidity_increased_from_data(data, metadata)
1399 }
1400 discriminators::ORCA_LIQUIDITY_DECREASED => {
1401 crate::logs::orca_whirlpool::parse_liquidity_decreased_from_data(data, metadata)
1402 }
1403 discriminators::ORCA_POOL_INITIALIZED => {
1404 crate::logs::orca_whirlpool::parse_pool_initialized_from_data(data, metadata)
1405 }
1406 _ => None,
1407 }
1408 }
1409 program_ids::METEORA_POOLS_PROGRAM_ID => {
1410 if let Some(filter) = event_type_filter {
1411 if !filter.includes_meteora_pools() {
1412 return None;
1413 }
1414 }
1415 match discriminator {
1416 discriminators::METEORA_AMM_SWAP => {
1417 crate::logs::meteora_amm::parse_swap_from_data(data, metadata)
1418 }
1419 discriminators::METEORA_AMM_ADD_LIQUIDITY => {
1420 crate::logs::meteora_amm::parse_add_liquidity_from_data(data, metadata)
1421 }
1422 discriminators::METEORA_AMM_REMOVE_LIQUIDITY => {
1423 crate::logs::meteora_amm::parse_remove_liquidity_from_data(data, metadata)
1424 }
1425 discriminators::METEORA_AMM_BOOTSTRAP_LIQUIDITY => {
1426 crate::logs::meteora_amm::parse_bootstrap_liquidity_from_data(data, metadata)
1427 }
1428 discriminators::METEORA_AMM_POOL_CREATED => {
1429 crate::logs::meteora_amm::parse_pool_created_from_data(data, metadata)
1430 }
1431 discriminators::METEORA_AMM_SET_POOL_FEES => {
1432 crate::logs::meteora_amm::parse_set_pool_fees_from_data(data, metadata)
1433 }
1434 _ => None,
1435 }
1436 }
1437 program_ids::METEORA_DAMM_V2_PROGRAM_ID => {
1438 if let Some(filter) = event_type_filter {
1439 if !filter.includes_meteora_damm_v2() {
1440 return None;
1441 }
1442 }
1443 match discriminator {
1444 discriminators::METEORA_DAMM_SWAP => {
1445 crate::logs::meteora_damm::parse_swap_from_data(data, metadata)
1446 }
1447 discriminators::METEORA_DAMM_SWAP2 => {
1448 crate::logs::meteora_damm::parse_swap2_from_data(data, metadata)
1449 }
1450 discriminators::METEORA_DAMM_ADD_LIQUIDITY => {
1451 crate::logs::meteora_damm::parse_add_liquidity_from_data(data, metadata)
1452 }
1453 discriminators::METEORA_DAMM_REMOVE_LIQUIDITY => {
1454 crate::logs::meteora_damm::parse_remove_liquidity_from_data(data, metadata)
1455 }
1456 discriminators::METEORA_DAMM_LIQUIDITY_CHANGE => {
1457 let event = crate::logs::meteora_damm::parse_liquidity_change_from_data(
1458 data, metadata,
1459 )?;
1460 apply_event_type_filter(event, event_type_filter)
1461 }
1462 discriminators::METEORA_DAMM_INITIALIZE_POOL => {
1463 crate::logs::meteora_damm::parse_initialize_pool_from_data(data, metadata)
1464 }
1465 discriminators::METEORA_DAMM_CREATE_POSITION => {
1466 crate::logs::meteora_damm::parse_create_position_from_data(data, metadata)
1467 }
1468 discriminators::METEORA_DAMM_CLOSE_POSITION => {
1469 crate::logs::meteora_damm::parse_close_position_from_data(data, metadata)
1470 }
1471 _ => None,
1472 }
1473 }
1474 program_ids::METEORA_DBC_PROGRAM_ID => {
1475 if let Some(filter) = event_type_filter {
1476 if !filter.includes_meteora_dbc() {
1477 return None;
1478 }
1479 }
1480 match discriminator {
1481 discriminators::METEORA_DBC_SWAP => {
1482 crate::logs::meteora_dbc::parse_swap_from_data(data, metadata)
1483 }
1484 discriminators::METEORA_DBC_INITIALIZE_POOL => {
1485 crate::logs::meteora_dbc::parse_initialize_pool_from_data(data, metadata)
1486 }
1487 discriminators::METEORA_DBC_CURVE_COMPLETE => {
1488 crate::logs::meteora_dbc::parse_curve_complete_from_data(data, metadata)
1489 }
1490 _ => None,
1491 }
1492 }
1493 program_ids::METEORA_DLMM_PROGRAM_ID => {
1494 if let Some(filter) = event_type_filter {
1495 if !filter.includes_meteora_dlmm() {
1496 return None;
1497 }
1498 }
1499 match discriminator {
1500 discriminators::METEORA_DLMM_SWAP => {
1501 crate::logs::meteora_dlmm::parse_swap_from_data(data, metadata)
1502 }
1503 discriminators::METEORA_DLMM_SWAP2 => {
1504 crate::logs::meteora_dlmm::parse_swap2_from_data(data, metadata)
1505 }
1506 discriminators::METEORA_DLMM_ADD_LIQUIDITY => {
1507 crate::logs::meteora_dlmm::parse_add_liquidity_from_data(data, metadata)
1508 }
1509 discriminators::METEORA_DLMM_REMOVE_LIQUIDITY => {
1510 crate::logs::meteora_dlmm::parse_remove_liquidity_from_data(data, metadata)
1511 }
1512 discriminators::METEORA_DLMM_INITIALIZE_POOL => {
1513 crate::logs::meteora_dlmm::parse_lb_pair_create_from_data(data, metadata)
1514 }
1515 discriminators::METEORA_DLMM_INITIALIZE_BIN_ARRAY => {
1516 crate::logs::meteora_dlmm::parse_initialize_bin_array_from_data(data, metadata)
1517 }
1518 discriminators::METEORA_DLMM_CREATE_POSITION => {
1519 crate::logs::meteora_dlmm::parse_position_create_from_data(data, metadata)
1520 }
1521 discriminators::METEORA_DLMM_CLOSE_POSITION => {
1522 crate::logs::meteora_dlmm::parse_position_close_from_data(data, metadata)
1523 }
1524 discriminators::METEORA_DLMM_CLAIM_FEE => {
1525 crate::logs::meteora_dlmm::parse_claim_fee_from_data(data, metadata)
1526 }
1527 discriminators::METEORA_DLMM_CLAIM_FEE2 => {
1528 crate::logs::meteora_dlmm::parse_claim_fee2_from_data(data, metadata)
1529 }
1530 _ => None,
1531 }
1532 }
1533 _ => None,
1534 }
1535}
1536
1537#[inline(always)]
1538fn filter_pumpfun_trade_variant(
1539 event: DexEvent,
1540 event_type_filter: Option<&EventTypeFilter>,
1541) -> Option<DexEvent> {
1542 if let Some(filter) = event_type_filter {
1543 if let Some(ref include_only) = filter.include_only {
1544 let has_specific_filter = !include_only.contains(&EventType::PumpFunTrade)
1545 && include_only.iter().any(|t| {
1546 matches!(
1547 t,
1548 EventType::PumpFunBuy
1549 | EventType::PumpFunSell
1550 | EventType::PumpFunBuyExactSolIn
1551 | EventType::PumpFunCreate
1552 | EventType::PumpFunCreateV2
1553 )
1554 });
1555 if has_specific_filter {
1556 let event_type_matches = match &event {
1557 DexEvent::PumpFunBuy(_) => include_only.iter().any(|t| {
1558 matches!(t, EventType::PumpFunBuy | EventType::PumpFunBuyExactSolIn)
1559 }),
1560 DexEvent::PumpFunSell(_) => include_only.contains(&EventType::PumpFunSell),
1561 DexEvent::PumpFunBuyExactSolIn(_) => include_only.iter().any(|t| {
1562 matches!(t, EventType::PumpFunBuy | EventType::PumpFunBuyExactSolIn)
1563 }),
1564 DexEvent::PumpFunTrade(_) => include_only.contains(&EventType::PumpFunTrade),
1565 DexEvent::PumpFunCreate(_) | DexEvent::PumpFunCreateV2(_) => {
1566 include_only.iter().any(|t| {
1567 matches!(t, EventType::PumpFunCreate | EventType::PumpFunCreateV2)
1568 })
1569 }
1570 _ => false,
1571 };
1572 if !event_type_matches {
1573 return None;
1574 }
1575 }
1576 }
1577 if filter.exclude_types.is_some() && !filter.should_include_dex_event(&event) {
1578 return None;
1579 }
1580 }
1581 Some(event)
1582}
1583
1584#[inline(always)]
1586fn discriminator_to_event_type(discriminator: u64) -> Option<EventType> {
1587 match discriminator {
1588 discriminators::PUMPFUN_CREATE => Some(EventType::PumpFunCreate),
1589 discriminators::PUMPFUN_TRADE => Some(EventType::PumpFunTrade),
1590 discriminators::PUMPFUN_MIGRATE => Some(EventType::PumpFunMigrate),
1591 discriminators::PUMP_FEES_CREATE_FEE_SHARING_CONFIG => {
1592 Some(EventType::PumpFeesCreateFeeSharingConfig)
1593 }
1594 discriminators::PUMP_FEES_INITIALIZE_FEE_CONFIG => {
1595 Some(EventType::PumpFeesInitializeFeeConfig)
1596 }
1597 discriminators::PUMP_FEES_RESET_FEE_SHARING_CONFIG => {
1598 Some(EventType::PumpFeesResetFeeSharingConfig)
1599 }
1600 discriminators::PUMP_FEES_REVOKE_FEE_SHARING_AUTHORITY => {
1601 Some(EventType::PumpFeesRevokeFeeSharingAuthority)
1602 }
1603 discriminators::PUMP_FEES_TRANSFER_FEE_SHARING_AUTHORITY => {
1604 Some(EventType::PumpFeesTransferFeeSharingAuthority)
1605 }
1606 discriminators::PUMP_FEES_UPDATE_ADMIN => Some(EventType::PumpFeesUpdateAdmin),
1607 discriminators::PUMP_FEES_UPDATE_FEE_CONFIG => Some(EventType::PumpFeesUpdateFeeConfig),
1608 discriminators::PUMP_FEES_UPDATE_FEE_SHARES => Some(EventType::PumpFeesUpdateFeeShares),
1609 discriminators::PUMP_FEES_UPSERT_FEE_TIERS => Some(EventType::PumpFeesUpsertFeeTiers),
1610 discriminators::PUMPFUN_MIGRATE_BONDING_CURVE_CREATOR => {
1611 Some(EventType::PumpFunMigrateBondingCurveCreator)
1612 }
1613 discriminators::PUMPSWAP_BUY => Some(EventType::PumpSwapBuy),
1614 discriminators::PUMPSWAP_SELL => Some(EventType::PumpSwapSell),
1615 discriminators::PUMPSWAP_CREATE_POOL => Some(EventType::PumpSwapCreatePool),
1616 discriminators::PUMPSWAP_ADD_LIQUIDITY => Some(EventType::PumpSwapLiquidityAdded),
1617 discriminators::PUMPSWAP_REMOVE_LIQUIDITY => Some(EventType::PumpSwapLiquidityRemoved),
1618 discriminators::RAYDIUM_LAUNCHLAB_POOL_CREATE => {
1619 Some(EventType::RaydiumLaunchlabPoolCreate)
1620 }
1621 discriminators::RAYDIUM_CLMM_SWAP => Some(EventType::RaydiumClmmSwap),
1622 discriminators::RAYDIUM_CLMM_INCREASE_LIQUIDITY => {
1623 Some(EventType::RaydiumClmmIncreaseLiquidity)
1624 }
1625 discriminators::RAYDIUM_CLMM_DECREASE_LIQUIDITY => {
1626 Some(EventType::RaydiumClmmDecreaseLiquidity)
1627 }
1628 discriminators::RAYDIUM_CLMM_LIQUIDITY_CHANGE => {
1629 Some(EventType::RaydiumClmmLiquidityChange)
1630 }
1631 discriminators::RAYDIUM_CLMM_CONFIG_CHANGE => Some(EventType::RaydiumClmmConfigChange),
1632 discriminators::RAYDIUM_CLMM_CREATE_PERSONAL_POSITION => {
1633 Some(EventType::RaydiumClmmCreatePersonalPosition)
1634 }
1635 discriminators::RAYDIUM_CLMM_LIQUIDITY_CALCULATE => {
1636 Some(EventType::RaydiumClmmLiquidityCalculate)
1637 }
1638 discriminators::RAYDIUM_CLMM_OPEN_LIMIT_ORDER => Some(EventType::RaydiumClmmOpenLimitOrder),
1639 discriminators::RAYDIUM_CLMM_INCREASE_LIMIT_ORDER => {
1640 Some(EventType::RaydiumClmmIncreaseLimitOrder)
1641 }
1642 discriminators::RAYDIUM_CLMM_DECREASE_LIMIT_ORDER => {
1643 Some(EventType::RaydiumClmmDecreaseLimitOrder)
1644 }
1645 discriminators::RAYDIUM_CLMM_SETTLE_LIMIT_ORDER => {
1646 Some(EventType::RaydiumClmmSettleLimitOrder)
1647 }
1648 discriminators::RAYDIUM_CLMM_UPDATE_REWARD_INFOS => {
1649 Some(EventType::RaydiumClmmUpdateRewardInfos)
1650 }
1651 discriminators::RAYDIUM_CLMM_CREATE_POOL => Some(EventType::RaydiumClmmCreatePool),
1652 discriminators::RAYDIUM_CLMM_COLLECT_PERSONAL_FEE
1653 | discriminators::RAYDIUM_CLMM_COLLECT_PROTOCOL_FEE => {
1654 Some(EventType::RaydiumClmmCollectFee)
1655 }
1656 discriminators::RAYDIUM_CPMM_SWAP_BASE_IN | discriminators::RAYDIUM_CPMM_SWAP_BASE_OUT => {
1657 Some(EventType::RaydiumCpmmSwap)
1658 }
1659 discriminators::RAYDIUM_CPMM_DEPOSIT => Some(EventType::RaydiumCpmmDeposit),
1660 discriminators::RAYDIUM_CPMM_WITHDRAW => Some(EventType::RaydiumCpmmWithdraw),
1661 discriminators::RAYDIUM_AMM_SWAP_BASE_IN | discriminators::RAYDIUM_AMM_SWAP_BASE_OUT => {
1662 Some(EventType::RaydiumAmmV4Swap)
1663 }
1664 discriminators::RAYDIUM_AMM_DEPOSIT => Some(EventType::RaydiumAmmV4Deposit),
1665 discriminators::RAYDIUM_AMM_WITHDRAW => Some(EventType::RaydiumAmmV4Withdraw),
1666 discriminators::RAYDIUM_AMM_INITIALIZE2 => Some(EventType::RaydiumAmmV4Initialize2),
1667 discriminators::RAYDIUM_AMM_WITHDRAW_PNL => Some(EventType::RaydiumAmmV4WithdrawPnl),
1668 discriminators::ORCA_TRADED => Some(EventType::OrcaWhirlpoolSwap),
1669 discriminators::ORCA_LIQUIDITY_INCREASED => {
1670 Some(EventType::OrcaWhirlpoolLiquidityIncreased)
1671 }
1672 discriminators::ORCA_LIQUIDITY_DECREASED => {
1673 Some(EventType::OrcaWhirlpoolLiquidityDecreased)
1674 }
1675 discriminators::ORCA_POOL_INITIALIZED => Some(EventType::OrcaWhirlpoolPoolInitialized),
1676 discriminators::METEORA_AMM_SWAP => Some(EventType::MeteoraPoolsSwap),
1677 discriminators::METEORA_AMM_ADD_LIQUIDITY => Some(EventType::MeteoraPoolsAddLiquidity),
1678 discriminators::METEORA_AMM_REMOVE_LIQUIDITY => {
1679 Some(EventType::MeteoraPoolsRemoveLiquidity)
1680 }
1681 discriminators::METEORA_AMM_BOOTSTRAP_LIQUIDITY => {
1682 Some(EventType::MeteoraPoolsBootstrapLiquidity)
1683 }
1684 discriminators::METEORA_AMM_POOL_CREATED => Some(EventType::MeteoraPoolsPoolCreated),
1685 discriminators::METEORA_AMM_SET_POOL_FEES => Some(EventType::MeteoraPoolsSetPoolFees),
1686 discriminators::METEORA_DAMM_SWAP | discriminators::METEORA_DAMM_SWAP2 => {
1687 Some(EventType::MeteoraDammV2Swap)
1688 }
1689 discriminators::METEORA_DAMM_ADD_LIQUIDITY => Some(EventType::MeteoraDammV2AddLiquidity),
1690 discriminators::METEORA_DAMM_REMOVE_LIQUIDITY => {
1691 Some(EventType::MeteoraDammV2RemoveLiquidity)
1692 }
1693 discriminators::METEORA_DAMM_INITIALIZE_POOL => {
1694 Some(EventType::MeteoraDammV2InitializePool)
1695 }
1696 discriminators::METEORA_DAMM_CREATE_POSITION => {
1697 Some(EventType::MeteoraDammV2CreatePosition)
1698 }
1699 discriminators::METEORA_DAMM_CLOSE_POSITION => Some(EventType::MeteoraDammV2ClosePosition),
1700 _ => None,
1701 }
1702}
1703
1704#[inline]
1708pub fn detect_pumpfun_create(logs: &[String]) -> bool {
1709 logs.iter().any(|log| PUMPFUN_CREATE_FINDER.find(log.as_bytes()).is_some())
1710}
1711
1712static INVOKE_FINDER: Lazy<memmem::Finder> = Lazy::new(|| memmem::Finder::new(b"invoke ["));
1713
1714#[inline]
1717pub fn parse_invoke_info(log: &str) -> Option<(&str, usize)> {
1718 let bytes = log.as_bytes();
1719 if !bytes.starts_with(b"Program ") || !bytes.ends_with(b"]") {
1720 return None;
1721 }
1722 let invoke_start = INVOKE_FINDER.find(bytes)?;
1723 if invoke_start <= 8 || bytes.get(invoke_start - 1) != Some(&b' ') {
1724 return None;
1725 }
1726 let depth_start = invoke_start + b"invoke [".len();
1727 let depth_bytes = bytes.get(depth_start..bytes.len() - 1)?;
1728 if depth_bytes.is_empty() {
1729 return None;
1730 }
1731
1732 let mut depth = 0usize;
1733 for &byte in depth_bytes {
1734 if !byte.is_ascii_digit() {
1735 return None;
1736 }
1737 let digit = (byte - b'0') as usize;
1738 depth = depth.checked_mul(10)?.checked_add(digit)?;
1739 }
1740 let program_id = log.get(8..invoke_start - 1)?;
1741 (depth > 0).then_some((program_id, depth))
1742}
1743
1744#[inline]
1746pub fn parse_program_complete_info(log: &str) -> Option<&str> {
1747 let rest = log.strip_prefix("Program ")?;
1748 if let Some(pos) = rest.find(" success") {
1749 return Some(&rest[..pos]);
1750 }
1751 if let Some(pos) = rest.find(" failed:") {
1752 return Some(&rest[..pos]);
1753 }
1754 None
1755}
1756
1757#[cfg(test)]
1758mod tests {
1759 use super::*;
1760 use crate::core::events::PumpFunTradeEvent;
1761 use base64::{engine::general_purpose::STANDARD, Engine as _};
1762 use solana_sdk::{pubkey::Pubkey, signature::Signature};
1763
1764 #[test]
1765 fn program_scoped_launchlab_trade_is_not_parsed_as_pumpfun() {
1766 let pool = Pubkey::new_unique();
1767 let mut raw = Vec::new();
1768 raw.extend_from_slice(&discriminators::RAYDIUM_LAUNCHLAB_TRADE.to_le_bytes());
1769 raw.extend_from_slice(pool.as_ref());
1770 for value in 0u64..13 {
1771 raw.extend_from_slice(&(100 + value).to_le_bytes());
1772 }
1773 raw.push(1); raw.push(2); raw.push(1); let log = format!("Program data: {}", STANDARD.encode(raw));
1778 let filter = EventTypeFilter::include_only(vec![EventType::RaydiumLaunchlabTrade]);
1779 let event = parse_log_optimized_with_program_id(
1780 &log,
1781 Signature::default(),
1782 1,
1783 2,
1784 Some(3),
1785 4,
1786 Some(&filter),
1787 false,
1788 None,
1789 Some(&program_ids::RAYDIUM_LAUNCHLAB_PROGRAM_ID),
1790 )
1791 .expect("launchlab trade should parse");
1792
1793 match event {
1794 DexEvent::RaydiumLaunchlabTrade(trade) => {
1795 assert_eq!(trade.pool_state, pool);
1796 assert_eq!(trade.amount_in, 107);
1797 assert_eq!(trade.amount_out, 108);
1798 assert!(!trade.is_buy);
1799 assert!(trade.exact_in);
1800 }
1801 other => panic!("expected RaydiumLaunchlabTrade, got {other:?}"),
1802 }
1803 }
1804
1805 fn launchlab_trade_log() -> (String, Pubkey) {
1806 let pool = Pubkey::new_unique();
1807 let mut raw = Vec::new();
1808 raw.extend_from_slice(&discriminators::RAYDIUM_LAUNCHLAB_TRADE.to_le_bytes());
1809 raw.extend_from_slice(pool.as_ref());
1810 for value in 0u64..13 {
1811 raw.extend_from_slice(&(100 + value).to_le_bytes());
1812 }
1813 raw.push(1); raw.push(2); raw.push(1); (format!("Program data: {}", STANDARD.encode(raw)), pool)
1818 }
1819
1820 #[test]
1821 fn unscoped_launchlab_trade_filter_parses_shared_discriminator() {
1822 let (log, pool) = launchlab_trade_log();
1823 let filter = EventTypeFilter::include_only(vec![EventType::RaydiumLaunchlabTrade]);
1824 let event = parse_log_optimized(
1825 &log,
1826 Signature::default(),
1827 1,
1828 2,
1829 Some(3),
1830 4,
1831 Some(&filter),
1832 false,
1833 None,
1834 )
1835 .expect("unscoped LaunchLab trade should parse when requested");
1836
1837 match event {
1838 DexEvent::RaydiumLaunchlabTrade(trade) => {
1839 assert_eq!(trade.pool_state, pool);
1840 assert_eq!(trade.amount_in, 107);
1841 assert_eq!(trade.amount_out, 108);
1842 assert!(!trade.is_buy);
1843 assert!(trade.exact_in);
1844 }
1845 other => panic!("expected RaydiumLaunchlabTrade, got {other:?}"),
1846 }
1847 }
1848
1849 #[test]
1850 fn unscoped_pumpfun_only_filter_does_not_parse_launchlab_trade() {
1851 let (log, _pool) = launchlab_trade_log();
1852 let filter = EventTypeFilter::include_only(vec![EventType::PumpFunBuy]);
1853 assert!(parse_log_optimized(
1854 &log,
1855 Signature::default(),
1856 1,
1857 2,
1858 Some(3),
1859 4,
1860 Some(&filter),
1861 false,
1862 None,
1863 )
1864 .is_none());
1865 }
1866
1867 #[test]
1868 fn program_scoped_dlmm_initialize_bin_array_parses_and_filters() {
1869 let pool = Pubkey::new_unique();
1870 let bin_array = Pubkey::new_unique();
1871 let mut raw = Vec::new();
1872 raw.extend_from_slice(&discriminators::METEORA_DLMM_INITIALIZE_BIN_ARRAY.to_le_bytes());
1873 raw.extend_from_slice(pool.as_ref());
1874 raw.extend_from_slice(bin_array.as_ref());
1875 raw.extend_from_slice(&(-12i64).to_le_bytes());
1876
1877 let log = format!("Program data: {}", STANDARD.encode(raw));
1878 let matching_filter =
1879 EventTypeFilter::include_only(vec![EventType::MeteoraDlmmInitializeBinArray]);
1880 let event = parse_log_optimized_with_program_id(
1881 &log,
1882 Signature::default(),
1883 1,
1884 2,
1885 Some(3),
1886 4,
1887 Some(&matching_filter),
1888 false,
1889 None,
1890 Some(&program_ids::METEORA_DLMM_PROGRAM_ID),
1891 )
1892 .expect("DLMM initialize bin array should parse");
1893
1894 match event {
1895 DexEvent::MeteoraDlmmInitializeBinArray(event) => {
1896 assert_eq!(event.pool, pool);
1897 assert_eq!(event.bin_array, bin_array);
1898 assert_eq!(event.index, -12);
1899 }
1900 other => panic!("expected MeteoraDlmmInitializeBinArray, got {other:?}"),
1901 }
1902
1903 let non_matching_filter = EventTypeFilter::include_only(vec![EventType::MeteoraDlmmSwap]);
1904 assert!(parse_log_optimized_with_program_id(
1905 &log,
1906 Signature::default(),
1907 1,
1908 2,
1909 Some(3),
1910 4,
1911 Some(&non_matching_filter),
1912 false,
1913 None,
1914 Some(&program_ids::METEORA_DLMM_PROGRAM_ID),
1915 )
1916 .is_none());
1917 }
1918
1919 #[test]
1920 fn pumpfun_trade_filter_remains_generic_when_combined_with_specific_type() {
1921 let filter =
1922 EventTypeFilter::include_only(vec![EventType::PumpFunTrade, EventType::PumpFunBuy]);
1923 let event = DexEvent::PumpFunSell(PumpFunTradeEvent {
1924 metadata: EventMetadata::default(),
1925 is_buy: false,
1926 ix_name: "sell".to_string(),
1927 ..Default::default()
1928 });
1929
1930 assert!(filter_pumpfun_trade_variant(event, Some(&filter)).is_some());
1931 }
1932
1933 #[test]
1934 fn pumpfun_buy_family_filter_matches_both_buy_variants() {
1935 let buy = DexEvent::PumpFunBuy(PumpFunTradeEvent {
1936 metadata: EventMetadata::default(),
1937 is_buy: true,
1938 ix_name: "buy_exact_quote_in_v2".to_string(),
1939 ..Default::default()
1940 });
1941 let exact_sol = DexEvent::PumpFunBuyExactSolIn(PumpFunTradeEvent {
1942 metadata: EventMetadata::default(),
1943 is_buy: true,
1944 ix_name: "buy_exact_sol_in".to_string(),
1945 ..Default::default()
1946 });
1947
1948 let buy_filter = EventTypeFilter::include_only(vec![EventType::PumpFunBuy]);
1949 assert!(filter_pumpfun_trade_variant(buy.clone(), Some(&buy_filter)).is_some());
1950 assert!(filter_pumpfun_trade_variant(exact_sol.clone(), Some(&buy_filter)).is_some());
1951
1952 let exact_filter = EventTypeFilter::include_only(vec![EventType::PumpFunBuyExactSolIn]);
1953 assert!(filter_pumpfun_trade_variant(buy, Some(&exact_filter)).is_some());
1954 assert!(filter_pumpfun_trade_variant(exact_sol, Some(&exact_filter)).is_some());
1955 }
1956
1957 #[test]
1958 fn discriminator_prefix_filter_handles_program_scoped_collisions() {
1959 let dlmm_filter = EventTypeFilter::include_only(vec![EventType::MeteoraDlmmSwap]);
1960 assert!(filter_allows_discriminator(
1961 Some(&program_ids::METEORA_DLMM_PROGRAM_ID),
1962 discriminators::METEORA_DLMM_SWAP,
1963 Some(&dlmm_filter),
1964 ));
1965 assert!(!filter_allows_discriminator(
1966 Some(&program_ids::RAYDIUM_CPMM_PROGRAM_ID),
1967 discriminators::METEORA_DLMM_SWAP,
1968 Some(&dlmm_filter),
1969 ));
1970
1971 let raydium_launchlab_filter =
1972 EventTypeFilter::include_only(vec![EventType::RaydiumLaunchlabTrade]);
1973 assert!(filter_allows_discriminator(
1974 Some(&program_ids::RAYDIUM_LAUNCHLAB_PROGRAM_ID),
1975 discriminators::RAYDIUM_LAUNCHLAB_TRADE,
1976 Some(&raydium_launchlab_filter),
1977 ));
1978 assert!(!filter_allows_discriminator(
1979 Some(&program_ids::PUMPFUN_PROGRAM_ID),
1980 discriminators::PUMPFUN_TRADE,
1981 Some(&raydium_launchlab_filter),
1982 ));
1983
1984 let pumpfun_buy_filter = EventTypeFilter::include_only(vec![EventType::PumpFunBuy]);
1985 assert!(filter_allows_discriminator(
1986 Some(&program_ids::PUMPFUN_PROGRAM_ID),
1987 discriminators::PUMPFUN_TRADE,
1988 Some(&pumpfun_buy_filter),
1989 ));
1990
1991 let dbc_filter = EventTypeFilter::include_only(vec![EventType::MeteoraDbcSwap]);
1992 assert!(filter_allows_discriminator(
1993 Some(&program_ids::METEORA_DBC_PROGRAM_ID),
1994 discriminators::METEORA_DBC_SWAP,
1995 Some(&dbc_filter),
1996 ));
1997 assert!(!filter_allows_discriminator(
1998 Some(&program_ids::METEORA_DAMM_V2_PROGRAM_ID),
1999 discriminators::METEORA_DAMM_SWAP,
2000 Some(&dbc_filter),
2001 ));
2002 assert!(!filter_allows_discriminator(
2003 Some(&program_ids::METEORA_DBC_PROGRAM_ID),
2004 discriminators::METEORA_DLMM_CLOSE_POSITION,
2005 Some(&raydium_launchlab_filter),
2006 ));
2007 }
2008
2009 #[test]
2010 fn program_scoped_pumpfun_buy_filter_parses_trade_log_variant() {
2011 let log = "Program data: vdt/007mYe5StuUGXKtQJzSLsEK5h79gIdGUQz7vyn59ApMQyeYlr3cK4wUAAAAA7dnMPhkDAAAB5uPeR/hOJigYiGhz2PiTzeNML3vtbrwijyhrJHoTgitivC1qAAAAALjcux8HAAAAt2E0T9e8AwC4MJgjAAAAALfJIQNGvgIA4ATIfOuY+lzkf4A4Bv0seUXSlSSVmuwA3tl4FPOPeEZfAAAAAAAAACBRDgAAAAAAbf5L76S20PsQ+d4EfYrWKDprZOVyf9lJPbA04mYiiiweAAAAAAAAAGmFBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAGJ1eQAAAAAAAAAAAAAAAAAAAAAAiBMAAAAAAACQKAcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHcK4wUAAAAAuNy7HwcAAAC4MJgjAAAAAA==";
2012 let filter = EventTypeFilter::include_only(vec![EventType::PumpFunBuy]);
2013 let event = parse_log_optimized_with_program_id(
2014 log,
2015 Signature::default(),
2016 426270756,
2017 268,
2018 Some(1781382243600841),
2019 1781382243601307,
2020 Some(&filter),
2021 false,
2022 None,
2023 Some(&program_ids::PUMPFUN_PROGRAM_ID),
2024 )
2025 .expect("PumpFun TradeEvent log should parse under PumpFunBuy filter");
2026
2027 match event {
2028 DexEvent::PumpFunBuy(trade) => {
2029 assert_eq!(trade.ix_name, "buy");
2030 assert_eq!(trade.sol_amount, 98_765_431);
2031 assert_eq!(trade.token_amount, 3_406_962_678_253);
2032 assert_eq!(trade.virtual_sol_reserves, 30_597_176_504);
2033 assert_eq!(trade.virtual_token_reserves, 1_052_057_862_955_447);
2034 assert_eq!(trade.real_sol_reserves, 597_176_504);
2035 assert_eq!(trade.real_token_reserves, 772_157_862_955_447);
2036 }
2037 other => panic!("expected PumpFunBuy, got {other:?}"),
2038 }
2039 }
2040
2041 #[test]
2042 fn unscoped_pumpfun_buy_filter_parses_trade_log_variant() {
2043 let log = "Program data: vdt/007mYe5StuUGXKtQJzSLsEK5h79gIdGUQz7vyn59ApMQyeYlr3cK4wUAAAAA7dnMPhkDAAAB5uPeR/hOJigYiGhz2PiTzeNML3vtbrwijyhrJHoTgitivC1qAAAAALjcux8HAAAAt2E0T9e8AwC4MJgjAAAAALfJIQNGvgIA4ATIfOuY+lzkf4A4Bv0seUXSlSSVmuwA3tl4FPOPeEZfAAAAAAAAACBRDgAAAAAAbf5L76S20PsQ+d4EfYrWKDprZOVyf9lJPbA04mYiiiweAAAAAAAAAGmFBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAGJ1eQAAAAAAAAAAAAAAAAAAAAAAiBMAAAAAAACQKAcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHcK4wUAAAAAuNy7HwcAAAC4MJgjAAAAAA==";
2044 let filter = EventTypeFilter::include_only(vec![EventType::PumpFunBuy]);
2045 let event = parse_log_optimized(
2046 log,
2047 Signature::default(),
2048 426270756,
2049 268,
2050 Some(1781382243600841),
2051 1781382243601307,
2052 Some(&filter),
2053 false,
2054 None,
2055 )
2056 .expect("Unscoped PumpFun TradeEvent log should parse under PumpFunBuy filter");
2057
2058 match event {
2059 DexEvent::PumpFunBuy(trade) => {
2060 assert_eq!(trade.ix_name, "buy");
2061 assert_eq!(trade.sol_amount, 98_765_431);
2062 assert_eq!(trade.token_amount, 3_406_962_678_253);
2063 assert_eq!(trade.virtual_sol_reserves, 30_597_176_504);
2064 assert_eq!(trade.virtual_token_reserves, 1_052_057_862_955_447);
2065 assert_eq!(trade.real_sol_reserves, 597_176_504);
2066 assert_eq!(trade.real_token_reserves, 772_157_862_955_447);
2067 }
2068 other => panic!("expected PumpFunBuy, got {other:?}"),
2069 }
2070 }
2071
2072 #[test]
2073 fn discriminator_prefix_filter_keeps_unscoped_collision_candidates() {
2074 let dlmm_filter = EventTypeFilter::include_only(vec![EventType::MeteoraDlmmSwap]);
2075 assert!(filter_allows_discriminator(
2076 None,
2077 discriminators::METEORA_DLMM_SWAP2,
2078 Some(&dlmm_filter),
2079 ));
2080
2081 let cpmm_filter = EventTypeFilter::include_only(vec![EventType::RaydiumCpmmInitialize]);
2082 assert!(filter_allows_discriminator(
2083 None,
2084 discriminators::RAYDIUM_CPMM_CREATE_POOL,
2085 Some(&cpmm_filter),
2086 ));
2087
2088 let pumpfun_buy_filter = EventTypeFilter::include_only(vec![EventType::PumpFunBuy]);
2089 assert!(filter_allows_discriminator(
2090 None,
2091 discriminators::PUMPFUN_TRADE,
2092 Some(&pumpfun_buy_filter),
2093 ));
2094 }
2095
2096 #[test]
2097 fn unscoped_collision_does_not_emit_wrong_protocol_event_after_filter() {
2098 let mut raw = Vec::new();
2099 raw.extend_from_slice(&discriminators::METEORA_DLMM_SWAP.to_le_bytes());
2100 raw.extend_from_slice(Pubkey::new_unique().as_ref()); raw.extend_from_slice(Pubkey::new_unique().as_ref()); raw.extend_from_slice(&1u64.to_le_bytes());
2103 raw.extend_from_slice(&2u64.to_le_bytes());
2104 raw.extend_from_slice(&3u64.to_le_bytes());
2105 raw.push(1);
2106
2107 let log = format!("Program data: {}", STANDARD.encode(raw));
2108 let filter = EventTypeFilter::include_only(vec![EventType::MeteoraDlmmSwap]);
2109 assert!(parse_log_optimized(
2110 &log,
2111 Signature::default(),
2112 1,
2113 2,
2114 Some(3),
2115 4,
2116 Some(&filter),
2117 false,
2118 None,
2119 )
2120 .is_none());
2121 }
2122
2123 #[test]
2124 fn unscoped_pumpfun_launchlab_collision_does_not_emit_wrong_protocol_event() {
2125 let mut raw = Vec::new();
2126 raw.extend_from_slice(&discriminators::PUMPFUN_TRADE.to_le_bytes());
2127 raw.extend_from_slice(Pubkey::new_unique().as_ref()); raw.extend_from_slice(&1u64.to_le_bytes()); raw.extend_from_slice(&2u64.to_le_bytes()); raw.push(1); raw.extend_from_slice(Pubkey::new_unique().as_ref()); raw.extend_from_slice(&3i64.to_le_bytes()); for value in 4u64..=9 {
2134 raw.extend_from_slice(&value.to_le_bytes());
2135 }
2136 raw.extend_from_slice(Pubkey::new_unique().as_ref()); raw.extend_from_slice(&10u64.to_le_bytes()); raw.extend_from_slice(&11u64.to_le_bytes()); raw.extend_from_slice(Pubkey::new_unique().as_ref()); raw.extend_from_slice(&12u64.to_le_bytes()); raw.extend_from_slice(&13u64.to_le_bytes()); let log = format!("Program data: {}", STANDARD.encode(raw));
2144 let filter = EventTypeFilter::include_only(vec![EventType::RaydiumLaunchlabTrade]);
2145 assert!(parse_log_optimized(
2146 &log,
2147 Signature::default(),
2148 1,
2149 2,
2150 Some(3),
2151 4,
2152 Some(&filter),
2153 false,
2154 None,
2155 )
2156 .is_none());
2157 }
2158
2159 #[test]
2160 fn discriminator_prefix_decode_reads_first_event_bytes() {
2161 let mut raw = Vec::new();
2162 raw.extend_from_slice(&discriminators::PUMP_FEES_UPDATE_ADMIN.to_le_bytes());
2163 raw.extend_from_slice(Pubkey::new_unique().as_ref());
2164 raw.extend_from_slice(Pubkey::new_unique().as_ref());
2165
2166 let encoded = STANDARD.encode(raw);
2167 assert_eq!(
2168 decode_base64_discriminator(&encoded),
2169 Some(discriminators::PUMP_FEES_UPDATE_ADMIN)
2170 );
2171 }
2172
2173 #[test]
2174 fn program_scoped_damm_add_liquidity_parses_from_decoded_data() {
2175 let pool = Pubkey::new_unique();
2176 let position = Pubkey::new_unique();
2177 let owner = Pubkey::new_unique();
2178 let mut raw = Vec::new();
2179 raw.extend_from_slice(&discriminators::METEORA_DAMM_ADD_LIQUIDITY.to_le_bytes());
2180 raw.extend_from_slice(pool.as_ref());
2181 raw.extend_from_slice(position.as_ref());
2182 raw.extend_from_slice(owner.as_ref());
2183 raw.extend_from_slice(&123u128.to_le_bytes());
2184 raw.extend_from_slice(&10u64.to_le_bytes());
2185 raw.extend_from_slice(&20u64.to_le_bytes());
2186 raw.extend_from_slice(&30u64.to_le_bytes());
2187 raw.extend_from_slice(&40u64.to_le_bytes());
2188 raw.extend_from_slice(&50u64.to_le_bytes());
2189 raw.extend_from_slice(&60u64.to_le_bytes());
2190
2191 let log = format!("Program data: {}", STANDARD.encode(raw));
2192 let filter = EventTypeFilter::include_only(vec![EventType::MeteoraDammV2AddLiquidity]);
2193 let event = parse_log_optimized_with_program_id(
2194 &log,
2195 Signature::default(),
2196 1,
2197 2,
2198 Some(3),
2199 4,
2200 Some(&filter),
2201 false,
2202 None,
2203 Some(&program_ids::METEORA_DAMM_V2_PROGRAM_ID),
2204 )
2205 .expect("DAMM V2 add-liquidity should parse");
2206
2207 match event {
2208 DexEvent::MeteoraDammV2AddLiquidity(event) => {
2209 assert_eq!(event.pool, pool);
2210 assert_eq!(event.position, position);
2211 assert_eq!(event.owner, owner);
2212 assert_eq!(event.liquidity_delta, 123);
2213 assert_eq!(event.token_a_amount, 30);
2214 assert_eq!(event.token_b_amount, 40);
2215 assert_eq!(event.total_amount_a, 50);
2216 assert_eq!(event.total_amount_b, 60);
2217 }
2218 other => panic!("expected MeteoraDammV2AddLiquidity, got {other:?}"),
2219 }
2220 }
2221
2222 #[test]
2223 fn program_scoped_dbc_swap_parses_and_does_not_emit_as_damm() {
2224 let pool = Pubkey::new_unique();
2225 let config = Pubkey::new_unique();
2226 let mut raw = Vec::new();
2227 raw.extend_from_slice(&discriminators::METEORA_DBC_SWAP.to_le_bytes());
2228 raw.extend_from_slice(pool.as_ref());
2229 raw.extend_from_slice(config.as_ref());
2230 raw.push(1);
2231 raw.push(0);
2232 raw.extend_from_slice(&100u64.to_le_bytes());
2233 raw.extend_from_slice(&90u64.to_le_bytes());
2234 raw.extend_from_slice(&100u64.to_le_bytes());
2235 raw.extend_from_slice(&95u64.to_le_bytes());
2236 raw.extend_from_slice(&(1u128 << 64).to_le_bytes());
2237 raw.extend_from_slice(&2u64.to_le_bytes());
2238 raw.extend_from_slice(&3u64.to_le_bytes());
2239 raw.extend_from_slice(&0u64.to_le_bytes());
2240 raw.extend_from_slice(&100u64.to_le_bytes());
2241 raw.extend_from_slice(&1_777_920_719u64.to_le_bytes());
2242
2243 let log = format!("Program data: {}", STANDARD.encode(raw));
2244 let dbc_filter = EventTypeFilter::include_only(vec![EventType::MeteoraDbcSwap]);
2245 let event = parse_log_optimized_with_program_id(
2246 &log,
2247 Signature::default(),
2248 1,
2249 2,
2250 Some(3),
2251 4,
2252 Some(&dbc_filter),
2253 false,
2254 None,
2255 Some(&program_ids::METEORA_DBC_PROGRAM_ID),
2256 )
2257 .expect("DBC swap should parse with DBC program context");
2258
2259 match event {
2260 DexEvent::MeteoraDbcSwap(event) => {
2261 assert_eq!(event.pool, pool);
2262 assert_eq!(event.config, config);
2263 assert_eq!(event.amount_in, 100);
2264 assert_eq!(event.output_amount, 95);
2265 }
2266 other => panic!("expected MeteoraDbcSwap, got {other:?}"),
2267 }
2268
2269 let damm_filter = EventTypeFilter::include_only(vec![EventType::MeteoraDammV2Swap]);
2270 assert!(parse_log_optimized_with_program_id(
2271 &log,
2272 Signature::default(),
2273 1,
2274 2,
2275 Some(3),
2276 4,
2277 Some(&damm_filter),
2278 false,
2279 None,
2280 Some(&program_ids::METEORA_DBC_PROGRAM_ID),
2281 )
2282 .is_none());
2283 }
2284
2285 #[test]
2286 fn large_program_data_uses_heap_fallback_without_dropping_event() {
2287 let mut raw = Vec::new();
2288 raw.extend_from_slice(&discriminators::PUMP_FEES_CREATE_FEE_SHARING_CONFIG.to_le_bytes());
2289 raw.extend_from_slice(&1_777_920_719i64.to_le_bytes());
2290 raw.extend_from_slice(Pubkey::new_unique().as_ref()); raw.extend_from_slice(Pubkey::new_unique().as_ref()); raw.push(0); raw.extend_from_slice(Pubkey::new_unique().as_ref()); raw.extend_from_slice(Pubkey::new_unique().as_ref()); raw.extend_from_slice(&64u32.to_le_bytes());
2296 for i in 0..64u16 {
2297 raw.extend_from_slice(Pubkey::new_unique().as_ref());
2298 raw.extend_from_slice(&i.to_le_bytes());
2299 }
2300 raw.push(1); let encoded = STANDARD.encode(&raw);
2303 assert!(encoded.len() > 2700, "test must exceed the old fixed stack buffer limit");
2304 let log = format!("Program data: {encoded}");
2305
2306 let event = parse_log_optimized_with_program_id(
2307 &log,
2308 Signature::default(),
2309 1,
2310 2,
2311 Some(3),
2312 4,
2313 None,
2314 false,
2315 None,
2316 Some(&program_ids::PUMP_FEES_PROGRAM_ID),
2317 )
2318 .expect("large pump-fees event should parse via heap fallback");
2319
2320 match event {
2321 DexEvent::PumpFeesCreateFeeSharingConfig(event) => {
2322 assert_eq!(event.initial_shareholders.len(), 64);
2323 assert_eq!(event.status, crate::core::events::PumpFeesConfigStatus::Active);
2324 }
2325 other => panic!("expected PumpFeesCreateFeeSharingConfig, got {other:?}"),
2326 }
2327 }
2328
2329 #[test]
2330 fn completion_parser_extracts_program_id() {
2331 assert_eq!(
2332 parse_program_complete_info(
2333 "Program LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj success"
2334 ),
2335 Some("LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj")
2336 );
2337 assert_eq!(
2338 parse_program_complete_info(
2339 "Program CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C failed: custom program error: 0x1"
2340 ),
2341 Some("CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C")
2342 );
2343 }
2344
2345 #[test]
2346 fn invoke_parser_accepts_only_complete_positive_depth_lines() {
2347 assert_eq!(
2348 parse_invoke_info("Program 11111111111111111111111111111111 invoke [12]"),
2349 Some(("11111111111111111111111111111111", 12))
2350 );
2351 for malformed in [
2352 "prefix Program 11111111111111111111111111111111 invoke [1]",
2353 "Program 11111111111111111111111111111111 invoke [0]",
2354 "Program 11111111111111111111111111111111 invoke [1",
2355 "Program 11111111111111111111111111111111 invoke []",
2356 "Program 11111111111111111111111111111111 invoke [1] trailing",
2357 ] {
2358 assert_eq!(parse_invoke_info(malformed), None, "accepted {malformed}");
2359 }
2360 }
2361}