1use crate::core::{
9 events::*, merger::merge_events, pumpfun_fee_enrich::enrich_pumpfun_same_tx_post_merge,
10};
11use crate::grpc::types::EventTypeFilter;
12use crate::instr::read_pubkey_fast;
13use solana_sdk::pubkey::Pubkey;
14use solana_sdk::signature::Signature;
15use std::collections::HashMap;
16use yellowstone_grpc_proto::prelude::{Transaction, TransactionStatusMeta};
17
18#[inline]
32pub fn parse_instructions_enhanced(
33 meta: &TransactionStatusMeta,
34 transaction: &Option<Transaction>,
35 sig: Signature,
36 slot: u64,
37 tx_idx: u64,
38 block_us: Option<i64>,
39 grpc_us: i64,
40 filter: Option<&EventTypeFilter>,
41) -> Vec<DexEvent> {
42 let Some(tx) = transaction else { return Vec::new() };
43 let Some(msg) = &tx.message else { return Vec::new() };
44
45 let recent_blockhash = if msg.recent_blockhash.is_empty() {
46 None
47 } else {
48 Some(bs58::encode(&msg.recent_blockhash).into_string())
49 };
50
51 if !should_parse_instructions(filter) {
53 return Vec::new();
54 }
55
56 let is_created_buy = crate::logs::optimized_matcher::detect_pumpfun_create(&meta.log_messages);
58
59 let keys_len = msg.account_keys.len();
61 let writable_len = meta.loaded_writable_addresses.len();
62 let get_key = |i: usize| -> Option<&Vec<u8>> {
63 if i < keys_len {
64 msg.account_keys.get(i)
65 } else if i < keys_len + writable_len {
66 meta.loaded_writable_addresses.get(i - keys_len)
67 } else {
68 meta.loaded_readonly_addresses.get(i - keys_len - writable_len)
69 }
70 };
71
72 let mut result = Vec::with_capacity(8);
73 let mut invokes: HashMap<Pubkey, Vec<(i32, i32)>> = HashMap::with_capacity(8);
74
75 for (i, ix) in msg.instructions.iter().enumerate() {
77 let pid = get_key(ix.program_id_index as usize)
78 .map_or(Pubkey::default(), |k| read_pubkey_fast(k));
79
80 invokes.entry(pid).or_default().push((i as i32, -1));
81
82 if let Some(event) = parse_outer_instruction(
84 &ix.data,
85 &pid,
86 sig,
87 slot,
88 tx_idx,
89 block_us,
90 grpc_us,
91 &ix.accounts,
92 &get_key,
93 filter,
94 is_created_buy,
95 ) {
96 result.push((i, None, event)); }
98 }
99
100 for inner in &meta.inner_instructions {
102 let outer_idx = inner.index as usize;
103
104 for (j, inner_ix) in inner.instructions.iter().enumerate() {
105 let pid = get_key(inner_ix.program_id_index as usize)
106 .map_or(Pubkey::default(), |k| read_pubkey_fast(k));
107
108 invokes.entry(pid).or_default().push((outer_idx as i32, j as i32));
109
110 if let Some(event) = parse_inner_instruction(
112 &inner_ix.data,
113 &pid,
114 sig,
115 slot,
116 tx_idx,
117 block_us,
118 grpc_us,
119 filter,
120 is_created_buy,
121 ) {
122 result.push((outer_idx, Some(j), event)); }
124 }
125 }
126
127 let mut merged = merge_instruction_events(result);
129 enrich_pumpfun_same_tx_post_merge(&mut merged);
130
131 for e in merged.iter_mut() {
132 if let Some(m) = e.metadata_mut() {
133 m.recent_blockhash = recent_blockhash.clone();
134 }
135 }
136
137 let mut final_result = Vec::with_capacity(merged.len());
139 for mut event in merged {
140 crate::core::account_dispatcher::fill_accounts_with_owned_keys(
141 &mut event,
142 meta,
143 transaction,
144 &invokes,
145 );
146 crate::core::common_filler::fill_data(&mut event, meta, transaction, &invokes);
147 final_result.push(event);
148 }
149
150 final_result
151}
152
153#[inline(always)]
161fn parse_outer_instruction<'a>(
162 data: &[u8],
163 program_id: &Pubkey,
164 sig: Signature,
165 slot: u64,
166 tx_idx: u64,
167 block_us: Option<i64>,
168 grpc_us: i64,
169 account_indices: &[u8],
170 get_key: &dyn Fn(usize) -> Option<&'a Vec<u8>>,
171 filter: Option<&EventTypeFilter>,
172 _is_created_buy: bool,
173) -> Option<DexEvent> {
174 if data.len() < 8 {
176 return None;
177 }
178
179 const STACK_CAP: usize = 64;
181 if account_indices.len() <= STACK_CAP {
182 let mut stack = [Pubkey::default(); STACK_CAP];
183 let mut n = 0usize;
184 for &idx in account_indices {
185 if let Some(k) = get_key(idx as usize) {
186 stack[n] = read_pubkey_fast(k);
187 n += 1;
188 }
189 }
190 crate::instr::parse_instruction_unified(
191 data,
192 &stack[..n],
193 sig,
194 slot,
195 tx_idx,
196 block_us,
197 grpc_us,
198 filter,
199 program_id,
200 )
201 } else {
202 let accounts: Vec<Pubkey> = account_indices
203 .iter()
204 .filter_map(|&idx| get_key(idx as usize).map(|k| read_pubkey_fast(k)))
205 .collect();
206 crate::instr::parse_instruction_unified(
207 data, &accounts, sig, slot, tx_idx, block_us, grpc_us, filter, program_id,
208 )
209 }
210}
211
212#[inline(always)]
216fn parse_inner_instruction(
217 data: &[u8],
218 program_id: &Pubkey,
219 sig: Signature,
220 slot: u64,
221 tx_idx: u64,
222 block_us: Option<i64>,
223 grpc_us: i64,
224 filter: Option<&EventTypeFilter>,
225 is_created_buy: bool,
226) -> Option<DexEvent> {
227 if data.len() < 16 {
229 return None;
230 }
231
232 let metadata = EventMetadata {
233 signature: sig,
234 slot,
235 tx_index: tx_idx,
236 block_time_us: block_us.unwrap_or(0),
237 grpc_recv_us: grpc_us,
238 recent_blockhash: None, };
240
241 let mut discriminator = [0u8; 16];
243 discriminator.copy_from_slice(&data[..16]);
244 let inner_data = &data[16..];
245
246 use crate::instr::{all_inner, program_ids, pump_amm_inner, pump_inner, raydium_clmm_inner};
247
248 if *program_id == program_ids::PUMPFUN_PROGRAM_ID {
250 if let Some(f) = filter {
251 if !f.includes_pumpfun() {
252 return None;
253 }
254 }
255 pump_inner::parse_pumpfun_inner_instruction(
256 &discriminator,
257 inner_data,
258 metadata,
259 is_created_buy,
260 )
261 } else if *program_id == program_ids::PUMPSWAP_PROGRAM_ID {
262 if let Some(f) = filter {
263 if !f.includes_pumpswap() {
264 return None;
265 }
266 }
267 pump_amm_inner::parse_pumpswap_inner_instruction(&discriminator, inner_data, metadata)
268 } else if *program_id == program_ids::RAYDIUM_CLMM_PROGRAM_ID {
269 raydium_clmm_inner::parse_raydium_clmm_inner_instruction(
270 &discriminator,
271 inner_data,
272 metadata,
273 )
274 } else if *program_id == program_ids::RAYDIUM_CPMM_PROGRAM_ID {
275 all_inner::raydium_cpmm::parse(&discriminator, inner_data, metadata)
276 } else if *program_id == program_ids::RAYDIUM_AMM_V4_PROGRAM_ID {
277 all_inner::raydium_amm::parse(&discriminator, inner_data, metadata)
278 } else if *program_id == program_ids::ORCA_WHIRLPOOL_PROGRAM_ID {
279 all_inner::orca::parse(&discriminator, inner_data, metadata)
280 } else if *program_id == program_ids::METEORA_POOLS_PROGRAM_ID {
281 all_inner::meteora_amm::parse(&discriminator, inner_data, metadata)
282 } else if *program_id == program_ids::METEORA_DAMM_V2_PROGRAM_ID {
283 if let Some(f) = filter {
284 if !f.includes_meteora_damm_v2() {
285 return None;
286 }
287 }
288 all_inner::meteora_damm::parse(&discriminator, inner_data, metadata)
289 } else if *program_id == program_ids::BONK_PROGRAM_ID {
290 all_inner::bonk::parse(&discriminator, inner_data, metadata)
291 } else {
292 None
293 }
294}
295
296#[inline(always)]
304fn merge_instruction_events(events: Vec<(usize, Option<usize>, DexEvent)>) -> Vec<DexEvent> {
305 if events.is_empty() {
306 return Vec::new();
307 }
308
309 let mut events = events;
312 events.sort_by_key(|(outer, inner, _)| (*outer, inner.map_or(0, |i| i + 1)));
313
314 let mut result = Vec::with_capacity(events.len());
315 let mut pending_outer: Option<(usize, DexEvent)> = None;
316
317 for (outer_idx, inner_idx, event) in events {
318 match inner_idx {
319 None => {
320 if let Some((_, outer_event)) = pending_outer.take() {
323 result.push(outer_event);
324 }
325 pending_outer = Some((outer_idx, event));
327 }
328 Some(_) => {
329 if let Some((pending_outer_idx, mut outer_event)) = pending_outer.take() {
331 if pending_outer_idx == outer_idx {
332 merge_events(&mut outer_event, event);
334 pending_outer = Some((outer_idx, outer_event));
335 } else {
336 result.push(outer_event);
338 result.push(event);
339 }
340 } else {
341 result.push(event);
343 }
344 }
345 }
346 }
347
348 if let Some((_, outer_event)) = pending_outer {
350 result.push(outer_event);
351 }
352
353 result
354}
355
356#[inline(always)]
358fn should_parse_instructions(filter: Option<&EventTypeFilter>) -> bool {
359 let Some(filter) = filter else { return true };
361
362 let Some(ref include_only) = filter.include_only else { return true };
364
365 if filter.includes_pumpfun() {
369 return true;
370 }
371
372 if filter.includes_pump_fees() {
373 return true;
374 }
375
376 include_only.iter().any(|t| {
378 use crate::grpc::types::EventType::*;
379 matches!(
380 t,
381 PumpFunMigrate
382 | MeteoraDammV2Swap
383 | MeteoraDammV2AddLiquidity
384 | MeteoraDammV2CreatePosition
385 | MeteoraDammV2ClosePosition
386 | MeteoraDammV2RemoveLiquidity
387 )
388 })
389}
390
391#[cfg(test)]
392mod tests {
393 use super::*;
394
395 #[test]
396 fn test_should_parse_instructions() {
397 assert!(should_parse_instructions(None));
399
400 let filter = EventTypeFilter { include_only: None, exclude_types: None };
402 assert!(should_parse_instructions(Some(&filter)));
403
404 use crate::grpc::types::EventType;
406 let filter = EventTypeFilter {
407 include_only: Some(vec![EventType::PumpFunMigrate]),
408 exclude_types: None,
409 };
410 assert!(should_parse_instructions(Some(&filter)));
411
412 let filter = EventTypeFilter {
414 include_only: Some(vec![EventType::PumpFunTrade]),
415 exclude_types: None,
416 };
417 assert!(should_parse_instructions(Some(&filter)));
418 }
419
420 #[test]
421 fn test_merge_instruction_events() {
422 use solana_sdk::signature::Signature;
423
424 let metadata = EventMetadata {
425 signature: Signature::default(),
426 slot: 100,
427 tx_index: 1,
428 block_time_us: 1000,
429 grpc_recv_us: 2000,
430 recent_blockhash: None,
431 };
432
433 let outer_event = DexEvent::PumpFunTrade(PumpFunTradeEvent {
435 metadata: metadata.clone(),
436 bonding_curve: Pubkey::new_unique(),
437 ..Default::default()
438 });
439
440 let inner_event = DexEvent::PumpFunTrade(PumpFunTradeEvent {
441 metadata: metadata.clone(),
442 sol_amount: 1000,
443 token_amount: 2000,
444 ..Default::default()
445 });
446
447 let events = vec![
448 (0, None, outer_event), (0, Some(0), inner_event), ];
451
452 let result = merge_instruction_events(events);
453
454 assert_eq!(result.len(), 1);
456
457 if let DexEvent::PumpFunTrade(trade) = &result[0] {
459 assert_eq!(trade.sol_amount, 1000); assert_eq!(trade.token_amount, 2000); assert_ne!(trade.bonding_curve, Pubkey::default()); } else {
463 panic!("Expected PumpFunTrade event");
464 }
465 }
466
467 #[test]
468 fn test_merge_instruction_events_chains_multiple_inners_same_outer() {
469 use solana_sdk::signature::Signature;
470
471 let metadata = EventMetadata {
472 signature: Signature::default(),
473 slot: 100,
474 tx_index: 1,
475 block_time_us: 1000,
476 grpc_recv_us: 2000,
477 recent_blockhash: None,
478 };
479
480 let bc = Pubkey::new_unique();
481 let fee = Pubkey::new_unique();
482
483 let outer_event = DexEvent::PumpFunTrade(PumpFunTradeEvent {
484 metadata: metadata.clone(),
485 bonding_curve: bc,
486 ..Default::default()
487 });
488
489 let inner_trade = DexEvent::PumpFunTrade(PumpFunTradeEvent {
490 metadata: metadata.clone(),
491 sol_amount: 1000,
492 token_amount: 2000,
493 is_buy: true,
494 ..Default::default()
495 });
496
497 let inner_fee_only = DexEvent::PumpFunTrade(PumpFunTradeEvent {
499 metadata: metadata.clone(),
500 fee_recipient: fee,
501 ..Default::default()
502 });
503
504 let events =
505 vec![(0, None, outer_event), (0, Some(0), inner_trade), (0, Some(1), inner_fee_only)];
506
507 let result = merge_instruction_events(events);
508 assert_eq!(result.len(), 1);
509 if let DexEvent::PumpFunTrade(trade) = &result[0] {
510 assert_eq!(trade.bonding_curve, bc);
511 assert_eq!(trade.sol_amount, 1000);
512 assert_eq!(trade.token_amount, 2000);
513 assert_eq!(trade.fee_recipient, fee);
514 } else {
515 panic!("Expected PumpFunTrade event");
516 }
517 }
518}