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