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