sol_parser_sdk/instr/
utils.rs1use solana_sdk::{pubkey::Pubkey, signature::Signature};
4use crate::core::events::EventMetadata;
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 {
16 signature,
17 slot,
18 tx_index,
19 block_time_us,
20 grpc_recv_us,
21 recent_blockhash: None,
22 }
23}
24
25#[inline(always)]
27pub fn create_metadata_simple(
28 signature: Signature,
29 slot: u64,
30 tx_index: u64,
31 block_time_us: Option<i64>,
32 _program_id: Pubkey,
33) -> EventMetadata {
34 let current_time = now_us();
35
36 EventMetadata {
37 signature,
38 slot,
39 tx_index,
40 block_time_us: block_time_us.unwrap_or(0),
41 grpc_recv_us: current_time,
42 recent_blockhash: None,
43 }
44}
45
46#[inline(always)]
48pub fn read_u64_le(data: &[u8], offset: usize) -> Option<u64> {
49 data.get(offset..offset + 8)
50 .map(|slice| u64::from_le_bytes(slice.try_into().unwrap()))
51}
52
53#[inline(always)]
55pub fn read_u32_le(data: &[u8], offset: usize) -> Option<u32> {
56 data.get(offset..offset + 4)
57 .map(|slice| u32::from_le_bytes(slice.try_into().unwrap()))
58}
59
60#[inline(always)]
62pub fn read_u16_le(data: &[u8], offset: usize) -> Option<u16> {
63 data.get(offset..offset + 2)
64 .map(|slice| u16::from_le_bytes(slice.try_into().unwrap()))
65}
66
67#[inline(always)]
69pub fn read_u8(data: &[u8], offset: usize) -> Option<u8> {
70 data.get(offset).copied()
71}
72
73#[inline(always)]
75pub fn read_i32_le(data: &[u8], offset: usize) -> Option<i32> {
76 data.get(offset..offset + 4)
77 .map(|slice| i32::from_le_bytes(slice.try_into().unwrap()))
78}
79
80#[inline(always)]
82pub fn read_u128_le(data: &[u8], offset: usize) -> Option<u128> {
83 data.get(offset..offset + 16)
84 .map(|slice| u128::from_le_bytes(slice.try_into().unwrap()))
85}
86
87#[inline(always)]
89pub fn read_bool(data: &[u8], offset: usize) -> Option<bool> {
90 data.get(offset).map(|&b| b != 0)
91}
92
93#[inline(always)]
95pub fn read_pubkey(data: &[u8], offset: usize) -> Option<Pubkey> {
96 data.get(offset..offset + 32)
97 .and_then(|slice| Pubkey::try_from(slice).ok())
98}
99
100#[inline(always)]
102pub fn get_account(accounts: &[Pubkey], index: usize) -> Option<Pubkey> {
103 accounts.get(index).copied()
104}
105
106pub fn calculate_slippage_bps(amount_in: u64, amount_out_min: u64) -> u16 {
108 if amount_in == 0 {
109 return 0;
110 }
111
112 let slippage = ((amount_in.saturating_sub(amount_out_min)) * 10000) / amount_in;
114 slippage.min(10000) as u16
115}
116
117pub fn calculate_price_impact_bps(_amount_in: u64, amount_out: u64, expected_out: u64) -> u16 {
119 if expected_out == 0 {
120 return 0;
121 }
122
123 let impact = ((expected_out.saturating_sub(amount_out)) * 10000) / expected_out;
124 impact.min(10000) as u16
125}
126
127pub fn read_bytes(data: &[u8], offset: usize, length: usize) -> Option<&[u8]> {
129 if data.len() < offset + length {
130 return None;
131 }
132 Some(&data[offset..offset + length])
133}
134
135#[inline]
138pub fn read_str_unchecked(data: &[u8], offset: usize) -> Option<(&str, usize)> {
139 if data.len() < offset + 4 {
140 return None;
141 }
142 let len = u32::from_le_bytes(data[offset..offset + 4].try_into().ok()?) as usize;
143 if data.len() < offset + 4 + len {
144 return None;
145 }
146 let string_bytes = &data[offset + 4..offset + 4 + len];
147 let s = std::str::from_utf8(string_bytes).ok()?;
148 Some((s, 4 + len))
149}
150
151pub fn read_vec_u64(_data: &[u8], _offset: usize) -> Option<Vec<u64>> {
153 Some(vec![0, 0])
156}
157
158#[inline(always)]
160pub fn read_pubkey_fast(bytes: &[u8]) -> Pubkey {
161 crate::logs::utils::read_pubkey(bytes, 0).unwrap_or_default()
162}
163
164pub fn get_instruction_account_getter<'a>(
167 meta: &'a TransactionStatusMeta,
168 transaction: &'a Option<Transaction>,
169 account_keys: Option<&'a Vec<Vec<u8>>>,
170 loaded_writable_addresses: &'a Vec<Vec<u8>>,
172 loaded_readonly_addresses: &'a Vec<Vec<u8>>,
173 index: &(i32, i32), ) -> Option<impl Fn(usize) -> Pubkey + 'a> {
175 let accounts = if index.1 >= 0 {
177 let outer_idx = index.0 as u32;
179 meta.inner_instructions
180 .binary_search_by_key(&outer_idx, |i| i.index)
181 .ok()
182 .and_then(|pos| meta.inner_instructions.get(pos))
183 .or_else(|| {
184 meta.inner_instructions.iter().find(|i| i.index == outer_idx)
186 })?
187 .instructions
188 .get(index.1 as usize)?
189 .accounts
190 .as_slice()
191 } else {
192 transaction
194 .as_ref()?
195 .message
196 .as_ref()?
197 .instructions
198 .get(index.0 as usize)?
199 .accounts
200 .as_slice()
201 };
202
203 Some(move |acc_index: usize| -> Pubkey {
205 let account_index = match accounts.get(acc_index) {
207 Some(&idx) => idx as usize,
208 None => return Pubkey::default(),
209 };
210 let Some(keys) = account_keys else {
212 return Pubkey::default();
213 };
214 if let Some(key_bytes) = keys.get(account_index) {
216 return read_pubkey_fast(key_bytes);
217 }
218 let writable_offset = account_index.saturating_sub(keys.len());
220 if let Some(key_bytes) = loaded_writable_addresses.get(writable_offset) {
221 return read_pubkey_fast(key_bytes);
222 }
223 let readonly_offset = writable_offset.saturating_sub(loaded_writable_addresses.len());
225 if let Some(key_bytes) = loaded_readonly_addresses.get(readonly_offset) {
226 return read_pubkey_fast(key_bytes);
227 }
228 Pubkey::default()
229 })
230}
231
232use std::collections::HashMap;
234use crate::core::clock::now_us;
235
236pub struct InnerInstructionsIndex<'a> {
238 index_map: HashMap<u32, &'a yellowstone_grpc_proto::prelude::InnerInstructions>,
240}
241
242impl<'a> InnerInstructionsIndex<'a> {
243 #[inline]
245 pub fn new(meta: &'a TransactionStatusMeta) -> Self {
246 let mut index_map = HashMap::with_capacity(meta.inner_instructions.len());
247 for inner in &meta.inner_instructions {
248 index_map.insert(inner.index, inner);
249 }
250 Self { index_map }
251 }
252
253 #[inline]
255 pub fn get(&self, outer_index: u32) -> Option<&'a yellowstone_grpc_proto::prelude::InnerInstructions> {
256 self.index_map.get(&outer_index).copied()
257 }
258}
259
260pub fn get_instruction_account_getter_indexed<'a>(
262 inner_index: &InnerInstructionsIndex<'a>,
263 transaction: &'a Option<Transaction>,
264 account_keys: Option<&'a Vec<Vec<u8>>>,
265 loaded_writable_addresses: &'a Vec<Vec<u8>>,
266 loaded_readonly_addresses: &'a Vec<Vec<u8>>,
267 index: &(i32, i32),
268) -> Option<impl Fn(usize) -> Pubkey + 'a> {
269 let accounts = if index.1 >= 0 {
270 inner_index.get(index.0 as u32)?
272 .instructions
273 .get(index.1 as usize)?
274 .accounts
275 .as_slice()
276 } else {
277 transaction
278 .as_ref()?
279 .message
280 .as_ref()?
281 .instructions
282 .get(index.0 as usize)?
283 .accounts
284 .as_slice()
285 };
286
287 Some(move |acc_index: usize| -> Pubkey {
288 let account_index = match accounts.get(acc_index) {
289 Some(&idx) => idx as usize,
290 None => return Pubkey::default(),
291 };
292 let Some(keys) = account_keys else {
293 return Pubkey::default();
294 };
295 if let Some(key_bytes) = keys.get(account_index) {
296 return read_pubkey_fast(key_bytes);
297 }
298 let writable_offset = account_index.saturating_sub(keys.len());
299 if let Some(key_bytes) = loaded_writable_addresses.get(writable_offset) {
300 return read_pubkey_fast(key_bytes);
301 }
302 let readonly_offset = writable_offset.saturating_sub(loaded_writable_addresses.len());
303 if let Some(key_bytes) = loaded_readonly_addresses.get(readonly_offset) {
304 return read_pubkey_fast(key_bytes);
305 }
306 Pubkey::default()
307 })
308}