1use 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_i64_le(data: &[u8], offset: usize) -> Option<i64> {
54 data.get(offset..offset + 8).map(|slice| i64::from_le_bytes(slice.try_into().unwrap()))
55}
56
57#[inline(always)]
59pub fn read_u16_le(data: &[u8], offset: usize) -> Option<u16> {
60 data.get(offset..offset + 2).map(|slice| u16::from_le_bytes(slice.try_into().unwrap()))
61}
62
63#[inline(always)]
65pub fn read_u8(data: &[u8], offset: usize) -> Option<u8> {
66 data.get(offset).copied()
67}
68
69#[inline(always)]
71pub fn read_i32_le(data: &[u8], offset: usize) -> Option<i32> {
72 data.get(offset..offset + 4).map(|slice| i32::from_le_bytes(slice.try_into().unwrap()))
73}
74
75#[inline(always)]
77pub fn read_u128_le(data: &[u8], offset: usize) -> Option<u128> {
78 data.get(offset..offset + 16).map(|slice| u128::from_le_bytes(slice.try_into().unwrap()))
79}
80
81#[inline(always)]
83pub fn read_bool(data: &[u8], offset: usize) -> Option<bool> {
84 data.get(offset).map(|&b| b != 0)
85}
86
87#[inline(always)]
90pub fn read_option_bool_idl(data: &[u8], offset: usize) -> Option<bool> {
91 match data.get(offset).copied()? {
92 0 => Some(false),
93 1 => Some(true),
94 _ => None,
95 }
96}
97
98#[inline(always)]
100pub fn read_pubkey(data: &[u8], offset: usize) -> Option<Pubkey> {
101 data.get(offset..offset + 32).and_then(|slice| Pubkey::try_from(slice).ok())
102}
103
104#[inline(always)]
106pub fn get_account(accounts: &[Pubkey], index: usize) -> Option<Pubkey> {
107 accounts.get(index).copied()
108}
109
110pub fn calculate_slippage_bps(amount_in: u64, amount_out_min: u64) -> u16 {
112 if amount_in == 0 {
113 return 0;
114 }
115
116 let slippage = ((amount_in.saturating_sub(amount_out_min)) * 10000) / amount_in;
118 slippage.min(10000) as u16
119}
120
121pub fn calculate_price_impact_bps(_amount_in: u64, amount_out: u64, expected_out: u64) -> u16 {
123 if expected_out == 0 {
124 return 0;
125 }
126
127 let impact = ((expected_out.saturating_sub(amount_out)) * 10000) / expected_out;
128 impact.min(10000) as u16
129}
130
131pub fn read_bytes(data: &[u8], offset: usize, length: usize) -> Option<&[u8]> {
133 if data.len() < offset + length {
134 return None;
135 }
136 Some(&data[offset..offset + length])
137}
138
139#[inline]
143pub fn parse_create_v2_tail_fields(
144 data_after_discriminator: &[u8],
145) -> Option<(Pubkey, bool, bool)> {
146 let mut offset = 0usize;
147 let (_, l) = read_str_unchecked(data_after_discriminator, offset)?;
148 offset += l;
149 let (_, l) = read_str_unchecked(data_after_discriminator, offset)?;
150 offset += l;
151 let (_, l) = read_str_unchecked(data_after_discriminator, offset)?;
152 offset += l;
153 if data_after_discriminator.len() < offset + 32 + 1 {
154 return None;
155 }
156 let creator = read_pubkey(data_after_discriminator, offset)?;
157 offset += 32;
158 let is_mayhem_mode = read_bool(data_after_discriminator, offset)?;
159 offset += 1;
160 let is_cashback_enabled = if offset < data_after_discriminator.len() {
161 read_option_bool_idl(data_after_discriminator, offset).unwrap_or(false)
162 } else {
163 false
164 };
165 Some((creator, is_mayhem_mode, is_cashback_enabled))
166}
167
168#[inline]
171pub fn read_str_unchecked(data: &[u8], offset: usize) -> Option<(&str, usize)> {
172 if data.len() < offset + 4 {
173 return None;
174 }
175 let len = u32::from_le_bytes(data[offset..offset + 4].try_into().ok()?) as usize;
176 if data.len() < offset + 4 + len {
177 return None;
178 }
179 let string_bytes = &data[offset + 4..offset + 4 + len];
180 let s = std::str::from_utf8(string_bytes).ok()?;
181 Some((s, 4 + len))
182}
183
184pub fn read_vec_u64(_data: &[u8], _offset: usize) -> Option<Vec<u64>> {
186 Some(vec![0, 0])
189}
190
191#[inline(always)]
193pub fn read_pubkey_fast(bytes: &[u8]) -> Pubkey {
194 crate::logs::utils::read_pubkey(bytes, 0).unwrap_or_default()
195}
196
197pub fn get_instruction_account_getter<'a>(
200 meta: &'a TransactionStatusMeta,
201 transaction: &'a Option<Transaction>,
202 account_keys: Option<&'a Vec<Vec<u8>>>,
203 loaded_writable_addresses: &'a [Vec<u8>],
205 loaded_readonly_addresses: &'a [Vec<u8>],
206 index: &(i32, i32), ) -> Option<impl Fn(usize) -> Pubkey + 'a> {
208 let accounts = if index.1 >= 0 {
210 let outer_idx = index.0 as u32;
212 meta.inner_instructions
213 .binary_search_by_key(&outer_idx, |i| i.index)
214 .ok()
215 .and_then(|pos| meta.inner_instructions.get(pos))
216 .or_else(|| {
217 meta.inner_instructions.iter().find(|i| i.index == outer_idx)
219 })?
220 .instructions
221 .get(index.1 as usize)?
222 .accounts
223 .as_slice()
224 } else {
225 transaction
227 .as_ref()?
228 .message
229 .as_ref()?
230 .instructions
231 .get(index.0 as usize)?
232 .accounts
233 .as_slice()
234 };
235
236 Some(move |acc_index: usize| -> Pubkey {
238 let account_index = match accounts.get(acc_index) {
240 Some(&idx) => idx as usize,
241 None => return Pubkey::default(),
242 };
243 let Some(keys) = account_keys else {
245 return Pubkey::default();
246 };
247 if let Some(key_bytes) = keys.get(account_index) {
249 return read_pubkey_fast(key_bytes);
250 }
251 let writable_offset = account_index.saturating_sub(keys.len());
253 if let Some(key_bytes) = loaded_writable_addresses.get(writable_offset) {
254 return read_pubkey_fast(key_bytes);
255 }
256 let readonly_offset = writable_offset.saturating_sub(loaded_writable_addresses.len());
258 if let Some(key_bytes) = loaded_readonly_addresses.get(readonly_offset) {
259 return read_pubkey_fast(key_bytes);
260 }
261 Pubkey::default()
262 })
263}
264
265use crate::core::clock::now_us;
266use std::collections::HashMap;
268
269pub struct InnerInstructionsIndex<'a> {
271 index_map: HashMap<u32, &'a yellowstone_grpc_proto::prelude::InnerInstructions>,
273}
274
275impl<'a> InnerInstructionsIndex<'a> {
276 #[inline]
278 pub fn new(meta: &'a TransactionStatusMeta) -> Self {
279 let mut index_map = HashMap::with_capacity(meta.inner_instructions.len());
280 for inner in &meta.inner_instructions {
281 index_map.insert(inner.index, inner);
282 }
283 Self { index_map }
284 }
285
286 #[inline]
288 pub fn get(
289 &self,
290 outer_index: u32,
291 ) -> Option<&'a yellowstone_grpc_proto::prelude::InnerInstructions> {
292 self.index_map.get(&outer_index).copied()
293 }
294}
295
296pub fn get_instruction_account_getter_indexed<'a>(
298 inner_index: &InnerInstructionsIndex<'a>,
299 transaction: &'a Option<Transaction>,
300 account_keys: Option<&'a Vec<Vec<u8>>>,
301 loaded_writable_addresses: &'a [Vec<u8>],
302 loaded_readonly_addresses: &'a [Vec<u8>],
303 index: &(i32, i32),
304) -> Option<impl Fn(usize) -> Pubkey + 'a> {
305 let accounts = if index.1 >= 0 {
306 inner_index.get(index.0 as u32)?.instructions.get(index.1 as usize)?.accounts.as_slice()
308 } else {
309 transaction
310 .as_ref()?
311 .message
312 .as_ref()?
313 .instructions
314 .get(index.0 as usize)?
315 .accounts
316 .as_slice()
317 };
318
319 Some(move |acc_index: usize| -> Pubkey {
320 let account_index = match accounts.get(acc_index) {
321 Some(&idx) => idx as usize,
322 None => return Pubkey::default(),
323 };
324 let Some(keys) = account_keys else {
325 return Pubkey::default();
326 };
327 if let Some(key_bytes) = keys.get(account_index) {
328 return read_pubkey_fast(key_bytes);
329 }
330 let writable_offset = account_index.saturating_sub(keys.len());
331 if let Some(key_bytes) = loaded_writable_addresses.get(writable_offset) {
332 return read_pubkey_fast(key_bytes);
333 }
334 let readonly_offset = writable_offset.saturating_sub(loaded_writable_addresses.len());
335 if let Some(key_bytes) = loaded_readonly_addresses.get(readonly_offset) {
336 return read_pubkey_fast(key_bytes);
337 }
338 Pubkey::default()
339 })
340}
341
342#[cfg(test)]
343mod option_bool_tests {
344 use super::*;
345
346 #[test]
347 fn read_option_bool_idl_strict() {
348 assert_eq!(read_option_bool_idl(&[0], 0), Some(false));
349 assert_eq!(read_option_bool_idl(&[1], 0), Some(true));
350 assert_eq!(read_option_bool_idl(&[2], 0), None);
351 }
352
353 #[test]
354 fn parse_create_v2_tail_matches_anchor_len() {
355 let mut p = Vec::new();
357 p.extend_from_slice(&(1u32.to_le_bytes()));
358 p.push(b'a');
359 p.extend_from_slice(&(1u32.to_le_bytes()));
360 p.push(b'b');
361 p.extend_from_slice(&(1u32.to_le_bytes()));
362 p.push(b'c');
363 p.extend_from_slice(&[0u8; 32]);
364 p.push(1u8); p.push(1u8); assert_eq!(p.len(), 49);
367 let (creator, mayhem, cb) = parse_create_v2_tail_fields(&p).expect("parse");
368 assert_eq!(creator, Pubkey::default());
369 assert!(mayhem);
370 assert!(cb);
371 }
372}