1pub mod meteora_amm;
6pub mod meteora_damm;
7pub mod meteora_dlmm;
8pub mod orca_whirlpool;
9pub mod program_ids;
10pub mod pump;
11pub mod pump_amm;
12pub mod pump_fees;
13pub mod raydium_amm;
14pub mod raydium_clmm;
15pub mod raydium_cpmm;
16pub mod raydium_launchlab;
17pub mod utils;
18
19pub mod all_inner;
21pub mod inner_common; pub mod pump_amm_inner; pub mod pump_inner; pub mod raydium_clmm_inner; use crate::grpc::types::EventTypeFilter;
26pub use meteora_damm::parse_instruction as parse_meteora_damm_instruction;
28pub use pump::parse_instruction as parse_pumpfun_instruction;
29pub use pump_amm::parse_instruction as parse_pumpswap_instruction;
30pub use raydium_launchlab::parse_instruction as parse_raydium_launchlab_instruction;
31
32pub use utils::*;
34
35use crate::core::events::DexEvent;
36use program_ids::*;
37use solana_sdk::{pubkey::Pubkey, signature::Signature};
38
39#[inline(always)]
40fn disc8(instruction_data: &[u8]) -> Option<[u8; 8]> {
41 instruction_data.get(..8)?.try_into().ok()
42}
43
44#[inline(always)]
45fn supports_pumpfun_instruction(disc: [u8; 8]) -> bool {
46 matches!(
47 disc,
48 pump::discriminators::CREATE
49 | pump::discriminators::BUY_V3
50 | pump::discriminators::BUY_EXACT_QUOTE_IN_V3
51 | pump::discriminators::SELL_V3
52 | pump::discriminators::CREATE_V2
53 | pump::discriminators::BUY
54 | pump::discriminators::SELL
55 | pump::discriminators::BUY_EXACT_SOL_IN
56 | pump::discriminators::BUY_V2
57 | pump::discriminators::BUY_EXACT_QUOTE_IN_V2
58 | pump::discriminators::SELL_V2
59 | pump::discriminators::MIGRATE_BONDING_CURVE_CREATOR
60 )
61}
62
63#[inline(always)]
64fn supports_pumpswap_instruction(disc: [u8; 8]) -> bool {
65 matches!(
66 disc,
67 pump_amm::discriminators::BUY
68 | pump_amm::discriminators::BUY_V2
69 | pump_amm::discriminators::BUY_EXACT_QUOTE_IN_V2
70 | pump_amm::discriminators::SELL_V2
71 | pump_amm::discriminators::SELL
72 | pump_amm::discriminators::CREATE_POOL
73 | pump_amm::discriminators::BUY_EXACT_QUOTE_IN
74 | pump_amm::discriminators::DEPOSIT
75 | pump_amm::discriminators::WITHDRAW
76 )
77}
78
79#[inline(always)]
80fn supports_pump_fees_instruction(disc: [u8; 8]) -> bool {
81 matches!(
82 disc,
83 pump_fees::CREATE_FEE_SHARING_IX
84 | pump_fees::INITIALIZE_FEE_CONFIG_IX
85 | pump_fees::RESET_FEE_SHARING_IX
86 | pump_fees::RESET_FEE_SHARING_V2_IX
87 | pump_fees::REVOKE_FEE_SHARING_IX
88 | pump_fees::TRANSFER_FEE_SHARING_IX
89 | pump_fees::UPDATE_ADMIN_IX
90 | pump_fees::UPDATE_FEE_CONFIG_IX
91 | pump_fees::UPDATE_FEE_SHARES_IX
92 | pump_fees::UPDATE_FEE_SHARES_V2_IX
93 | pump_fees::UPSERT_FEE_TIERS_IX
94 )
95}
96
97#[inline(always)]
98fn supports_launchlab_instruction(disc: [u8; 8]) -> bool {
99 matches!(
100 disc,
101 raydium_launchlab::discriminators::BUY_EXACT_IN
102 | raydium_launchlab::discriminators::BUY_EXACT_OUT
103 | raydium_launchlab::discriminators::SELL_EXACT_IN
104 | raydium_launchlab::discriminators::SELL_EXACT_OUT
105 | raydium_launchlab::discriminators::INITIALIZE
106 | raydium_launchlab::discriminators::INITIALIZE_V2
107 | raydium_launchlab::discriminators::INITIALIZE_WITH_TOKEN_2022
108 | raydium_launchlab::discriminators::MIGRATE_TO_AMM
109 | raydium_launchlab::discriminators::MIGRATE_TO_CPSWAP
110 )
111}
112
113#[inline(always)]
114fn supports_cpmm_instruction(disc: [u8; 8]) -> bool {
115 matches!(
116 disc,
117 raydium_cpmm::discriminators::SWAP_BASE_IN
118 | raydium_cpmm::discriminators::SWAP_BASE_OUT
119 | raydium_cpmm::discriminators::INITIALIZE
120 | raydium_cpmm::discriminators::INITIALIZE_PERMISSION
121 | raydium_cpmm::discriminators::DEPOSIT
122 | raydium_cpmm::discriminators::WITHDRAW
123 | raydium_cpmm::discriminators::COLLECT_CREATOR_FEE
124 | raydium_cpmm::discriminators::COLLECT_CREATOR_FEE_PERMISSIONLESS
125 )
126}
127
128#[inline(always)]
129fn supports_clmm_instruction(disc: [u8; 8]) -> bool {
130 matches!(
131 disc,
132 raydium_clmm::discriminators::SWAP
133 | raydium_clmm::discriminators::SWAP_V2
134 | raydium_clmm::discriminators::INCREASE_LIQUIDITY_V2
135 | raydium_clmm::discriminators::DECREASE_LIQUIDITY_V2
136 | raydium_clmm::discriminators::CREATE_POOL
137 | raydium_clmm::discriminators::CREATE_CUSTOMIZABLE_POOL
138 | raydium_clmm::discriminators::OPEN_POSITION
139 | raydium_clmm::discriminators::OPEN_POSITION_V2
140 | raydium_clmm::discriminators::OPEN_POSITION_WITH_TOKEN_22_NFT
141 | raydium_clmm::discriminators::CLOSE_POSITION
142 )
143}
144
145#[inline(always)]
146fn supports_raydium_amm_v4_instruction(instruction_data: &[u8]) -> bool {
147 matches!(
148 instruction_data.first().copied(),
149 Some(raydium_amm::discriminators::SWAP_BASE_IN)
150 | Some(raydium_amm::discriminators::SWAP_BASE_OUT)
151 | Some(raydium_amm::discriminators::DEPOSIT)
152 | Some(raydium_amm::discriminators::WITHDRAW)
153 | Some(raydium_amm::discriminators::INITIALIZE2)
154 | Some(raydium_amm::discriminators::WITHDRAW_PNL)
155 | Some(raydium_amm::discriminators::SWAP_BASE_IN_V2)
156 | Some(raydium_amm::discriminators::SWAP_BASE_OUT_V2)
157 )
158}
159
160#[inline(always)]
161fn supports_orca_instruction(disc: [u8; 8]) -> bool {
162 matches!(
163 disc,
164 orca_whirlpool::discriminators::SWAP
165 | orca_whirlpool::discriminators::SWAP_V2
166 | orca_whirlpool::discriminators::INCREASE_LIQUIDITY
167 | orca_whirlpool::discriminators::DECREASE_LIQUIDITY
168 | orca_whirlpool::discriminators::INITIALIZE_POOL
169 | orca_whirlpool::discriminators::INITIALIZE_POOL_V2
170 | orca_whirlpool::discriminators::INCREASE_LIQUIDITY_V2
171 | orca_whirlpool::discriminators::DECREASE_LIQUIDITY_V2
172 )
173}
174
175#[inline(always)]
176fn supports_meteora_pools_instruction(disc: [u8; 8]) -> bool {
177 matches!(
178 disc,
179 meteora_amm::discriminators::SWAP
180 | meteora_amm::discriminators::ADD_LIQUIDITY
181 | meteora_amm::discriminators::ADD_IMBALANCE_LIQUIDITY
182 | meteora_amm::discriminators::REMOVE_LIQUIDITY
183 | meteora_amm::discriminators::REMOVE_LIQUIDITY_SINGLE_SIDE
184 | meteora_amm::discriminators::BOOTSTRAP_LIQUIDITY
185 | meteora_amm::discriminators::INITIALIZE_PERMISSIONED_POOL
186 | meteora_amm::discriminators::INITIALIZE_PERMISSIONLESS_POOL
187 | meteora_amm::discriminators::INITIALIZE_PERMISSIONLESS_POOL_WITH_FEE_TIER
188 | meteora_amm::discriminators::INITIALIZE_CUSTOMIZABLE_POOL
189 | meteora_amm::discriminators::CREATE_POOL
190 | meteora_amm::discriminators::CREATE_POOL_WITH_CONFIG2
191 | meteora_amm::discriminators::SET_POOL_FEES
192 )
193}
194
195#[inline(always)]
196fn supports_meteora_damm_v2_instruction(instruction_data: &[u8]) -> bool {
197 let Some(disc) = disc8(instruction_data) else {
198 return false;
199 };
200 if disc == meteora_damm::discriminators::INITIALIZE_POOL {
201 return true;
202 }
203 let Some(cpi_disc) = instruction_data.get(8..16).and_then(|bytes| bytes.try_into().ok()) else {
204 return false;
205 };
206 matches!(
207 cpi_disc,
208 meteora_damm::discriminators::SWAP_LOG
209 | meteora_damm::discriminators::SWAP2_LOG
210 | meteora_damm::discriminators::CREATE_POSITION_LOG
211 | meteora_damm::discriminators::CLOSE_POSITION_LOG
212 | meteora_damm::discriminators::ADD_LIQUIDITY_LOG
213 | meteora_damm::discriminators::REMOVE_LIQUIDITY_LOG
214 )
215}
216
217#[inline(always)]
218fn supports_meteora_dlmm_instruction(instruction_data: &[u8]) -> bool {
219 let Some(disc) = disc8(instruction_data) else {
220 return false;
221 };
222 matches!(
223 disc,
224 meteora_dlmm::discriminators::INITIALIZE_LB_PAIR
225 | meteora_dlmm::discriminators::INITIALIZE_LB_PAIR2
226 | meteora_dlmm::discriminators::INITIALIZE_BIN_ARRAY
227 | meteora_dlmm::discriminators::ADD_LIQUIDITY
228 | meteora_dlmm::discriminators::ADD_LIQUIDITY2
229 | meteora_dlmm::discriminators::REMOVE_LIQUIDITY
230 | meteora_dlmm::discriminators::REMOVE_LIQUIDITY2
231 | meteora_dlmm::discriminators::INITIALIZE_POSITION
232 | meteora_dlmm::discriminators::INITIALIZE_POSITION2
233 | meteora_dlmm::discriminators::INITIALIZE_POSITION_PDA
234 | meteora_dlmm::discriminators::SWAP
235 | meteora_dlmm::discriminators::SWAP2
236 | meteora_dlmm::discriminators::SWAP_EXACT_OUT
237 | meteora_dlmm::discriminators::SWAP_EXACT_OUT2
238 | meteora_dlmm::discriminators::SWAP_WITH_PRICE_IMPACT
239 | meteora_dlmm::discriminators::SWAP_WITH_PRICE_IMPACT2
240 | meteora_dlmm::discriminators::CLAIM_FEE
241 | meteora_dlmm::discriminators::CLAIM_FEE2
242 | meteora_dlmm::discriminators::CLOSE_POSITION
243 | meteora_dlmm::discriminators::CLOSE_POSITION2
244 )
245}
246
247#[inline(always)]
248pub(crate) fn instruction_data_may_parse(program_id: &Pubkey, instruction_data: &[u8]) -> bool {
249 if instruction_data.is_empty() {
250 return false;
251 }
252 if *program_id == RAYDIUM_AMM_V4_PROGRAM_ID {
253 return supports_raydium_amm_v4_instruction(instruction_data);
254 }
255 if *program_id == METEORA_DLMM_PROGRAM_ID {
256 return supports_meteora_dlmm_instruction(instruction_data);
257 }
258 if *program_id == METEORA_DBC_PROGRAM_ID {
259 return instruction_data.get(..8) == Some(&[228, 69, 165, 46, 81, 203, 154, 29])
260 && instruction_data.len() >= 16;
261 }
262 if *program_id == METEORA_DAMM_V2_PROGRAM_ID {
263 return supports_meteora_damm_v2_instruction(instruction_data);
264 }
265
266 let Some(disc) = disc8(instruction_data) else {
267 return false;
268 };
269 if *program_id == PUMPFUN_PROGRAM_ID {
270 supports_pumpfun_instruction(disc)
271 } else if *program_id == PUMPSWAP_PROGRAM_ID {
272 supports_pumpswap_instruction(disc)
273 } else if *program_id == PUMP_FEES_PROGRAM_ID {
274 supports_pump_fees_instruction(disc)
275 } else if *program_id == RAYDIUM_LAUNCHLAB_PROGRAM_ID {
276 supports_launchlab_instruction(disc)
277 } else if *program_id == RAYDIUM_CPMM_PROGRAM_ID {
278 supports_cpmm_instruction(disc)
279 } else if *program_id == RAYDIUM_CLMM_PROGRAM_ID {
280 supports_clmm_instruction(disc)
281 } else if *program_id == ORCA_WHIRLPOOL_PROGRAM_ID {
282 supports_orca_instruction(disc)
283 } else if *program_id == METEORA_POOLS_PROGRAM_ID {
284 supports_meteora_pools_instruction(disc)
285 } else {
286 false
287 }
288}
289
290#[inline(always)]
291pub(crate) fn normal_instruction_data_may_parse(
292 program_id: &Pubkey,
293 instruction_data: &[u8],
294) -> bool {
295 if *program_id == METEORA_DBC_PROGRAM_ID {
296 return false;
297 }
298 if *program_id == METEORA_DAMM_V2_PROGRAM_ID {
299 return disc8(instruction_data)
300 .is_some_and(|disc| disc == meteora_damm::discriminators::INITIALIZE_POOL);
301 }
302 instruction_data_may_parse(program_id, instruction_data)
303}
304
305#[inline(always)]
306fn filter_parsed_event(
307 event: Option<DexEvent>,
308 event_type_filter: Option<&EventTypeFilter>,
309) -> Option<DexEvent> {
310 let event = event?;
311 if event_type_filter.map(|f| f.should_include_dex_event(&event)).unwrap_or(true) {
312 Some(event)
313 } else {
314 None
315 }
316}
317
318#[inline]
320pub fn parse_instruction_unified(
321 instruction_data: &[u8],
322 accounts: &[Pubkey],
323 signature: Signature,
324 slot: u64,
325 tx_index: u64,
326 block_time_us: Option<i64>,
327 grpc_recv_us: i64,
328 event_type_filter: Option<&EventTypeFilter>,
329 program_id: &Pubkey,
330) -> Option<DexEvent> {
331 if instruction_data.is_empty() {
333 return None;
334 }
335
336 if *program_id == METEORA_DBC_PROGRAM_ID {
337 if event_type_filter.is_some_and(|f| !f.includes_meteora_dbc()) {
338 return None;
339 }
340 let metadata = crate::logs::utils::create_metadata_simple(
341 signature,
342 slot,
343 tx_index,
344 block_time_us,
345 Pubkey::default(),
346 grpc_recv_us,
347 );
348 return filter_parsed_event(
349 crate::logs::meteora_dbc::parse_event_cpi(instruction_data, metadata),
350 event_type_filter,
351 );
352 }
353
354 if *program_id == PUMPFUN_PROGRAM_ID {
358 if event_type_filter.is_some() && !event_type_filter.unwrap().includes_pumpfun() {
359 return None;
360 }
361 return filter_parsed_event(
362 parse_pumpfun_instruction(
363 instruction_data,
364 accounts,
365 signature,
366 slot,
367 tx_index,
368 block_time_us,
369 grpc_recv_us,
370 ),
371 event_type_filter,
372 );
373 }
374 else if *program_id == PUMPSWAP_PROGRAM_ID {
376 if event_type_filter.is_some() && !event_type_filter.unwrap().includes_pumpswap() {
377 return None;
378 }
379 return filter_parsed_event(
380 parse_pumpswap_instruction(
381 instruction_data,
382 accounts,
383 signature,
384 slot,
385 tx_index,
386 block_time_us,
387 ),
388 event_type_filter,
389 );
390 }
391 else if *program_id == METEORA_DAMM_V2_PROGRAM_ID {
393 if event_type_filter.is_some() && !event_type_filter.unwrap().includes_meteora_damm_v2() {
394 return None;
395 }
396 return filter_parsed_event(
397 parse_meteora_damm_instruction(
398 instruction_data,
399 accounts,
400 signature,
401 slot,
402 tx_index,
403 block_time_us,
404 grpc_recv_us,
405 ),
406 event_type_filter,
407 );
408 }
409 else if *program_id == PUMP_FEES_PROGRAM_ID {
411 if event_type_filter.is_some() && !event_type_filter.unwrap().includes_pump_fees() {
412 return None;
413 }
414 return filter_parsed_event(
415 crate::instr::pump_fees::parse_instruction(
416 instruction_data,
417 accounts,
418 signature,
419 slot,
420 tx_index,
421 block_time_us,
422 grpc_recv_us,
423 ),
424 event_type_filter,
425 );
426 }
427 else if *program_id == RAYDIUM_LAUNCHLAB_PROGRAM_ID {
429 if event_type_filter.is_some() && !event_type_filter.unwrap().includes_raydium_launchlab() {
430 return None;
431 }
432 return filter_parsed_event(
433 parse_raydium_launchlab_instruction(
434 instruction_data,
435 accounts,
436 signature,
437 slot,
438 tx_index,
439 block_time_us,
440 ),
441 event_type_filter,
442 );
443 }
444 else if *program_id == RAYDIUM_CPMM_PROGRAM_ID {
446 if event_type_filter.is_some() && !event_type_filter.unwrap().includes_raydium_cpmm() {
447 return None;
448 }
449 return filter_parsed_event(
450 crate::instr::raydium_cpmm::parse_instruction(
451 instruction_data,
452 accounts,
453 signature,
454 slot,
455 tx_index,
456 block_time_us,
457 ),
458 event_type_filter,
459 );
460 }
461 else if *program_id == RAYDIUM_CLMM_PROGRAM_ID {
463 if event_type_filter.is_some() && !event_type_filter.unwrap().includes_raydium_clmm() {
464 return None;
465 }
466 return filter_parsed_event(
467 crate::instr::raydium_clmm::parse_instruction(
468 instruction_data,
469 accounts,
470 signature,
471 slot,
472 tx_index,
473 block_time_us,
474 ),
475 event_type_filter,
476 );
477 }
478 else if *program_id == RAYDIUM_AMM_V4_PROGRAM_ID {
480 if event_type_filter.is_some() && !event_type_filter.unwrap().includes_raydium_amm_v4() {
481 return None;
482 }
483 return filter_parsed_event(
484 crate::instr::raydium_amm::parse_instruction(
485 instruction_data,
486 accounts,
487 signature,
488 slot,
489 tx_index,
490 block_time_us,
491 ),
492 event_type_filter,
493 );
494 }
495 else if *program_id == ORCA_WHIRLPOOL_PROGRAM_ID {
497 if event_type_filter.is_some() && !event_type_filter.unwrap().includes_orca_whirlpool() {
498 return None;
499 }
500 return filter_parsed_event(
501 crate::instr::orca_whirlpool::parse_instruction(
502 instruction_data,
503 accounts,
504 signature,
505 slot,
506 tx_index,
507 block_time_us,
508 ),
509 event_type_filter,
510 );
511 }
512 else if *program_id == METEORA_POOLS_PROGRAM_ID {
514 if event_type_filter.is_some() && !event_type_filter.unwrap().includes_meteora_pools() {
515 return None;
516 }
517 return filter_parsed_event(
518 crate::instr::meteora_amm::parse_instruction(
519 instruction_data,
520 accounts,
521 signature,
522 slot,
523 tx_index,
524 block_time_us,
525 ),
526 event_type_filter,
527 );
528 }
529 else if *program_id == METEORA_DLMM_PROGRAM_ID {
531 if event_type_filter.is_some() && !event_type_filter.unwrap().includes_meteora_dlmm() {
532 return None;
533 }
534 return filter_parsed_event(
535 crate::instr::meteora_dlmm::parse_instruction(
536 instruction_data,
537 accounts,
538 signature,
539 slot,
540 tx_index,
541 block_time_us,
542 ),
543 event_type_filter,
544 );
545 }
546
547 None
548}
549
550#[cfg(test)]
551mod tests {
552 use super::*;
553
554 fn data8(disc: [u8; 8]) -> Vec<u8> {
555 let mut data = Vec::from(disc);
556 data.extend_from_slice(&1u64.to_le_bytes());
557 data.extend_from_slice(&2u64.to_le_bytes());
558 data
559 }
560
561 #[test]
562 fn instruction_data_gate_covers_supported_normal_instruction_protocols() {
563 assert!(instruction_data_may_parse(
564 &PUMPSWAP_PROGRAM_ID,
565 &data8(pump_amm::discriminators::CREATE_POOL)
566 ));
567 assert!(instruction_data_may_parse(
568 &PUMP_FEES_PROGRAM_ID,
569 &data8(pump_fees::UPDATE_FEE_SHARES_IX)
570 ));
571 assert!(instruction_data_may_parse(
572 &RAYDIUM_LAUNCHLAB_PROGRAM_ID,
573 &data8(raydium_launchlab::discriminators::BUY_EXACT_IN)
574 ));
575 assert!(instruction_data_may_parse(
576 &RAYDIUM_CPMM_PROGRAM_ID,
577 &data8(raydium_cpmm::discriminators::SWAP_BASE_IN)
578 ));
579 assert!(instruction_data_may_parse(
580 &RAYDIUM_CLMM_PROGRAM_ID,
581 &data8(raydium_clmm::discriminators::SWAP_V2)
582 ));
583 assert!(instruction_data_may_parse(
584 &RAYDIUM_AMM_V4_PROGRAM_ID,
585 &[raydium_amm::discriminators::SWAP_BASE_IN, 1, 0, 0, 0, 0, 0, 0, 0]
586 ));
587 assert!(instruction_data_may_parse(
588 &ORCA_WHIRLPOOL_PROGRAM_ID,
589 &data8(orca_whirlpool::discriminators::SWAP)
590 ));
591 assert!(instruction_data_may_parse(
592 &METEORA_POOLS_PROGRAM_ID,
593 &data8(meteora_amm::discriminators::CREATE_POOL)
594 ));
595 assert!(instruction_data_may_parse(
596 &METEORA_DAMM_V2_PROGRAM_ID,
597 &data8(meteora_damm::discriminators::INITIALIZE_POOL)
598 ));
599 assert!(instruction_data_may_parse(
600 &METEORA_DLMM_PROGRAM_ID,
601 &data8(meteora_dlmm::discriminators::SWAP2)
602 ));
603 assert!(!instruction_data_may_parse(&METEORA_DLMM_PROGRAM_ID, &[11, 1, 2, 3]));
604 }
605
606 #[test]
607 fn instruction_data_gate_rejects_unknown_program_and_event_cpi_layouts() {
608 assert!(!instruction_data_may_parse(&Pubkey::new_unique(), &data8([1; 8])));
609 assert!(!instruction_data_may_parse(&PUMPSWAP_PROGRAM_ID, &data8([0xff; 8])));
610 assert!(instruction_data_may_parse(
611 &PUMPFUN_PROGRAM_ID,
612 &data8(pump::discriminators::MIGRATE_BONDING_CURVE_CREATOR)
613 ));
614
615 let mut pumpswap_event_cpi = Vec::from(pump_amm_inner::discriminators::CREATE_POOL);
616 pumpswap_event_cpi.extend_from_slice(&[0; 64]);
617 assert!(!instruction_data_may_parse(&PUMPSWAP_PROGRAM_ID, &pumpswap_event_cpi));
618 }
619
620 #[test]
621 fn normal_instruction_gate_keeps_meteora_damm_event_cpi_on_event_path() {
622 let mut event_cpi = Vec::new();
623 event_cpi.extend_from_slice(&[228, 69, 165, 46, 81, 203, 154, 29]);
624 event_cpi.extend_from_slice(&meteora_damm::discriminators::SWAP_LOG);
625 event_cpi.extend_from_slice(&[0; 64]);
626
627 assert!(instruction_data_may_parse(&METEORA_DAMM_V2_PROGRAM_ID, &event_cpi));
628 assert!(!normal_instruction_data_may_parse(&METEORA_DAMM_V2_PROGRAM_ID, &event_cpi));
629 assert!(normal_instruction_data_may_parse(
630 &METEORA_DAMM_V2_PROGRAM_ID,
631 &data8(meteora_damm::discriminators::INITIALIZE_POOL)
632 ));
633 }
634}