1use crate::core::{events::*, merger::merge_events, pumpfun_fee_enrich::enrich_create_v2_observed_fee_recipient};
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 enrich_create_v2_observed_fee_recipient(&mut merged);
128
129 for e in merged.iter_mut() {
130 if let Some(m) = e.metadata_mut() {
131 m.recent_blockhash = recent_blockhash.clone();
132 }
133 }
134
135 let invokes_str: HashMap<&str, Vec<(i32, i32)>> =
137 invokes.iter().map(|(k, v)| (k.to_string().leak() as &str, v.clone())).collect();
138
139 let mut final_result = Vec::with_capacity(merged.len());
141 for mut event in merged {
142 crate::core::account_dispatcher::fill_accounts_with_owned_keys(
143 &mut event,
144 meta,
145 transaction,
146 &invokes,
147 );
148 crate::core::common_filler::fill_data(&mut event, meta, transaction, &invokes_str);
149 final_result.push(event);
150 }
151
152 final_result
153}
154
155#[inline(always)]
163fn parse_outer_instruction<'a>(
164 data: &[u8],
165 program_id: &Pubkey,
166 sig: Signature,
167 slot: u64,
168 tx_idx: u64,
169 block_us: Option<i64>,
170 grpc_us: i64,
171 account_indices: &[u8],
172 get_key: &dyn Fn(usize) -> Option<&'a Vec<u8>>,
173 filter: Option<&EventTypeFilter>,
174 _is_created_buy: bool,
175) -> Option<DexEvent> {
176 if data.len() < 8 {
178 return None;
179 }
180
181 let accounts: Vec<Pubkey> = account_indices
183 .iter()
184 .filter_map(|&idx| get_key(idx as usize).map(|k| read_pubkey_fast(k)))
185 .collect();
186
187 crate::instr::parse_instruction_unified(
189 data, &accounts, sig, slot, tx_idx, block_us, grpc_us, filter, program_id,
190 )
191}
192
193#[inline(always)]
197fn parse_inner_instruction(
198 data: &[u8],
199 program_id: &Pubkey,
200 sig: Signature,
201 slot: u64,
202 tx_idx: u64,
203 block_us: Option<i64>,
204 grpc_us: i64,
205 filter: Option<&EventTypeFilter>,
206 is_created_buy: bool,
207) -> Option<DexEvent> {
208 if data.len() < 16 {
210 return None;
211 }
212
213 let metadata = EventMetadata {
214 signature: sig,
215 slot,
216 tx_index: tx_idx,
217 block_time_us: block_us.unwrap_or(0),
218 grpc_recv_us: grpc_us,
219 recent_blockhash: None, };
221
222 let mut discriminator = [0u8; 16];
224 discriminator.copy_from_slice(&data[..16]);
225 let inner_data = &data[16..];
226
227 use crate::instr::{all_inner, program_ids, pump_amm_inner, pump_inner, raydium_clmm_inner};
228
229 if *program_id == program_ids::PUMPFUN_PROGRAM_ID {
231 if let Some(f) = filter {
232 if !f.includes_pumpfun() {
233 return None;
234 }
235 }
236 pump_inner::parse_pumpfun_inner_instruction(
237 &discriminator,
238 inner_data,
239 metadata,
240 is_created_buy,
241 )
242 } else if *program_id == program_ids::PUMPSWAP_PROGRAM_ID {
243 if let Some(f) = filter {
244 if !f.includes_pumpswap() {
245 return None;
246 }
247 }
248 pump_amm_inner::parse_pumpswap_inner_instruction(&discriminator, inner_data, metadata)
249 } else if *program_id == program_ids::RAYDIUM_CLMM_PROGRAM_ID {
250 raydium_clmm_inner::parse_raydium_clmm_inner_instruction(
251 &discriminator,
252 inner_data,
253 metadata,
254 )
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(events: Vec<(usize, Option<usize>, DexEvent)>) -> Vec<DexEvent> {
285 if events.is_empty() {
286 return Vec::new();
287 }
288
289 let mut events = events;
291 events.sort_by_key(|(outer, inner, _)| (*outer, inner.unwrap_or(usize::MAX)));
292
293 let mut result = Vec::with_capacity(events.len());
294 let mut pending_outer: Option<(usize, DexEvent)> = None;
295
296 for (outer_idx, inner_idx, event) in events {
297 match inner_idx {
298 None => {
299 if let Some((_, outer_event)) = pending_outer.take() {
302 result.push(outer_event);
303 }
304 pending_outer = Some((outer_idx, event));
306 }
307 Some(_) => {
308 if let Some((pending_outer_idx, mut outer_event)) = pending_outer.take() {
310 if pending_outer_idx == outer_idx {
311 merge_events(&mut outer_event, event);
313 result.push(outer_event);
314 } else {
315 result.push(outer_event);
317 result.push(event);
318 }
319 } else {
320 result.push(event);
322 }
323 }
324 }
325 }
326
327 if let Some((_, outer_event)) = pending_outer {
329 result.push(outer_event);
330 }
331
332 result
333}
334
335#[inline(always)]
337fn should_parse_instructions(filter: Option<&EventTypeFilter>) -> bool {
338 let Some(filter) = filter else { return true };
340
341 let Some(ref include_only) = filter.include_only else { return true };
343
344 include_only.iter().any(|t| {
346 use crate::grpc::types::EventType::*;
347 matches!(
348 t,
349 PumpFunMigrate
350 | MeteoraDammV2Swap
351 | MeteoraDammV2AddLiquidity
352 | MeteoraDammV2CreatePosition
353 | MeteoraDammV2ClosePosition
354 | MeteoraDammV2RemoveLiquidity
355 )
356 })
357}
358
359#[cfg(test)]
360mod tests {
361 use super::*;
362
363 #[test]
364 fn test_should_parse_instructions() {
365 assert!(should_parse_instructions(None));
367
368 let filter = EventTypeFilter { include_only: None, exclude_types: None };
370 assert!(should_parse_instructions(Some(&filter)));
371
372 use crate::grpc::types::EventType;
374 let filter = EventTypeFilter {
375 include_only: Some(vec![EventType::PumpFunMigrate]),
376 exclude_types: None,
377 };
378 assert!(should_parse_instructions(Some(&filter)));
379
380 let filter = EventTypeFilter {
382 include_only: Some(vec![EventType::PumpFunTrade]),
383 exclude_types: None,
384 };
385 assert!(!should_parse_instructions(Some(&filter)));
386 }
387
388 #[test]
389 fn test_merge_instruction_events() {
390 use solana_sdk::signature::Signature;
391
392 let metadata = EventMetadata {
393 signature: Signature::default(),
394 slot: 100,
395 tx_index: 1,
396 block_time_us: 1000,
397 grpc_recv_us: 2000,
398 recent_blockhash: None,
399 };
400
401 let outer_event = DexEvent::PumpFunTrade(PumpFunTradeEvent {
403 metadata: metadata.clone(),
404 bonding_curve: Pubkey::new_unique(),
405 ..Default::default()
406 });
407
408 let inner_event = DexEvent::PumpFunTrade(PumpFunTradeEvent {
409 metadata: metadata.clone(),
410 sol_amount: 1000,
411 token_amount: 2000,
412 ..Default::default()
413 });
414
415 let events = vec![
416 (0, None, outer_event), (0, Some(0), inner_event), ];
419
420 let result = merge_instruction_events(events);
421
422 assert_eq!(result.len(), 1);
424
425 if let DexEvent::PumpFunTrade(trade) = &result[0] {
427 assert_eq!(trade.sol_amount, 1000); assert_eq!(trade.token_amount, 2000); assert_ne!(trade.bonding_curve, Pubkey::default()); } else {
431 panic!("Expected PumpFunTrade event");
432 }
433 }
434}