1use crate::core::{events::*, merger::merge_events};
9use crate::grpc::types::EventTypeFilter;
10use crate::instr::read_pubkey_fast;
11use solana_sdk::pubkey::Pubkey;
12use solana_sdk::signature::Signature;
13use std::collections::HashMap;
14use yellowstone_grpc_proto::prelude::{Transaction, TransactionStatusMeta};
15
16#[inline]
30pub fn parse_instructions_enhanced(
31 meta: &TransactionStatusMeta,
32 transaction: &Option<Transaction>,
33 sig: Signature,
34 slot: u64,
35 tx_idx: u64,
36 block_us: Option<i64>,
37 grpc_us: i64,
38 filter: Option<&EventTypeFilter>,
39) -> Vec<DexEvent> {
40 let Some(tx) = transaction else { return Vec::new() };
41 let Some(msg) = &tx.message else { return Vec::new() };
42
43 let recent_blockhash = if msg.recent_blockhash.is_empty() {
44 None
45 } else {
46 Some(bs58::encode(&msg.recent_blockhash).into_string())
47 };
48
49 if !should_parse_instructions(filter) {
51 return Vec::new();
52 }
53
54 let is_created_buy = crate::logs::optimized_matcher::detect_pumpfun_create(&meta.log_messages);
56
57 let keys_len = msg.account_keys.len();
59 let writable_len = meta.loaded_writable_addresses.len();
60 let get_key = |i: usize| -> Option<&Vec<u8>> {
61 if i < keys_len {
62 msg.account_keys.get(i)
63 } else if i < keys_len + writable_len {
64 meta.loaded_writable_addresses.get(i - keys_len)
65 } else {
66 meta.loaded_readonly_addresses.get(i - keys_len - writable_len)
67 }
68 };
69
70 let mut result = Vec::with_capacity(8);
71 let mut invokes: HashMap<Pubkey, Vec<(i32, i32)>> = HashMap::with_capacity(8);
72
73 for (i, ix) in msg.instructions.iter().enumerate() {
75 let pid = get_key(ix.program_id_index as usize)
76 .map_or(Pubkey::default(), |k| read_pubkey_fast(k));
77
78 invokes.entry(pid).or_default().push((i as i32, -1));
79
80 if let Some(event) = parse_outer_instruction(
82 &ix.data,
83 &pid,
84 sig,
85 slot,
86 tx_idx,
87 block_us,
88 grpc_us,
89 &ix.accounts,
90 &get_key,
91 filter,
92 is_created_buy,
93 ) {
94 result.push((i, None, event)); }
96 }
97
98 for inner in &meta.inner_instructions {
100 let outer_idx = inner.index as usize;
101
102 for (j, inner_ix) in inner.instructions.iter().enumerate() {
103 let pid = get_key(inner_ix.program_id_index as usize)
104 .map_or(Pubkey::default(), |k| read_pubkey_fast(k));
105
106 invokes.entry(pid).or_default().push((outer_idx as i32, j as i32));
107
108 if let Some(event) = parse_inner_instruction(
110 &inner_ix.data,
111 &pid,
112 sig,
113 slot,
114 tx_idx,
115 block_us,
116 grpc_us,
117 filter,
118 is_created_buy,
119 ) {
120 result.push((outer_idx, Some(j), event)); }
122 }
123 }
124
125 let mut merged = merge_instruction_events(result);
127
128 for e in merged.iter_mut() {
129 if let Some(m) = e.metadata_mut() {
130 m.recent_blockhash = recent_blockhash.clone();
131 }
132 }
133
134 let invokes_str: HashMap<&str, Vec<(i32, i32)>> = invokes
136 .iter()
137 .map(|(k, v)| (k.to_string().leak() as &str, v.clone()))
138 .collect();
139
140 let mut final_result = Vec::with_capacity(merged.len());
142 for mut event in merged {
143 crate::core::account_dispatcher::fill_accounts_with_owned_keys(
144 &mut event,
145 meta,
146 transaction,
147 &invokes,
148 );
149 crate::core::common_filler::fill_data(&mut event, meta, transaction, &invokes_str);
150 final_result.push(event);
151 }
152
153 final_result
154}
155
156#[inline(always)]
164fn parse_outer_instruction<'a>(
165 data: &[u8],
166 program_id: &Pubkey,
167 sig: Signature,
168 slot: u64,
169 tx_idx: u64,
170 block_us: Option<i64>,
171 grpc_us: i64,
172 account_indices: &[u8],
173 get_key: &dyn Fn(usize) -> Option<&'a Vec<u8>>,
174 filter: Option<&EventTypeFilter>,
175 _is_created_buy: bool,
176) -> Option<DexEvent> {
177 if data.len() < 8 {
179 return None;
180 }
181
182 let accounts: Vec<Pubkey> = account_indices
184 .iter()
185 .filter_map(|&idx| get_key(idx as usize).map(|k| read_pubkey_fast(k)))
186 .collect();
187
188 crate::instr::parse_instruction_unified(
190 data,
191 &accounts,
192 sig,
193 slot,
194 tx_idx,
195 block_us,
196 grpc_us,
197 filter,
198 program_id,
199 )
200}
201
202#[inline(always)]
206fn parse_inner_instruction(
207 data: &[u8],
208 program_id: &Pubkey,
209 sig: Signature,
210 slot: u64,
211 tx_idx: u64,
212 block_us: Option<i64>,
213 grpc_us: i64,
214 filter: Option<&EventTypeFilter>,
215 is_created_buy: bool,
216) -> Option<DexEvent> {
217 if data.len() < 16 {
219 return None;
220 }
221
222 let metadata = EventMetadata {
223 signature: sig,
224 slot,
225 tx_index: tx_idx,
226 block_time_us: block_us.unwrap_or(0),
227 grpc_recv_us: grpc_us,
228 recent_blockhash: None, };
230
231 let mut discriminator = [0u8; 16];
233 discriminator.copy_from_slice(&data[..16]);
234 let inner_data = &data[16..];
235
236 use crate::instr::{all_inner, program_ids, pump_amm_inner, pump_inner, raydium_clmm_inner};
237
238 if *program_id == program_ids::PUMPFUN_PROGRAM_ID {
240 if let Some(f) = filter {
241 if !f.includes_pumpfun() {
242 return None;
243 }
244 }
245 pump_inner::parse_pumpfun_inner_instruction(&discriminator, inner_data, metadata, is_created_buy)
246 } else if *program_id == program_ids::PUMPSWAP_PROGRAM_ID {
247 if let Some(f) = filter {
248 if !f.includes_pumpswap() {
249 return None;
250 }
251 }
252 pump_amm_inner::parse_pumpswap_inner_instruction(&discriminator, inner_data, metadata)
253 } else if *program_id == program_ids::RAYDIUM_CLMM_PROGRAM_ID {
254 raydium_clmm_inner::parse_raydium_clmm_inner_instruction(&discriminator, inner_data, metadata)
255 } else if *program_id == program_ids::RAYDIUM_CPMM_PROGRAM_ID {
256 all_inner::raydium_cpmm::parse(&discriminator, inner_data, metadata)
257 } else if *program_id == program_ids::RAYDIUM_AMM_V4_PROGRAM_ID {
258 all_inner::raydium_amm::parse(&discriminator, inner_data, metadata)
259 } else if *program_id == program_ids::ORCA_WHIRLPOOL_PROGRAM_ID {
260 all_inner::orca::parse(&discriminator, inner_data, metadata)
261 } else if *program_id == program_ids::METEORA_POOLS_PROGRAM_ID {
262 all_inner::meteora_amm::parse(&discriminator, inner_data, metadata)
263 } else if *program_id == program_ids::METEORA_DAMM_V2_PROGRAM_ID {
264 if let Some(f) = filter {
265 if !f.includes_meteora_damm_v2() {
266 return None;
267 }
268 }
269 all_inner::meteora_damm::parse(&discriminator, inner_data, metadata)
270 } else if *program_id == program_ids::BONK_PROGRAM_ID {
271 all_inner::bonk::parse(&discriminator, inner_data, metadata)
272 } else {
273 None
274 }
275}
276
277#[inline]
284fn merge_instruction_events(
285 events: Vec<(usize, Option<usize>, DexEvent)>,
286) -> Vec<DexEvent> {
287 if events.is_empty() {
288 return Vec::new();
289 }
290
291 let mut events = events;
293 events.sort_by_key(|(outer, inner, _)| (*outer, inner.unwrap_or(usize::MAX)));
294
295 let mut result = Vec::with_capacity(events.len());
296 let mut pending_outer: Option<(usize, DexEvent)> = None;
297
298 for (outer_idx, inner_idx, event) in events {
299 match inner_idx {
300 None => {
301 if let Some((_, outer_event)) = pending_outer.take() {
304 result.push(outer_event);
305 }
306 pending_outer = Some((outer_idx, event));
308 }
309 Some(_) => {
310 if let Some((pending_outer_idx, mut outer_event)) = pending_outer.take() {
312 if pending_outer_idx == outer_idx {
313 merge_events(&mut outer_event, event);
315 result.push(outer_event);
316 } else {
317 result.push(outer_event);
319 result.push(event);
320 }
321 } else {
322 result.push(event);
324 }
325 }
326 }
327 }
328
329 if let Some((_, outer_event)) = pending_outer {
331 result.push(outer_event);
332 }
333
334 result
335}
336
337#[inline(always)]
339fn should_parse_instructions(filter: Option<&EventTypeFilter>) -> bool {
340 let Some(filter) = filter else { return true };
342
343 let Some(ref include_only) = filter.include_only else { return true };
345
346 include_only.iter().any(|t| {
348 use crate::grpc::types::EventType::*;
349 matches!(
350 t,
351 PumpFunMigrate | MeteoraDammV2Swap | MeteoraDammV2AddLiquidity
352 | MeteoraDammV2CreatePosition | MeteoraDammV2ClosePosition
353 | MeteoraDammV2RemoveLiquidity
354 )
355 })
356}
357
358#[cfg(test)]
359mod tests {
360 use super::*;
361
362 #[test]
363 fn test_should_parse_instructions() {
364 assert!(should_parse_instructions(None));
366
367 let filter = EventTypeFilter { include_only: None, exclude_types: None };
369 assert!(should_parse_instructions(Some(&filter)));
370
371 use crate::grpc::types::EventType;
373 let filter = EventTypeFilter {
374 include_only: Some(vec![EventType::PumpFunMigrate]),
375 exclude_types: None,
376 };
377 assert!(should_parse_instructions(Some(&filter)));
378
379 let filter = EventTypeFilter {
381 include_only: Some(vec![EventType::PumpFunTrade]),
382 exclude_types: None,
383 };
384 assert!(!should_parse_instructions(Some(&filter)));
385 }
386
387 #[test]
388 fn test_merge_instruction_events() {
389 use solana_sdk::signature::Signature;
390
391 let metadata = EventMetadata {
392 signature: Signature::default(),
393 slot: 100,
394 tx_index: 1,
395 block_time_us: 1000,
396 grpc_recv_us: 2000,
397 recent_blockhash: None,
398 };
399
400 let outer_event = DexEvent::PumpFunTrade(PumpFunTradeEvent {
402 metadata: metadata.clone(),
403 bonding_curve: Pubkey::new_unique(),
404 ..Default::default()
405 });
406
407 let inner_event = DexEvent::PumpFunTrade(PumpFunTradeEvent {
408 metadata: metadata.clone(),
409 sol_amount: 1000,
410 token_amount: 2000,
411 ..Default::default()
412 });
413
414 let events = vec![
415 (0, None, outer_event), (0, Some(0), inner_event), ];
418
419 let result = merge_instruction_events(events);
420
421 assert_eq!(result.len(), 1);
423
424 if let DexEvent::PumpFunTrade(trade) = &result[0] {
426 assert_eq!(trade.sol_amount, 1000); assert_eq!(trade.token_amount, 2000); assert_ne!(trade.bonding_curve, Pubkey::default()); } else {
430 panic!("Expected PumpFunTrade event");
431 }
432 }
433}