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 BONK_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 BONK_INVOKE: &str = "Program LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj invoke";
49 pub const BONK_SUCCESS: &str = "Program LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj success";
50 pub const BONK_ID: &str = "LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj";
51
52 pub const RAYDIUM_CLMM_INVOKE: &str =
53 "Program CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK invoke";
54 pub const RAYDIUM_CLMM_SUCCESS: &str =
55 "Program CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK success";
56 pub const RAYDIUM_CLMM_ID: &str = "CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK";
57
58 pub const RAYDIUM_CPMM_INVOKE: &str =
59 "Program CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C invoke";
60 pub const RAYDIUM_CPMM_SUCCESS: &str =
61 "Program CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C success";
62 pub const RAYDIUM_CPMM_ID: &str = "CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C";
63
64 pub const RAYDIUM_AMM_V4_ID: &str = "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8";
65
66 pub const PROGRAM_DATA: &str = "Program data: ";
68 pub const PROGRAM_LOG: &str = "Program log: ";
69
70 pub const PUMPFUN_CREATE_DISCRIMINATOR: &str = "GB7IKAUcB3c"; }
73
74#[derive(Debug, Copy, Clone, PartialEq)]
76pub enum LogType {
77 PumpFun,
78 RaydiumLaunchpad,
79 PumpAmm,
80 RaydiumClmm,
81 RaydiumCpmm,
82 RaydiumAmm,
83 OrcaWhirlpool,
84 MeteoraAmm,
85 MeteoraDamm,
86 MeteoraDlmm,
87 Unknown,
88}
89
90#[inline(always)]
92pub fn detect_log_type(log: &str) -> LogType {
93 let log_bytes = log.as_bytes();
94
95 if log_bytes.len() < 20 {
97 return LogType::Unknown;
98 }
99
100 let has_program_data = PROGRAM_DATA_FINDER.find(log_bytes).is_some();
102
103 if unlikely(!has_program_data) {
105 return LogType::Unknown;
106 }
107
108 if likely(RAYDIUM_AMM_FINDER.find(log_bytes).is_some()) {
111 return LogType::RaydiumAmm;
112 }
113
114 if RAYDIUM_CLMM_FINDER.find(log_bytes).is_some() {
116 return LogType::RaydiumClmm;
117 }
118
119 if RAYDIUM_CPMM_FINDER.find(log_bytes).is_some() {
121 return LogType::RaydiumCpmm;
122 }
123
124 if BONK_FINDER.find(log_bytes).is_some() {
126 return LogType::RaydiumLaunchpad;
127 }
128
129 if WHIRL_FINDER.find(log_bytes).is_some() {
131 return LogType::OrcaWhirlpool;
132 }
133
134 if let Some(pos) = METEORA_FINDER.find(log_bytes) {
136 let rest = &log_bytes[pos..];
137 if METEORA_LB_FINDER.find(rest).is_some() {
138 return LogType::MeteoraDamm;
139 } else if METEORA_DLMM_FINDER.find(rest).is_some() {
140 return LogType::MeteoraDlmm;
141 } else {
142 return LogType::MeteoraAmm;
143 }
144 }
145
146 if PUMPSWAP_LOWER_FINDER.find(log_bytes).is_some()
148 || PUMPSWAP_UPPER_FINDER.find(log_bytes).is_some()
149 {
150 return LogType::PumpAmm;
151 }
152
153 if likely(PUMPFUN_FINDER.find(log_bytes).is_some()) {
156 return LogType::PumpFun;
157 }
158
159 if log.len() > 30 {
163 return LogType::PumpFun;
164 }
165
166 LogType::Unknown
167}
168
169mod discriminators {
173 pub const PUMPFUN_CREATE: u64 = u64::from_le_bytes([27, 114, 169, 77, 222, 235, 99, 118]);
175 pub const PUMPFUN_TRADE: u64 = u64::from_le_bytes([189, 219, 127, 211, 78, 230, 97, 238]);
176 pub const PUMPFUN_MIGRATE: u64 = u64::from_le_bytes([189, 233, 93, 185, 92, 148, 234, 148]);
177 pub const PUMPFUN_MIGRATE_BONDING_CURVE_CREATOR: u64 =
178 u64::from_le_bytes([155, 167, 104, 220, 213, 108, 243, 3]);
179 pub const RAYDIUM_LAUNCHPAD_POOL_CREATE: u64 =
183 u64::from_le_bytes([151, 215, 226, 9, 118, 161, 115, 174]);
184 pub const RAYDIUM_LAUNCHPAD_TRADE: u64 =
185 u64::from_le_bytes([189, 219, 127, 211, 78, 230, 97, 238]);
186 pub const PUMP_FEES_CREATE_FEE_SHARING_CONFIG: u64 =
188 u64::from_le_bytes([133, 105, 170, 200, 184, 116, 251, 88]);
189 pub const PUMP_FEES_INITIALIZE_FEE_CONFIG: u64 =
190 u64::from_le_bytes([89, 138, 244, 230, 10, 56, 226, 126]);
191 pub const PUMP_FEES_RESET_FEE_SHARING_CONFIG: u64 =
192 u64::from_le_bytes([203, 204, 151, 226, 120, 55, 214, 243]);
193 pub const PUMP_FEES_REVOKE_FEE_SHARING_AUTHORITY: u64 =
194 u64::from_le_bytes([114, 23, 101, 60, 14, 190, 153, 62]);
195 pub const PUMP_FEES_TRANSFER_FEE_SHARING_AUTHORITY: u64 =
196 u64::from_le_bytes([124, 143, 198, 245, 77, 184, 8, 236]);
197 pub const PUMP_FEES_UPDATE_ADMIN: u64 =
198 u64::from_le_bytes([225, 152, 171, 87, 246, 63, 66, 234]);
199 pub const PUMP_FEES_UPDATE_FEE_CONFIG: u64 =
200 u64::from_le_bytes([90, 23, 65, 35, 62, 244, 188, 208]);
201 pub const PUMP_FEES_UPDATE_FEE_SHARES: u64 =
202 u64::from_le_bytes([21, 186, 196, 184, 91, 228, 225, 203]);
203 pub const PUMP_FEES_UPSERT_FEE_TIERS: u64 =
204 u64::from_le_bytes([171, 89, 169, 187, 122, 186, 33, 204]);
205
206 pub const PUMPSWAP_BUY: u64 = u64::from_le_bytes([103, 244, 82, 31, 44, 245, 119, 119]);
208 pub const PUMPSWAP_SELL: u64 = u64::from_le_bytes([62, 47, 55, 10, 165, 3, 220, 42]);
209 pub const PUMPSWAP_CREATE_POOL: u64 =
210 u64::from_le_bytes([177, 49, 12, 210, 160, 118, 167, 116]);
211 pub const PUMPSWAP_ADD_LIQUIDITY: u64 =
212 u64::from_le_bytes([120, 248, 61, 83, 31, 142, 107, 144]);
213 pub const PUMPSWAP_REMOVE_LIQUIDITY: u64 =
214 u64::from_le_bytes([22, 9, 133, 26, 160, 44, 71, 192]);
215
216 pub const RAYDIUM_CLMM_SWAP: u64 = u64::from_le_bytes([248, 198, 158, 145, 225, 117, 135, 200]);
218 pub const RAYDIUM_CLMM_INCREASE_LIQUIDITY: u64 =
219 u64::from_le_bytes([133, 29, 89, 223, 69, 238, 176, 10]);
220 pub const RAYDIUM_CLMM_DECREASE_LIQUIDITY: u64 =
221 u64::from_le_bytes([160, 38, 208, 111, 104, 91, 44, 1]);
222 pub const RAYDIUM_CLMM_CREATE_POOL: u64 =
223 u64::from_le_bytes([233, 146, 209, 142, 207, 104, 64, 188]);
224 pub const RAYDIUM_CLMM_COLLECT_FEE: u64 =
225 u64::from_le_bytes([164, 152, 207, 99, 187, 104, 171, 119]);
226
227 pub const RAYDIUM_CPMM_SWAP_BASE_IN: u64 =
229 u64::from_le_bytes([143, 190, 90, 218, 196, 30, 51, 222]);
230 pub const RAYDIUM_CPMM_SWAP_BASE_OUT: u64 =
231 u64::from_le_bytes([55, 217, 98, 86, 163, 74, 180, 173]);
232 pub const RAYDIUM_CPMM_CREATE_POOL: u64 =
233 u64::from_le_bytes([233, 146, 209, 142, 207, 104, 64, 188]);
234 pub const RAYDIUM_CPMM_DEPOSIT: u64 =
235 u64::from_le_bytes([242, 35, 198, 137, 82, 225, 242, 182]);
236 pub const RAYDIUM_CPMM_WITHDRAW: u64 =
237 u64::from_le_bytes([183, 18, 70, 156, 148, 109, 161, 34]);
238
239 pub const RAYDIUM_AMM_SWAP_BASE_IN: u64 = u64::from_le_bytes([0, 0, 0, 0, 0, 0, 0, 9]);
241 pub const RAYDIUM_AMM_SWAP_BASE_OUT: u64 = u64::from_le_bytes([0, 0, 0, 0, 0, 0, 0, 11]);
242 pub const RAYDIUM_AMM_DEPOSIT: u64 = u64::from_le_bytes([0, 0, 0, 0, 0, 0, 0, 3]);
243 pub const RAYDIUM_AMM_WITHDRAW: u64 = u64::from_le_bytes([0, 0, 0, 0, 0, 0, 0, 4]);
244 pub const RAYDIUM_AMM_INITIALIZE2: u64 = u64::from_le_bytes([0, 0, 0, 0, 0, 0, 0, 1]);
245
246 pub const ORCA_TRADED: u64 = u64::from_le_bytes([225, 202, 73, 175, 147, 43, 160, 150]);
248 pub const ORCA_LIQUIDITY_INCREASED: u64 =
249 u64::from_le_bytes([30, 7, 144, 181, 102, 254, 155, 161]);
250 pub const ORCA_LIQUIDITY_DECREASED: u64 =
251 u64::from_le_bytes([166, 1, 36, 71, 112, 202, 181, 171]);
252 pub const ORCA_POOL_INITIALIZED: u64 =
253 u64::from_le_bytes([100, 118, 173, 87, 12, 198, 254, 229]);
254
255 pub const METEORA_AMM_SWAP: u64 = u64::from_le_bytes([81, 108, 227, 190, 205, 208, 10, 196]);
257 pub const METEORA_AMM_ADD_LIQUIDITY: u64 =
258 u64::from_le_bytes([31, 94, 125, 90, 227, 52, 61, 186]);
259 pub const METEORA_AMM_REMOVE_LIQUIDITY: u64 =
260 u64::from_le_bytes([116, 244, 97, 232, 103, 31, 152, 58]);
261 pub const METEORA_AMM_BOOTSTRAP_LIQUIDITY: u64 =
262 u64::from_le_bytes([121, 127, 38, 136, 92, 55, 14, 247]);
263 pub const METEORA_AMM_POOL_CREATED: u64 =
264 u64::from_le_bytes([202, 44, 41, 88, 104, 220, 157, 82]);
265
266 pub const METEORA_DAMM_SWAP: u64 = u64::from_le_bytes([27, 60, 21, 213, 138, 170, 187, 147]);
268 pub const METEORA_DAMM_SWAP2: u64 = u64::from_le_bytes([189, 66, 51, 168, 38, 80, 117, 153]);
269 pub const METEORA_DAMM_ADD_LIQUIDITY: u64 =
270 u64::from_le_bytes([175, 242, 8, 157, 30, 247, 185, 169]);
271 pub const METEORA_DAMM_REMOVE_LIQUIDITY: u64 =
272 u64::from_le_bytes([87, 46, 88, 98, 175, 96, 34, 91]);
273 pub const METEORA_DAMM_INITIALIZE_POOL: u64 =
274 u64::from_le_bytes([228, 50, 246, 85, 203, 66, 134, 37]);
275 pub const METEORA_DAMM_CREATE_POSITION: u64 =
276 u64::from_le_bytes([156, 15, 119, 198, 29, 181, 221, 55]);
277 pub const METEORA_DAMM_CLOSE_POSITION: u64 =
278 u64::from_le_bytes([20, 145, 144, 68, 143, 142, 214, 178]);
279
280 pub const METEORA_DLMM_SWAP: u64 = u64::from_le_bytes([143, 190, 90, 218, 196, 30, 51, 222]);
282 pub const METEORA_DLMM_ADD_LIQUIDITY: u64 =
283 u64::from_le_bytes([181, 157, 89, 67, 143, 182, 52, 72]);
284 pub const METEORA_DLMM_REMOVE_LIQUIDITY: u64 =
285 u64::from_le_bytes([80, 85, 209, 72, 24, 206, 35, 178]);
286 pub const METEORA_DLMM_INITIALIZE_POOL: u64 =
287 u64::from_le_bytes([95, 180, 10, 172, 84, 174, 232, 40]);
288 pub const METEORA_DLMM_CREATE_POSITION: u64 =
289 u64::from_le_bytes([123, 233, 11, 43, 146, 180, 97, 119]);
290 pub const METEORA_DLMM_CLOSE_POSITION: u64 =
291 u64::from_le_bytes([94, 168, 102, 45, 59, 122, 137, 54]);
292}
293
294#[inline(always)]
306pub fn parse_log_optimized(
308 log: &str,
309 signature: Signature,
310 slot: u64,
311 tx_index: u64,
312 block_time_us: Option<i64>,
313 grpc_recv_us: i64,
314 event_type_filter: Option<&EventTypeFilter>,
315 is_created_buy: bool,
316 recent_blockhash: Option<&[u8]>,
317) -> Option<DexEvent> {
318 parse_log_optimized_inner(
319 log,
320 signature,
321 slot,
322 tx_index,
323 block_time_us,
324 grpc_recv_us,
325 event_type_filter,
326 is_created_buy,
327 recent_blockhash,
328 None,
329 )
330}
331
332#[inline(always)]
338pub fn parse_log_optimized_with_program_id(
339 log: &str,
340 signature: Signature,
341 slot: u64,
342 tx_index: u64,
343 block_time_us: Option<i64>,
344 grpc_recv_us: i64,
345 event_type_filter: Option<&EventTypeFilter>,
346 is_created_buy: bool,
347 recent_blockhash: Option<&[u8]>,
348 program_id: Option<&Pubkey>,
349) -> Option<DexEvent> {
350 parse_log_optimized_inner(
351 log,
352 signature,
353 slot,
354 tx_index,
355 block_time_us,
356 grpc_recv_us,
357 event_type_filter,
358 is_created_buy,
359 recent_blockhash,
360 program_id,
361 )
362}
363
364#[inline(always)]
365fn parse_log_optimized_inner(
366 log: &str,
367 signature: Signature,
368 slot: u64,
369 tx_index: u64,
370 block_time_us: Option<i64>,
371 grpc_recv_us: i64,
372 event_type_filter: Option<&EventTypeFilter>,
373 is_created_buy: bool,
374 recent_blockhash: Option<&[u8]>,
375 program_id: Option<&Pubkey>,
376) -> Option<DexEvent> {
377 let log_bytes = log.as_bytes();
379 let pos = PROGRAM_DATA_FINDER.find(log_bytes)?;
380 let data_start = pos + 14; if log_bytes.len() <= data_start {
383 return None;
384 }
385
386 let mut buf = [0u8; 2048]; let data_part = &log[data_start..];
389 let trimmed = data_part.trim();
390
391 if trimmed.len() > 2700 {
394 return None;
395 }
396
397 use base64_simd::AsOut;
399 let decoded_slice =
400 base64_simd::STANDARD.decode(trimmed.as_bytes(), buf.as_mut().as_out()).ok()?;
401 let decoded_len = decoded_slice.len();
402
403 if decoded_len < 8 {
404 return None;
405 }
406
407 let program_data = &buf[..decoded_len];
408
409 let discriminator = unsafe {
411 let ptr = program_data.as_ptr() as *const u64;
412 ptr.read_unaligned()
413 };
414
415 let event_type = discriminator_to_event_type(discriminator);
417
418 if program_id.is_none() {
420 if let Some(filter) = event_type_filter {
421 if let Some(et) = event_type {
422 if !filter.should_include(et) {
423 return None; }
425 } else {
426 if let Some(ref include_only) = filter.include_only {
428 let wants_supported = include_only.iter().any(|t| {
429 matches!(
430 t,
431 EventType::PumpFunTrade
432 | EventType::PumpFunCreate
433 | EventType::PumpFunMigrate
434 | EventType::PumpFunBuy
435 | EventType::PumpFunSell
436 | EventType::PumpFunBuyExactSolIn
437 | EventType::PumpFunMigrateBondingCurveCreator
438 | EventType::PumpFeesCreateFeeSharingConfig
439 | EventType::PumpFeesInitializeFeeConfig
440 | EventType::PumpFeesResetFeeSharingConfig
441 | EventType::PumpFeesRevokeFeeSharingAuthority
442 | EventType::PumpFeesTransferFeeSharingAuthority
443 | EventType::PumpFeesUpdateAdmin
444 | EventType::PumpFeesUpdateFeeConfig
445 | EventType::PumpFeesUpdateFeeShares
446 | EventType::PumpFeesUpsertFeeTiers
447 | EventType::PumpSwapBuy
448 | EventType::PumpSwapSell
449 | EventType::PumpSwapCreatePool
450 | EventType::PumpSwapLiquidityAdded
451 | EventType::PumpSwapLiquidityRemoved
452 )
453 });
454 if !wants_supported {
455 return None;
456 }
457 }
458 }
459 }
460 }
461
462 let data = &program_data[8..]; use crate::core::events::*;
466
467 let metadata = EventMetadata {
468 signature,
469 slot,
470 tx_index,
471 block_time_us: block_time_us.unwrap_or(0),
472 grpc_recv_us,
473 recent_blockhash: recent_blockhash.map(|s| bs58::encode(s).into_string()),
474 };
475
476 if let Some(program_id) = program_id {
477 return parse_program_scoped_event(
478 program_id,
479 discriminator,
480 data,
481 metadata,
482 log,
483 signature,
484 slot,
485 tx_index,
486 block_time_us,
487 grpc_recv_us,
488 event_type_filter,
489 is_created_buy,
490 );
491 }
492
493 if likely(discriminator == discriminators::PUMPFUN_TRADE) {
501 let event = crate::logs::pump::parse_trade_from_data(data, metadata, is_created_buy)?;
503 if let Some(filter) = event_type_filter {
505 if let Some(ref include_only) = filter.include_only {
506 let has_specific_filter = include_only.iter().any(|t| {
507 matches!(
508 t,
509 EventType::PumpFunBuy
510 | EventType::PumpFunSell
511 | EventType::PumpFunBuyExactSolIn
512 | EventType::PumpFunCreate
513 | EventType::PumpFunCreateV2
514 )
515 });
516 if has_specific_filter {
517 let event_type_matches = match &event {
518 DexEvent::PumpFunBuy(_) => include_only.contains(&EventType::PumpFunBuy),
519 DexEvent::PumpFunSell(_) => include_only.contains(&EventType::PumpFunSell),
520 DexEvent::PumpFunBuyExactSolIn(_) => {
521 include_only.contains(&EventType::PumpFunBuyExactSolIn)
522 }
523 DexEvent::PumpFunTrade(_) => {
524 include_only.contains(&EventType::PumpFunTrade)
525 }
526 DexEvent::PumpFunCreate(_) => {
527 include_only.contains(&EventType::PumpFunCreate)
528 }
529 DexEvent::PumpFunCreateV2(_) => {
530 include_only.contains(&EventType::PumpFunCreateV2)
531 }
532 _ => false,
533 };
534 if !event_type_matches {
535 return None;
536 }
537 }
538 }
539 }
540 return Some(event);
541 }
542
543 if likely(discriminator == discriminators::RAYDIUM_CLMM_SWAP) {
544 return crate::logs::raydium_clmm::parse_swap_from_data(data, metadata);
546 }
547
548 if likely(discriminator == discriminators::RAYDIUM_AMM_SWAP_BASE_IN) {
549 return crate::logs::raydium_amm::parse_swap_base_in_from_data(data, metadata);
551 }
552
553 if likely(discriminator == discriminators::PUMPSWAP_BUY) {
554 return crate::logs::pump_amm::parse_buy_from_data(data, metadata);
556 }
557
558 if discriminator == discriminators::PUMPSWAP_SELL {
559 return crate::logs::pump_amm::parse_sell_from_data(data, metadata);
561 }
562
563 match discriminator {
568 discriminators::PUMPFUN_CREATE => crate::logs::pump::parse_create_from_data(data, metadata),
573 discriminators::PUMPFUN_MIGRATE => {
574 crate::logs::pump::parse_migrate_from_data(data, metadata)
575 }
576 discriminators::PUMP_FEES_CREATE_FEE_SHARING_CONFIG => {
577 crate::logs::pump_fees::parse_create_fee_sharing_config_from_data(data, metadata)
578 }
579 discriminators::PUMP_FEES_INITIALIZE_FEE_CONFIG => {
580 crate::logs::pump_fees::parse_initialize_fee_config_from_data(data, metadata)
581 }
582 discriminators::PUMP_FEES_RESET_FEE_SHARING_CONFIG => {
583 crate::logs::pump_fees::parse_reset_fee_sharing_config_from_data(data, metadata)
584 }
585 discriminators::PUMP_FEES_REVOKE_FEE_SHARING_AUTHORITY => {
586 crate::logs::pump_fees::parse_revoke_fee_sharing_authority_from_data(data, metadata)
587 }
588 discriminators::PUMP_FEES_TRANSFER_FEE_SHARING_AUTHORITY => {
589 crate::logs::pump_fees::parse_transfer_fee_sharing_authority_from_data(data, metadata)
590 }
591 discriminators::PUMP_FEES_UPDATE_ADMIN => {
592 crate::logs::pump_fees::parse_update_admin_from_data(data, metadata)
593 }
594 discriminators::PUMP_FEES_UPDATE_FEE_CONFIG => {
595 crate::logs::pump_fees::parse_update_fee_config_from_data(data, metadata)
596 }
597 discriminators::PUMP_FEES_UPDATE_FEE_SHARES => {
598 crate::logs::pump_fees::parse_update_fee_shares_from_data(data, metadata)
599 }
600 discriminators::PUMP_FEES_UPSERT_FEE_TIERS => {
601 crate::logs::pump_fees::parse_upsert_fee_tiers_from_data(data, metadata)
602 }
603 discriminators::PUMPFUN_MIGRATE_BONDING_CURVE_CREATOR => {
604 crate::logs::pump::parse_migrate_bonding_curve_creator_from_data(data, metadata)
605 }
606 discriminators::PUMPSWAP_CREATE_POOL => {
607 crate::logs::pump_amm::parse_create_pool_from_data(data, metadata)
608 }
609 discriminators::PUMPSWAP_ADD_LIQUIDITY => {
610 crate::logs::pump_amm::parse_add_liquidity_from_data(data, metadata)
611 }
612 discriminators::PUMPSWAP_REMOVE_LIQUIDITY => {
613 crate::logs::pump_amm::parse_remove_liquidity_from_data(data, metadata)
614 }
615
616 discriminators::RAYDIUM_CLMM_INCREASE_LIQUIDITY => {
619 crate::logs::raydium_clmm::parse_increase_liquidity_from_data(data, metadata)
620 }
621 discriminators::RAYDIUM_CLMM_DECREASE_LIQUIDITY => {
622 crate::logs::raydium_clmm::parse_decrease_liquidity_from_data(data, metadata)
623 }
624 discriminators::RAYDIUM_CLMM_CREATE_POOL => {
625 crate::logs::raydium_clmm::parse_create_pool_from_data(data, metadata)
626 }
627 discriminators::RAYDIUM_CLMM_COLLECT_FEE => {
628 crate::logs::raydium_clmm::parse_collect_fee_from_data(data, metadata)
629 }
630
631 discriminators::RAYDIUM_CPMM_SWAP_BASE_IN => {
633 crate::logs::raydium_cpmm::parse_swap_base_in_from_data(data, metadata)
634 }
635 discriminators::RAYDIUM_CPMM_SWAP_BASE_OUT => {
636 crate::logs::raydium_cpmm::parse_swap_base_out_from_data(data, metadata)
637 }
638 discriminators::RAYDIUM_CPMM_DEPOSIT => {
641 crate::logs::raydium_cpmm::parse_deposit_from_data(data, metadata)
642 }
643 discriminators::RAYDIUM_CPMM_WITHDRAW => {
644 crate::logs::raydium_cpmm::parse_withdraw_from_data(data, metadata)
645 }
646
647 discriminators::RAYDIUM_AMM_SWAP_BASE_IN => {
649 crate::logs::raydium_amm::parse_swap_base_in_from_data(data, metadata)
650 }
651 discriminators::RAYDIUM_AMM_SWAP_BASE_OUT => {
652 crate::logs::raydium_amm::parse_swap_base_out_from_data(data, metadata)
653 }
654 discriminators::RAYDIUM_AMM_DEPOSIT => {
655 crate::logs::raydium_amm::parse_deposit_from_data(data, metadata)
656 }
657 discriminators::RAYDIUM_AMM_WITHDRAW => {
658 crate::logs::raydium_amm::parse_withdraw_from_data(data, metadata)
659 }
660 discriminators::RAYDIUM_AMM_INITIALIZE2 => {
661 crate::logs::raydium_amm::parse_initialize2_from_data(data, metadata)
662 }
663
664 discriminators::ORCA_TRADED => {
666 crate::logs::orca_whirlpool::parse_traded_from_data(data, metadata)
667 }
668 discriminators::ORCA_LIQUIDITY_INCREASED => {
669 crate::logs::orca_whirlpool::parse_liquidity_increased_from_data(data, metadata)
670 }
671 discriminators::ORCA_LIQUIDITY_DECREASED => {
672 crate::logs::orca_whirlpool::parse_liquidity_decreased_from_data(data, metadata)
673 }
674 discriminators::ORCA_POOL_INITIALIZED => {
675 crate::logs::orca_whirlpool::parse_pool_initialized_from_data(data, metadata)
676 }
677
678 discriminators::METEORA_AMM_SWAP => {
680 crate::logs::meteora_amm::parse_swap_from_data(data, metadata)
681 }
682 discriminators::METEORA_AMM_ADD_LIQUIDITY => {
683 crate::logs::meteora_amm::parse_add_liquidity_from_data(data, metadata)
684 }
685 discriminators::METEORA_AMM_REMOVE_LIQUIDITY => {
686 crate::logs::meteora_amm::parse_remove_liquidity_from_data(data, metadata)
687 }
688 discriminators::METEORA_AMM_BOOTSTRAP_LIQUIDITY => {
689 crate::logs::meteora_amm::parse_bootstrap_liquidity_from_data(data, metadata)
690 }
691 discriminators::METEORA_AMM_POOL_CREATED => {
692 crate::logs::meteora_amm::parse_pool_created_from_data(data, metadata)
693 }
694
695 discriminators::METEORA_DAMM_SWAP
697 | discriminators::METEORA_DAMM_SWAP2
698 | discriminators::METEORA_DAMM_ADD_LIQUIDITY
699 | discriminators::METEORA_DAMM_REMOVE_LIQUIDITY
700 | discriminators::METEORA_DAMM_INITIALIZE_POOL
701 | discriminators::METEORA_DAMM_CREATE_POSITION
702 | discriminators::METEORA_DAMM_CLOSE_POSITION => crate::logs::parse_meteora_damm_log(
703 log,
704 signature,
705 slot,
706 tx_index,
707 block_time_us,
708 grpc_recv_us,
709 ),
710
711 _ => {
717 if let Some(event) = crate::logs::parse_meteora_dlmm_log(
719 log,
720 signature,
721 slot,
722 tx_index,
723 block_time_us,
724 grpc_recv_us,
725 ) {
726 return Some(event);
727 }
728 None
729 }
730 }
731}
732
733#[inline(always)]
734fn filter_allows_untyped_protocol(event_type_filter: Option<&EventTypeFilter>) -> bool {
735 event_type_filter.and_then(|f| f.include_only.as_ref()).is_none()
736}
737
738#[inline(always)]
739fn parse_program_scoped_event(
740 program_id: &Pubkey,
741 discriminator: u64,
742 data: &[u8],
743 metadata: EventMetadata,
744 log: &str,
745 signature: Signature,
746 slot: u64,
747 tx_index: u64,
748 block_time_us: Option<i64>,
749 grpc_recv_us: i64,
750 event_type_filter: Option<&EventTypeFilter>,
751 is_created_buy: bool,
752) -> Option<DexEvent> {
753 match *program_id {
754 program_ids::PUMPFUN_PROGRAM_ID => {
755 if let Some(filter) = event_type_filter {
756 if !filter.includes_pumpfun() {
757 return None;
758 }
759 }
760 match discriminator {
761 discriminators::PUMPFUN_TRADE => {
762 let event =
763 crate::logs::pump::parse_trade_from_data(data, metadata, is_created_buy)?;
764 filter_pumpfun_trade_variant(event, event_type_filter)
765 }
766 discriminators::PUMPFUN_CREATE => {
767 crate::logs::pump::parse_create_from_data(data, metadata)
768 }
769 discriminators::PUMPFUN_MIGRATE => {
770 crate::logs::pump::parse_migrate_from_data(data, metadata)
771 }
772 discriminators::PUMPFUN_MIGRATE_BONDING_CURVE_CREATOR => {
773 crate::logs::pump::parse_migrate_bonding_curve_creator_from_data(data, metadata)
774 }
775 _ => None,
776 }
777 }
778 program_ids::PUMP_FEES_PROGRAM_ID => {
779 if let Some(filter) = event_type_filter {
780 if !filter.includes_pump_fees() {
781 return None;
782 }
783 }
784 match discriminator {
785 discriminators::PUMP_FEES_CREATE_FEE_SHARING_CONFIG => {
786 crate::logs::pump_fees::parse_create_fee_sharing_config_from_data(
787 data, metadata,
788 )
789 }
790 discriminators::PUMP_FEES_INITIALIZE_FEE_CONFIG => {
791 crate::logs::pump_fees::parse_initialize_fee_config_from_data(data, metadata)
792 }
793 discriminators::PUMP_FEES_RESET_FEE_SHARING_CONFIG => {
794 crate::logs::pump_fees::parse_reset_fee_sharing_config_from_data(data, metadata)
795 }
796 discriminators::PUMP_FEES_REVOKE_FEE_SHARING_AUTHORITY => {
797 crate::logs::pump_fees::parse_revoke_fee_sharing_authority_from_data(
798 data, metadata,
799 )
800 }
801 discriminators::PUMP_FEES_TRANSFER_FEE_SHARING_AUTHORITY => {
802 crate::logs::pump_fees::parse_transfer_fee_sharing_authority_from_data(
803 data, metadata,
804 )
805 }
806 discriminators::PUMP_FEES_UPDATE_ADMIN => {
807 crate::logs::pump_fees::parse_update_admin_from_data(data, metadata)
808 }
809 discriminators::PUMP_FEES_UPDATE_FEE_CONFIG => {
810 crate::logs::pump_fees::parse_update_fee_config_from_data(data, metadata)
811 }
812 discriminators::PUMP_FEES_UPDATE_FEE_SHARES => {
813 crate::logs::pump_fees::parse_update_fee_shares_from_data(data, metadata)
814 }
815 discriminators::PUMP_FEES_UPSERT_FEE_TIERS => {
816 crate::logs::pump_fees::parse_upsert_fee_tiers_from_data(data, metadata)
817 }
818 _ => None,
819 }
820 }
821 program_ids::PUMPSWAP_PROGRAM_ID => {
822 if let Some(filter) = event_type_filter {
823 if !filter.includes_pumpswap() {
824 return None;
825 }
826 }
827 match discriminator {
828 discriminators::PUMPSWAP_BUY => {
829 crate::logs::pump_amm::parse_buy_from_data(data, metadata)
830 }
831 discriminators::PUMPSWAP_SELL => {
832 crate::logs::pump_amm::parse_sell_from_data(data, metadata)
833 }
834 discriminators::PUMPSWAP_CREATE_POOL => {
835 crate::logs::pump_amm::parse_create_pool_from_data(data, metadata)
836 }
837 discriminators::PUMPSWAP_ADD_LIQUIDITY => {
838 crate::logs::pump_amm::parse_add_liquidity_from_data(data, metadata)
839 }
840 discriminators::PUMPSWAP_REMOVE_LIQUIDITY => {
841 crate::logs::pump_amm::parse_remove_liquidity_from_data(data, metadata)
842 }
843 _ => None,
844 }
845 }
846 program_ids::BONK_PROGRAM_ID => {
847 if let Some(filter) = event_type_filter {
848 if !filter.includes_raydium_launchpad() {
849 return None;
850 }
851 }
852 match discriminator {
853 discriminators::RAYDIUM_LAUNCHPAD_TRADE => {
854 crate::logs::raydium_launchpad::parse_trade_from_data(data, metadata)
855 }
856 discriminators::RAYDIUM_LAUNCHPAD_POOL_CREATE => {
857 crate::logs::raydium_launchpad::parse_pool_create_from_data(data, metadata)
858 }
859 _ => None,
860 }
861 }
862 program_ids::RAYDIUM_CLMM_PROGRAM_ID => {
863 if !filter_allows_untyped_protocol(event_type_filter) {
864 return None;
865 }
866 match discriminator {
867 discriminators::RAYDIUM_CLMM_SWAP => {
868 crate::logs::raydium_clmm::parse_swap_from_data(data, metadata)
869 }
870 discriminators::RAYDIUM_CLMM_INCREASE_LIQUIDITY => {
871 crate::logs::raydium_clmm::parse_increase_liquidity_from_data(data, metadata)
872 }
873 discriminators::RAYDIUM_CLMM_DECREASE_LIQUIDITY => {
874 crate::logs::raydium_clmm::parse_decrease_liquidity_from_data(data, metadata)
875 }
876 discriminators::RAYDIUM_CLMM_CREATE_POOL => {
877 crate::logs::raydium_clmm::parse_create_pool_from_data(data, metadata)
878 }
879 discriminators::RAYDIUM_CLMM_COLLECT_FEE => {
880 crate::logs::raydium_clmm::parse_collect_fee_from_data(data, metadata)
881 }
882 _ => None,
883 }
884 }
885 program_ids::RAYDIUM_CPMM_PROGRAM_ID => {
886 if !filter_allows_untyped_protocol(event_type_filter) {
887 return None;
888 }
889 match discriminator {
890 discriminators::RAYDIUM_CPMM_SWAP_BASE_IN => {
891 crate::logs::raydium_cpmm::parse_swap_base_in_from_data(data, metadata)
892 }
893 discriminators::RAYDIUM_CPMM_SWAP_BASE_OUT => {
894 crate::logs::raydium_cpmm::parse_swap_base_out_from_data(data, metadata)
895 }
896 discriminators::RAYDIUM_CPMM_CREATE_POOL => {
897 crate::logs::raydium_cpmm::parse_create_pool_from_data(data, metadata)
898 }
899 discriminators::RAYDIUM_CPMM_DEPOSIT => {
900 crate::logs::raydium_cpmm::parse_deposit_from_data(data, metadata)
901 }
902 discriminators::RAYDIUM_CPMM_WITHDRAW => {
903 crate::logs::raydium_cpmm::parse_withdraw_from_data(data, metadata)
904 }
905 _ => None,
906 }
907 }
908 program_ids::RAYDIUM_AMM_V4_PROGRAM_ID => {
909 if !filter_allows_untyped_protocol(event_type_filter) {
910 return None;
911 }
912 match discriminator {
913 discriminators::RAYDIUM_AMM_SWAP_BASE_IN => {
914 crate::logs::raydium_amm::parse_swap_base_in_from_data(data, metadata)
915 }
916 discriminators::RAYDIUM_AMM_SWAP_BASE_OUT => {
917 crate::logs::raydium_amm::parse_swap_base_out_from_data(data, metadata)
918 }
919 discriminators::RAYDIUM_AMM_DEPOSIT => {
920 crate::logs::raydium_amm::parse_deposit_from_data(data, metadata)
921 }
922 discriminators::RAYDIUM_AMM_WITHDRAW => {
923 crate::logs::raydium_amm::parse_withdraw_from_data(data, metadata)
924 }
925 discriminators::RAYDIUM_AMM_INITIALIZE2 => {
926 crate::logs::raydium_amm::parse_initialize2_from_data(data, metadata)
927 }
928 _ => None,
929 }
930 }
931 program_ids::ORCA_WHIRLPOOL_PROGRAM_ID => {
932 if !filter_allows_untyped_protocol(event_type_filter) {
933 return None;
934 }
935 match discriminator {
936 discriminators::ORCA_TRADED => {
937 crate::logs::orca_whirlpool::parse_traded_from_data(data, metadata)
938 }
939 discriminators::ORCA_LIQUIDITY_INCREASED => {
940 crate::logs::orca_whirlpool::parse_liquidity_increased_from_data(data, metadata)
941 }
942 discriminators::ORCA_LIQUIDITY_DECREASED => {
943 crate::logs::orca_whirlpool::parse_liquidity_decreased_from_data(data, metadata)
944 }
945 discriminators::ORCA_POOL_INITIALIZED => {
946 crate::logs::orca_whirlpool::parse_pool_initialized_from_data(data, metadata)
947 }
948 _ => None,
949 }
950 }
951 program_ids::METEORA_POOLS_PROGRAM_ID => {
952 if !filter_allows_untyped_protocol(event_type_filter) {
953 return None;
954 }
955 match discriminator {
956 discriminators::METEORA_AMM_SWAP => {
957 crate::logs::meteora_amm::parse_swap_from_data(data, metadata)
958 }
959 discriminators::METEORA_AMM_ADD_LIQUIDITY => {
960 crate::logs::meteora_amm::parse_add_liquidity_from_data(data, metadata)
961 }
962 discriminators::METEORA_AMM_REMOVE_LIQUIDITY => {
963 crate::logs::meteora_amm::parse_remove_liquidity_from_data(data, metadata)
964 }
965 discriminators::METEORA_AMM_BOOTSTRAP_LIQUIDITY => {
966 crate::logs::meteora_amm::parse_bootstrap_liquidity_from_data(data, metadata)
967 }
968 discriminators::METEORA_AMM_POOL_CREATED => {
969 crate::logs::meteora_amm::parse_pool_created_from_data(data, metadata)
970 }
971 _ => None,
972 }
973 }
974 program_ids::METEORA_DAMM_V2_PROGRAM_ID => {
975 if let Some(filter) = event_type_filter {
976 if !filter.includes_meteora_damm_v2() {
977 return None;
978 }
979 }
980 crate::logs::parse_meteora_damm_log(
981 log,
982 signature,
983 slot,
984 tx_index,
985 block_time_us,
986 grpc_recv_us,
987 )
988 }
989 program_ids::METEORA_DLMM_PROGRAM_ID => {
990 if !filter_allows_untyped_protocol(event_type_filter) {
991 return None;
992 }
993 crate::logs::parse_meteora_dlmm_log(
994 log,
995 signature,
996 slot,
997 tx_index,
998 block_time_us,
999 grpc_recv_us,
1000 )
1001 }
1002 _ => None,
1003 }
1004}
1005
1006#[inline(always)]
1007fn filter_pumpfun_trade_variant(
1008 event: DexEvent,
1009 event_type_filter: Option<&EventTypeFilter>,
1010) -> Option<DexEvent> {
1011 if let Some(filter) = event_type_filter {
1012 if let Some(ref include_only) = filter.include_only {
1013 let has_specific_filter = include_only.iter().any(|t| {
1014 matches!(
1015 t,
1016 EventType::PumpFunBuy
1017 | EventType::PumpFunSell
1018 | EventType::PumpFunBuyExactSolIn
1019 | EventType::PumpFunCreate
1020 | EventType::PumpFunCreateV2
1021 )
1022 });
1023 if has_specific_filter {
1024 let event_type_matches = match &event {
1025 DexEvent::PumpFunBuy(_) => include_only.contains(&EventType::PumpFunBuy),
1026 DexEvent::PumpFunSell(_) => include_only.contains(&EventType::PumpFunSell),
1027 DexEvent::PumpFunBuyExactSolIn(_) => {
1028 include_only.contains(&EventType::PumpFunBuyExactSolIn)
1029 }
1030 DexEvent::PumpFunTrade(_) => include_only.contains(&EventType::PumpFunTrade),
1031 DexEvent::PumpFunCreate(_) => include_only.contains(&EventType::PumpFunCreate),
1032 DexEvent::PumpFunCreateV2(_) => {
1033 include_only.contains(&EventType::PumpFunCreateV2)
1034 }
1035 _ => false,
1036 };
1037 if !event_type_matches {
1038 return None;
1039 }
1040 }
1041 }
1042 }
1043 Some(event)
1044}
1045
1046#[inline(always)]
1048fn discriminator_to_event_type(discriminator: u64) -> Option<EventType> {
1049 match discriminator {
1050 discriminators::PUMPFUN_CREATE => Some(EventType::PumpFunCreate),
1051 discriminators::PUMPFUN_TRADE => Some(EventType::PumpFunTrade),
1052 discriminators::PUMPFUN_MIGRATE => Some(EventType::PumpFunMigrate),
1053 discriminators::PUMP_FEES_CREATE_FEE_SHARING_CONFIG => {
1054 Some(EventType::PumpFeesCreateFeeSharingConfig)
1055 }
1056 discriminators::PUMP_FEES_INITIALIZE_FEE_CONFIG => {
1057 Some(EventType::PumpFeesInitializeFeeConfig)
1058 }
1059 discriminators::PUMP_FEES_RESET_FEE_SHARING_CONFIG => {
1060 Some(EventType::PumpFeesResetFeeSharingConfig)
1061 }
1062 discriminators::PUMP_FEES_REVOKE_FEE_SHARING_AUTHORITY => {
1063 Some(EventType::PumpFeesRevokeFeeSharingAuthority)
1064 }
1065 discriminators::PUMP_FEES_TRANSFER_FEE_SHARING_AUTHORITY => {
1066 Some(EventType::PumpFeesTransferFeeSharingAuthority)
1067 }
1068 discriminators::PUMP_FEES_UPDATE_ADMIN => Some(EventType::PumpFeesUpdateAdmin),
1069 discriminators::PUMP_FEES_UPDATE_FEE_CONFIG => Some(EventType::PumpFeesUpdateFeeConfig),
1070 discriminators::PUMP_FEES_UPDATE_FEE_SHARES => Some(EventType::PumpFeesUpdateFeeShares),
1071 discriminators::PUMP_FEES_UPSERT_FEE_TIERS => Some(EventType::PumpFeesUpsertFeeTiers),
1072 discriminators::PUMPFUN_MIGRATE_BONDING_CURVE_CREATOR => {
1073 Some(EventType::PumpFunMigrateBondingCurveCreator)
1074 }
1075 discriminators::PUMPSWAP_BUY => Some(EventType::PumpSwapBuy),
1076 discriminators::PUMPSWAP_SELL => Some(EventType::PumpSwapSell),
1077 discriminators::PUMPSWAP_CREATE_POOL => Some(EventType::PumpSwapCreatePool),
1078 discriminators::PUMPSWAP_ADD_LIQUIDITY => Some(EventType::PumpSwapLiquidityAdded),
1079 discriminators::PUMPSWAP_REMOVE_LIQUIDITY => Some(EventType::PumpSwapLiquidityRemoved),
1080 _ => None,
1081 }
1082}
1083
1084#[inline]
1088pub fn detect_pumpfun_create(logs: &[String]) -> bool {
1089 logs.iter().any(|log| PUMPFUN_CREATE_FINDER.find(log.as_bytes()).is_some())
1090}
1091
1092static INVOKE_FINDER: Lazy<memmem::Finder> = Lazy::new(|| memmem::Finder::new(b"invoke ["));
1094
1095#[inline]
1098pub fn parse_invoke_info(log: &str) -> Option<(&str, usize)> {
1099 let log_bytes = log.as_bytes();
1100
1101 let invoke_start = INVOKE_FINDER.find(log_bytes)?;
1103 let bracket_start = invoke_start + 8; if bracket_start >= log_bytes.len() {
1107 return None;
1108 }
1109
1110 let mut depth = 0usize;
1112 for &byte in &log_bytes[bracket_start..] {
1113 match byte {
1114 b'0'..=b'9' => {
1115 depth = depth * 10 + (byte - b'0') as usize;
1116 }
1117 b']' => break,
1118 _ => return None, }
1120 }
1121
1122 if invoke_start < 8 {
1124 return None; }
1126
1127 let program_start = 8; let program_end = invoke_start - 1; if program_end <= program_start {
1131 return None;
1132 }
1133
1134 let program_id = std::str::from_utf8(&log_bytes[program_start..program_end]).ok()?;
1135
1136 Some((program_id, depth))
1137}
1138
1139#[inline]
1141pub fn parse_program_complete_info(log: &str) -> Option<&str> {
1142 let rest = log.strip_prefix("Program ")?;
1143 if let Some(pos) = rest.find(" success") {
1144 return Some(&rest[..pos]);
1145 }
1146 if let Some(pos) = rest.find(" failed:") {
1147 return Some(&rest[..pos]);
1148 }
1149 None
1150}
1151
1152#[cfg(test)]
1153mod tests {
1154 use super::*;
1155 use base64::{engine::general_purpose::STANDARD, Engine as _};
1156 use solana_sdk::{pubkey::Pubkey, signature::Signature};
1157
1158 #[test]
1159 fn program_scoped_launchpad_trade_is_not_parsed_as_pumpfun() {
1160 let pool = Pubkey::new_unique();
1161 let mut raw = Vec::new();
1162 raw.extend_from_slice(&discriminators::RAYDIUM_LAUNCHPAD_TRADE.to_le_bytes());
1163 raw.extend_from_slice(pool.as_ref());
1164 for value in 0u64..13 {
1165 raw.extend_from_slice(&(100 + value).to_le_bytes());
1166 }
1167 raw.push(1); raw.push(2); raw.push(1); let log = format!("Program data: {}", STANDARD.encode(raw));
1172 let filter = EventTypeFilter::include_only(vec![EventType::BonkTrade]);
1173 let event = parse_log_optimized_with_program_id(
1174 &log,
1175 Signature::default(),
1176 1,
1177 2,
1178 Some(3),
1179 4,
1180 Some(&filter),
1181 false,
1182 None,
1183 Some(&program_ids::BONK_PROGRAM_ID),
1184 )
1185 .expect("launchpad trade should parse");
1186
1187 match event {
1188 DexEvent::BonkTrade(trade) => {
1189 assert_eq!(trade.pool_state, pool);
1190 assert_eq!(trade.amount_in, 107);
1191 assert_eq!(trade.amount_out, 108);
1192 assert!(!trade.is_buy);
1193 assert!(trade.exact_in);
1194 }
1195 other => panic!("expected BonkTrade, got {other:?}"),
1196 }
1197 }
1198
1199 #[test]
1200 fn completion_parser_extracts_program_id() {
1201 assert_eq!(
1202 parse_program_complete_info(
1203 "Program LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj success"
1204 ),
1205 Some("LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj")
1206 );
1207 assert_eq!(
1208 parse_program_complete_info(
1209 "Program CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C failed: custom program error: 0x1"
1210 ),
1211 Some("CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C")
1212 );
1213 }
1214}