1use smallvec::SmallVec;
5use solana_sdk::pubkey::Pubkey;
6use yellowstone_grpc_proto::prelude::{
7 SubscribeUpdateTransaction, Transaction, TransactionStatusMeta,
8};
9
10use super::transaction_meta::try_yellowstone_signature;
11use super::types::EventTypeFilter;
12use crate::DexEvent;
13
14const PROGRAM_DATA_PREFIX: &[u8] = b"Program data: ";
15
16struct ActiveProgram<'a> {
17 encoded: &'a str,
18 pubkey: Pubkey,
19}
20
21#[inline]
23pub fn parse_subscribe_update_transaction(
24 tx: &SubscribeUpdateTransaction,
25 grpc_recv_us: i64,
26 block_us: Option<i64>,
27 filter: Option<&EventTypeFilter>,
28) -> Vec<DexEvent> {
29 parse_transaction_core(tx, grpc_recv_us, block_us, filter)
30}
31
32#[inline]
33pub(crate) fn parse_transaction_core(
34 tx: &SubscribeUpdateTransaction,
35 grpc_us: i64,
36 block_us: Option<i64>,
37 filter: Option<&EventTypeFilter>,
38) -> Vec<DexEvent> {
39 let Some(info) = &tx.transaction else { return Vec::new() };
40 let Some(meta) = &info.meta else { return Vec::new() };
41
42 let Some(sig) = try_yellowstone_signature(&info.signature) else {
43 return Vec::new();
44 };
45 let slot = tx.slot;
46 let idx = info.index;
47 let needs_pumpfun = filter.map(EventTypeFilter::includes_pumpfun).unwrap_or(true);
48 let is_created_buy =
49 needs_pumpfun && crate::logs::optimized_matcher::detect_pumpfun_create(&meta.log_messages);
50
51 let (log_events, instr_events) = rayon::join(
52 || {
53 parse_logs(
54 meta,
55 &info.transaction,
56 &meta.log_messages,
57 sig,
58 slot,
59 idx,
60 block_us,
61 grpc_us,
62 filter,
63 is_created_buy,
64 )
65 },
66 || {
67 parse_instructions(
68 meta,
69 &info.transaction,
70 sig,
71 slot,
72 idx,
73 block_us,
74 grpc_us,
75 filter,
76 is_created_buy,
77 )
78 },
79 );
80
81 let mut events =
82 crate::grpc::log_instr_dedup::dedupe_log_instruction_events(log_events, instr_events);
83 crate::grpc::transaction_meta::fill_recent_blockhash(&mut events, &info.transaction);
84 for event in &mut events {
85 crate::core::common_filler::fill_token_balances(event, meta, &info.transaction);
86 }
87 if let Some(filter) = filter {
88 events.into_iter().map(|e| filter.normalize_dex_event(e)).collect()
89 } else {
90 events
91 }
92}
93
94#[inline]
99pub fn parse_subscribe_update_transaction_low_latency(
100 tx: &SubscribeUpdateTransaction,
101 grpc_recv_us: i64,
102 block_us: Option<i64>,
103 filter: Option<&EventTypeFilter>,
104) -> Vec<DexEvent> {
105 parse_transaction_core_sequential(tx, grpc_recv_us, block_us, filter)
106}
107
108#[inline]
109fn parse_transaction_core_sequential(
110 tx: &SubscribeUpdateTransaction,
111 grpc_us: i64,
112 block_us: Option<i64>,
113 filter: Option<&EventTypeFilter>,
114) -> Vec<DexEvent> {
115 let Some(info) = &tx.transaction else {
116 return Vec::new();
117 };
118 let Some(meta) = &info.meta else {
119 return Vec::new();
120 };
121
122 let Some(sig) = try_yellowstone_signature(&info.signature) else {
123 return Vec::new();
124 };
125 let slot = tx.slot;
126 let idx = info.index;
127 let needs_pumpfun = filter.map(EventTypeFilter::includes_pumpfun).unwrap_or(true);
128 let is_created_buy =
129 needs_pumpfun && crate::logs::optimized_matcher::detect_pumpfun_create(&meta.log_messages);
130
131 let log_events = parse_logs(
132 meta,
133 &info.transaction,
134 &meta.log_messages,
135 sig,
136 slot,
137 idx,
138 block_us,
139 grpc_us,
140 filter,
141 is_created_buy,
142 );
143 let instr_events = parse_instructions(
144 meta,
145 &info.transaction,
146 sig,
147 slot,
148 idx,
149 block_us,
150 grpc_us,
151 filter,
152 is_created_buy,
153 );
154
155 let mut events =
156 crate::grpc::log_instr_dedup::dedupe_log_instruction_events(log_events, instr_events);
157 crate::grpc::transaction_meta::fill_recent_blockhash(&mut events, &info.transaction);
158 for event in &mut events {
159 crate::core::common_filler::fill_token_balances(event, meta, &info.transaction);
160 }
161 if let Some(filter) = filter {
162 events.into_iter().map(|e| filter.normalize_dex_event(e)).collect()
163 } else {
164 events
165 }
166}
167
168#[inline]
169fn parse_logs(
170 meta: &TransactionStatusMeta,
171 transaction: &Option<Transaction>,
172 logs: &[String],
173 sig: solana_sdk::signature::Signature,
174 slot: u64,
175 tx_idx: u64,
176 block_us: Option<i64>,
177 grpc_us: i64,
178 filter: Option<&EventTypeFilter>,
179 is_created_buy: bool,
180) -> Vec<DexEvent> {
181 let mut outer_idx: i32 = -1;
182 let mut inner_idx: i32 = -1;
183 let mut invokes = crate::core::invoke_context::InvokeContext::default();
184 let mut active_program_stack: SmallVec<[ActiveProgram<'_>; 8]> = SmallVec::new();
185 let mut result = Vec::with_capacity(4);
186
187 for log in logs {
188 if log.as_bytes().starts_with(PROGRAM_DATA_PREFIX) {
189 let current_program = active_program_stack.last().map(|active| &active.pubkey);
190 if let Some(mut e) = crate::logs::parse_log_with_program_id(
191 log,
192 sig,
193 slot,
194 tx_idx,
195 block_us,
196 grpc_us,
197 filter,
198 is_created_buy,
199 None,
200 current_program,
201 ) {
202 crate::core::account_dispatcher::fill_accounts_with_invoke_context(
203 &mut e,
204 meta,
205 transaction,
206 &invokes,
207 );
208 crate::core::common_filler::fill_data_with_invoke_context(
209 &mut e,
210 meta,
211 transaction,
212 &invokes,
213 );
214 result.push(e);
215 }
216 continue;
217 }
218
219 if let Some((pid, depth)) = crate::logs::optimized_matcher::parse_invoke_info(log) {
220 if depth == 1 {
221 inner_idx = -1;
222 outer_idx += 1;
223 } else {
224 inner_idx += 1;
225 }
226 let pk = crate::grpc::program_ids::known_program_id(pid).unwrap_or_default();
227 active_program_stack.truncate(depth - 1);
228 active_program_stack.push(ActiveProgram { encoded: pid, pubkey: pk });
229 if crate::grpc::program_ids::needs_invoke_context(&pk) {
230 invokes.push(pk, (outer_idx, inner_idx));
231 }
232 continue;
233 }
234
235 if let Some(pid) = crate::logs::optimized_matcher::parse_program_complete_info(log) {
236 if let Some(pos) = active_program_stack.iter().rposition(|active| active.encoded == pid)
237 {
238 active_program_stack.truncate(pos);
239 }
240 }
241 }
242 result
243}
244
245#[inline]
246fn parse_instructions(
247 meta: &TransactionStatusMeta,
248 transaction: &Option<Transaction>,
249 sig: solana_sdk::signature::Signature,
250 slot: u64,
251 tx_idx: u64,
252 block_us: Option<i64>,
253 grpc_us: i64,
254 filter: Option<&EventTypeFilter>,
255 is_created_buy: bool,
256) -> Vec<DexEvent> {
257 crate::grpc::instruction_parser::parse_instructions_enhanced_with_created_buy(
258 meta,
259 transaction,
260 sig,
261 slot,
262 tx_idx,
263 block_us,
264 grpc_us,
265 filter,
266 is_created_buy,
267 )
268}