sol_parser_sdk/instr/
utils.rs1use crate::core::events::EventMetadata;
4use solana_sdk::{pubkey::Pubkey, signature::Signature};
5use yellowstone_grpc_proto::prelude::{Transaction, TransactionStatusMeta};
6
7pub fn create_metadata(
9 signature: Signature,
10 slot: u64,
11 tx_index: u64,
12 block_time_us: i64,
13 grpc_recv_us: i64,
14) -> EventMetadata {
15 EventMetadata { signature, slot, tx_index, block_time_us, grpc_recv_us, recent_blockhash: None }
16}
17
18#[inline(always)]
20pub fn create_metadata_simple(
21 signature: Signature,
22 slot: u64,
23 tx_index: u64,
24 block_time_us: Option<i64>,
25 _program_id: Pubkey,
26) -> EventMetadata {
27 let current_time = now_us();
28
29 EventMetadata {
30 signature,
31 slot,
32 tx_index,
33 block_time_us: block_time_us.unwrap_or(0),
34 grpc_recv_us: current_time,
35 recent_blockhash: None,
36 }
37}
38
39#[inline(always)]
41pub fn read_u64_le(data: &[u8], offset: usize) -> Option<u64> {
42 data.get(offset..offset + 8).map(|slice| u64::from_le_bytes(slice.try_into().unwrap()))
43}
44
45#[inline(always)]
47pub fn read_u32_le(data: &[u8], offset: usize) -> Option<u32> {
48 data.get(offset..offset + 4).map(|slice| u32::from_le_bytes(slice.try_into().unwrap()))
49}
50
51#[inline(always)]
53pub fn read_u16_le(data: &[u8], offset: usize) -> Option<u16> {
54 data.get(offset..offset + 2).map(|slice| u16::from_le_bytes(slice.try_into().unwrap()))
55}
56
57#[inline(always)]
59pub fn read_u8(data: &[u8], offset: usize) -> Option<u8> {
60 data.get(offset).copied()
61}
62
63#[inline(always)]
65pub fn read_i32_le(data: &[u8], offset: usize) -> Option<i32> {
66 data.get(offset..offset + 4).map(|slice| i32::from_le_bytes(slice.try_into().unwrap()))
67}
68
69#[inline(always)]
71pub fn read_u128_le(data: &[u8], offset: usize) -> Option<u128> {
72 data.get(offset..offset + 16).map(|slice| u128::from_le_bytes(slice.try_into().unwrap()))
73}
74
75#[inline(always)]
77pub fn read_bool(data: &[u8], offset: usize) -> Option<bool> {
78 data.get(offset).map(|&b| b != 0)
79}
80
81#[inline(always)]
83pub fn read_pubkey(data: &[u8], offset: usize) -> Option<Pubkey> {
84 data.get(offset..offset + 32).and_then(|slice| Pubkey::try_from(slice).ok())
85}
86
87#[inline(always)]
89pub fn get_account(accounts: &[Pubkey], index: usize) -> Option<Pubkey> {
90 accounts.get(index).copied()
91}
92
93pub fn calculate_slippage_bps(amount_in: u64, amount_out_min: u64) -> u16 {
95 if amount_in == 0 {
96 return 0;
97 }
98
99 let slippage = ((amount_in.saturating_sub(amount_out_min)) * 10000) / amount_in;
101 slippage.min(10000) as u16
102}
103
104pub fn calculate_price_impact_bps(_amount_in: u64, amount_out: u64, expected_out: u64) -> u16 {
106 if expected_out == 0 {
107 return 0;
108 }
109
110 let impact = ((expected_out.saturating_sub(amount_out)) * 10000) / expected_out;
111 impact.min(10000) as u16
112}
113
114pub fn read_bytes(data: &[u8], offset: usize, length: usize) -> Option<&[u8]> {
116 if data.len() < offset + length {
117 return None;
118 }
119 Some(&data[offset..offset + length])
120}
121
122#[inline]
125pub fn parse_create_v2_tail_fields(data_after_discriminator: &[u8]) -> Option<(Pubkey, bool, bool)> {
126 let mut offset = 0usize;
127 let (_, l) = read_str_unchecked(data_after_discriminator, offset)?;
128 offset += l;
129 let (_, l) = read_str_unchecked(data_after_discriminator, offset)?;
130 offset += l;
131 let (_, l) = read_str_unchecked(data_after_discriminator, offset)?;
132 offset += l;
133 if data_after_discriminator.len() < offset + 32 + 1 {
134 return None;
135 }
136 let creator = read_pubkey(data_after_discriminator, offset)?;
137 offset += 32;
138 let is_mayhem_mode = read_bool(data_after_discriminator, offset)?;
139 offset += 1;
140 let is_cashback_enabled = if offset < data_after_discriminator.len() {
141 read_bool(data_after_discriminator, offset).unwrap_or(false)
142 } else {
143 false
144 };
145 Some((creator, is_mayhem_mode, is_cashback_enabled))
146}
147
148#[inline]
151pub fn read_str_unchecked(data: &[u8], offset: usize) -> Option<(&str, usize)> {
152 if data.len() < offset + 4 {
153 return None;
154 }
155 let len = u32::from_le_bytes(data[offset..offset + 4].try_into().ok()?) as usize;
156 if data.len() < offset + 4 + len {
157 return None;
158 }
159 let string_bytes = &data[offset + 4..offset + 4 + len];
160 let s = std::str::from_utf8(string_bytes).ok()?;
161 Some((s, 4 + len))
162}
163
164pub fn read_vec_u64(_data: &[u8], _offset: usize) -> Option<Vec<u64>> {
166 Some(vec![0, 0])
169}
170
171#[inline(always)]
173pub fn read_pubkey_fast(bytes: &[u8]) -> Pubkey {
174 crate::logs::utils::read_pubkey(bytes, 0).unwrap_or_default()
175}
176
177pub fn get_instruction_account_getter<'a>(
180 meta: &'a TransactionStatusMeta,
181 transaction: &'a Option<Transaction>,
182 account_keys: Option<&'a Vec<Vec<u8>>>,
183 loaded_writable_addresses: &'a Vec<Vec<u8>>,
185 loaded_readonly_addresses: &'a Vec<Vec<u8>>,
186 index: &(i32, i32), ) -> Option<impl Fn(usize) -> Pubkey + 'a> {
188 let accounts = if index.1 >= 0 {
190 let outer_idx = index.0 as u32;
192 meta.inner_instructions
193 .binary_search_by_key(&outer_idx, |i| i.index)
194 .ok()
195 .and_then(|pos| meta.inner_instructions.get(pos))
196 .or_else(|| {
197 meta.inner_instructions.iter().find(|i| i.index == outer_idx)
199 })?
200 .instructions
201 .get(index.1 as usize)?
202 .accounts
203 .as_slice()
204 } else {
205 transaction
207 .as_ref()?
208 .message
209 .as_ref()?
210 .instructions
211 .get(index.0 as usize)?
212 .accounts
213 .as_slice()
214 };
215
216 Some(move |acc_index: usize| -> Pubkey {
218 let account_index = match accounts.get(acc_index) {
220 Some(&idx) => idx as usize,
221 None => return Pubkey::default(),
222 };
223 let Some(keys) = account_keys else {
225 return Pubkey::default();
226 };
227 if let Some(key_bytes) = keys.get(account_index) {
229 return read_pubkey_fast(key_bytes);
230 }
231 let writable_offset = account_index.saturating_sub(keys.len());
233 if let Some(key_bytes) = loaded_writable_addresses.get(writable_offset) {
234 return read_pubkey_fast(key_bytes);
235 }
236 let readonly_offset = writable_offset.saturating_sub(loaded_writable_addresses.len());
238 if let Some(key_bytes) = loaded_readonly_addresses.get(readonly_offset) {
239 return read_pubkey_fast(key_bytes);
240 }
241 Pubkey::default()
242 })
243}
244
245use crate::core::clock::now_us;
246use std::collections::HashMap;
248
249pub struct InnerInstructionsIndex<'a> {
251 index_map: HashMap<u32, &'a yellowstone_grpc_proto::prelude::InnerInstructions>,
253}
254
255impl<'a> InnerInstructionsIndex<'a> {
256 #[inline]
258 pub fn new(meta: &'a TransactionStatusMeta) -> Self {
259 let mut index_map = HashMap::with_capacity(meta.inner_instructions.len());
260 for inner in &meta.inner_instructions {
261 index_map.insert(inner.index, inner);
262 }
263 Self { index_map }
264 }
265
266 #[inline]
268 pub fn get(
269 &self,
270 outer_index: u32,
271 ) -> Option<&'a yellowstone_grpc_proto::prelude::InnerInstructions> {
272 self.index_map.get(&outer_index).copied()
273 }
274}
275
276pub fn get_instruction_account_getter_indexed<'a>(
278 inner_index: &InnerInstructionsIndex<'a>,
279 transaction: &'a Option<Transaction>,
280 account_keys: Option<&'a Vec<Vec<u8>>>,
281 loaded_writable_addresses: &'a Vec<Vec<u8>>,
282 loaded_readonly_addresses: &'a Vec<Vec<u8>>,
283 index: &(i32, i32),
284) -> Option<impl Fn(usize) -> Pubkey + 'a> {
285 let accounts = if index.1 >= 0 {
286 inner_index.get(index.0 as u32)?.instructions.get(index.1 as usize)?.accounts.as_slice()
288 } else {
289 transaction
290 .as_ref()?
291 .message
292 .as_ref()?
293 .instructions
294 .get(index.0 as usize)?
295 .accounts
296 .as_slice()
297 };
298
299 Some(move |acc_index: usize| -> Pubkey {
300 let account_index = match accounts.get(acc_index) {
301 Some(&idx) => idx as usize,
302 None => return Pubkey::default(),
303 };
304 let Some(keys) = account_keys else {
305 return Pubkey::default();
306 };
307 if let Some(key_bytes) = keys.get(account_index) {
308 return read_pubkey_fast(key_bytes);
309 }
310 let writable_offset = account_index.saturating_sub(keys.len());
311 if let Some(key_bytes) = loaded_writable_addresses.get(writable_offset) {
312 return read_pubkey_fast(key_bytes);
313 }
314 let readonly_offset = writable_offset.saturating_sub(loaded_writable_addresses.len());
315 if let Some(key_bytes) = loaded_readonly_addresses.get(readonly_offset) {
316 return read_pubkey_fast(key_bytes);
317 }
318 Pubkey::default()
319 })
320}