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