sol_parser_sdk/
wrapped_pumpfun_parser.rs1use crate::core::events::{DexEvent, RawInstructionEvent};
4use serde::{Deserialize, Serialize};
5use solana_sdk::pubkey::Pubkey;
6
7#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
8pub struct ThirdProgram {
9 pub program_id: Pubkey,
10 pub instruction_index: usize,
11 pub rule_id: String,
12}
13
14#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
17pub struct WrappedPumpfunRule {
18 pub rule_id: String,
19 pub program_id: Pubkey,
20 pub outer_account_count: usize,
21 pub data_pattern: Vec<Option<u8>>,
23 pub pump_discriminator: [u8; 8],
24 pub argument_offsets: [usize; 2],
26 pub account_map: Vec<usize>,
28 pub fixed_accounts: Vec<(usize, Pubkey)>,
29 pub equal_accounts: Vec<(usize, usize)>,
32}
33
34pub fn pump_trade_name(disc: &[u8]) -> Option<&'static str> {
37 use crate::instr::pump::discriminators::*;
38 match disc {
39 d if d == BUY_V2 => Some("buy_v2"),
40 d if d == BUY_EXACT_QUOTE_IN_V2 => Some("buy_exact_quote_in_v2"),
41 d if d == SELL_V2 => Some("sell_v2"),
42 _ => None,
43 }
44}
45
46pub fn restore_instruction(
49 raw: &RawInstructionEvent,
50 rule: &WrappedPumpfunRule,
51) -> Option<([u8; 24], Vec<Pubkey>)> {
52 if raw.program_id != rule.program_id
53 || raw.accounts.len() != rule.outer_account_count
54 || raw.instruction_data.len() != rule.data_pattern.len()
55 || rule.data_pattern.is_empty()
56 || rule.data_pattern.iter().all(Option::is_none)
57 || pump_trade_name(&rule.pump_discriminator).is_none()
58 || raw
59 .instruction_data
60 .iter()
61 .zip(&rule.data_pattern)
62 .any(|(actual, expected)| expected.is_some_and(|v| v != *actual))
63 {
64 return None;
65 }
66 if raw.accounts.iter().any(Option::is_none) {
67 return None;
68 }
69 let accounts = &raw.accounts;
70 for &(index, key) in &rule.fixed_accounts {
71 if accounts.get(index) != Some(&Some(key)) {
72 return None;
73 }
74 }
75 for &(a, b) in &rule.equal_accounts {
76 if accounts.get(a).is_none() || accounts.get(a) != accounts.get(b) {
77 return None;
78 }
79 }
80 let mut data = [0u8; 24];
81 data[..8].copy_from_slice(&rule.pump_discriminator);
82 for (i, offset) in rule.argument_offsets.iter().copied().enumerate() {
83 data[8 + i * 8..16 + i * 8]
84 .copy_from_slice(raw.instruction_data.get(offset..offset.checked_add(8)?)?);
85 }
86 let mapped = rule
87 .account_map
88 .iter()
89 .map(|i| accounts.get(*i).copied().flatten())
90 .collect::<Option<Vec<_>>>()?;
91 Some((data, mapped))
92}
93
94pub fn parse_wrapped_pumpfun_instruction(
98 raw: &RawInstructionEvent,
99 rule: &WrappedPumpfunRule,
100 filter: Option<&crate::grpc::EventTypeFilter>,
101) -> Option<DexEvent> {
102 if filter.is_some_and(|f| !f.includes_pumpfun()) {
103 return None;
104 }
105 let (data, accounts) = restore_instruction(raw, rule)?;
106 let m = &raw.metadata;
107 let event = crate::instr::pump::parse_instruction(
108 &data,
109 &accounts,
110 m.signature,
111 m.slot,
112 m.tx_index,
113 Some(m.block_time_us),
114 m.grpc_recv_us,
115 )?;
116 let mut trade = match event {
117 DexEvent::PumpFunTrade(t)
118 | DexEvent::PumpFunBuy(t)
119 | DexEvent::PumpFunSell(t)
120 | DexEvent::PumpFunBuyExactSolIn(t) => t,
121 _ => return None,
122 };
123 trade.metadata = m.clone();
124 trade.sol_amount = 0;
125 trade.quote_amount = 0;
126 trade.token_amount = 0;
127 trade.third_program = Some(ThirdProgram {
128 program_id: raw.program_id,
129 instruction_index: raw.instruction_index,
130 rule_id: rule.rule_id.clone(),
131 });
132 let event = DexEvent::PumpFunTrade(trade);
133 filter.is_none_or(|f| f.should_include_dex_event(&event)).then_some(event)
134}