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