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)]
101pub fn read_option_u64_idl(data: &[u8], offset: usize) -> Option<u64> {
102 read_u64_le(data, offset)
103}
104
105#[inline(always)]
107pub fn read_pubkey(data: &[u8], offset: usize) -> Option<Pubkey> {
108 data.get(offset..offset + 32).and_then(|slice| Pubkey::try_from(slice).ok())
109}
110
111#[inline(always)]
113pub fn get_account(accounts: &[Pubkey], index: usize) -> Option<Pubkey> {
114 accounts.get(index).copied()
115}
116
117pub fn calculate_slippage_bps(amount_in: u64, amount_out_min: u64) -> u16 {
119 if amount_in == 0 {
120 return 0;
121 }
122
123 let slippage = ((amount_in.saturating_sub(amount_out_min)) * 10000) / amount_in;
125 slippage.min(10000) as u16
126}
127
128pub fn calculate_price_impact_bps(_amount_in: u64, amount_out: u64, expected_out: u64) -> u16 {
130 if expected_out == 0 {
131 return 0;
132 }
133
134 let impact = ((expected_out.saturating_sub(amount_out)) * 10000) / expected_out;
135 impact.min(10000) as u16
136}
137
138pub fn read_bytes(data: &[u8], offset: usize, length: usize) -> Option<&[u8]> {
140 if data.len() < offset + length {
141 return None;
142 }
143 Some(&data[offset..offset + length])
144}
145
146#[inline]
150pub fn parse_create_v2_tail_fields(
151 data_after_discriminator: &[u8],
152) -> Option<(Pubkey, bool, bool, u64, bool)> {
153 let mut offset = 0usize;
154 let (_, l) = read_str_unchecked(data_after_discriminator, offset)?;
155 offset += l;
156 let (_, l) = read_str_unchecked(data_after_discriminator, offset)?;
157 offset += l;
158 let (_, l) = read_str_unchecked(data_after_discriminator, offset)?;
159 offset += l;
160 if data_after_discriminator.len() < offset + 32 + 1 {
161 return None;
162 }
163 let creator = read_pubkey(data_after_discriminator, offset)?;
164 offset += 32;
165 let is_mayhem_mode = read_bool(data_after_discriminator, offset)?;
166 offset += 1;
167 let is_cashback_enabled = if offset < data_after_discriminator.len() {
168 read_option_bool_idl(data_after_discriminator, offset).unwrap_or(false)
169 } else {
170 false
171 };
172 if offset < data_after_discriminator.len() {
173 offset += 1;
174 }
175 let creator_fee_bps = read_option_u64_idl(data_after_discriminator, offset).unwrap_or_default();
176 if offset + 8 <= data_after_discriminator.len() {
177 offset += 8;
178 }
179 let is_holder_reward =
180 read_option_bool_idl(data_after_discriminator, offset).unwrap_or_default();
181 Some((creator, is_mayhem_mode, is_cashback_enabled, creator_fee_bps, is_holder_reward))
182}
183
184#[inline]
187pub fn read_str_unchecked(data: &[u8], offset: usize) -> Option<(&str, usize)> {
188 if data.len() < offset + 4 {
189 return None;
190 }
191 let len = u32::from_le_bytes(data[offset..offset + 4].try_into().ok()?) as usize;
192 if data.len() < offset + 4 + len {
193 return None;
194 }
195 let string_bytes = &data[offset + 4..offset + 4 + len];
196 let s = std::str::from_utf8(string_bytes).ok()?;
197 Some((s, 4 + len))
198}
199
200pub fn read_vec_u64(_data: &[u8], _offset: usize) -> Option<Vec<u64>> {
202 Some(vec![0, 0])
205}
206
207#[inline(always)]
209pub fn read_pubkey_fast(bytes: &[u8]) -> Pubkey {
210 crate::logs::utils::read_pubkey(bytes, 0).unwrap_or_default()
211}
212
213pub fn get_instruction_account_getter<'a>(
216 meta: &'a TransactionStatusMeta,
217 transaction: &'a Option<Transaction>,
218 account_keys: Option<&'a Vec<Vec<u8>>>,
219 loaded_writable_addresses: &'a [Vec<u8>],
221 loaded_readonly_addresses: &'a [Vec<u8>],
222 index: &(i32, i32), ) -> Option<impl Fn(usize) -> Pubkey + 'a> {
224 let accounts = if index.1 >= 0 {
226 let outer_idx = index.0 as u32;
228 meta.inner_instructions
229 .binary_search_by_key(&outer_idx, |i| i.index)
230 .ok()
231 .and_then(|pos| meta.inner_instructions.get(pos))
232 .or_else(|| {
233 meta.inner_instructions.iter().find(|i| i.index == outer_idx)
235 })?
236 .instructions
237 .get(index.1 as usize)?
238 .accounts
239 .as_slice()
240 } else {
241 transaction
243 .as_ref()?
244 .message
245 .as_ref()?
246 .instructions
247 .get(index.0 as usize)?
248 .accounts
249 .as_slice()
250 };
251
252 Some(move |acc_index: usize| -> Pubkey {
254 let account_index = match accounts.get(acc_index) {
256 Some(&idx) => idx as usize,
257 None => return Pubkey::default(),
258 };
259 let Some(keys) = account_keys else {
261 return Pubkey::default();
262 };
263 if let Some(key_bytes) = keys.get(account_index) {
265 return read_pubkey_fast(key_bytes);
266 }
267 let writable_offset = account_index.saturating_sub(keys.len());
269 if let Some(key_bytes) = loaded_writable_addresses.get(writable_offset) {
270 return read_pubkey_fast(key_bytes);
271 }
272 let readonly_offset = writable_offset.saturating_sub(loaded_writable_addresses.len());
274 if let Some(key_bytes) = loaded_readonly_addresses.get(readonly_offset) {
275 return read_pubkey_fast(key_bytes);
276 }
277 Pubkey::default()
278 })
279}
280
281use crate::core::clock::now_us;
282use std::collections::HashMap;
284
285pub struct InnerInstructionsIndex<'a> {
287 index_map: HashMap<u32, &'a yellowstone_grpc_proto::prelude::InnerInstructions>,
289}
290
291impl<'a> InnerInstructionsIndex<'a> {
292 #[inline]
294 pub fn new(meta: &'a TransactionStatusMeta) -> Self {
295 let mut index_map = HashMap::with_capacity(meta.inner_instructions.len());
296 for inner in &meta.inner_instructions {
297 index_map.insert(inner.index, inner);
298 }
299 Self { index_map }
300 }
301
302 #[inline]
304 pub fn get(
305 &self,
306 outer_index: u32,
307 ) -> Option<&'a yellowstone_grpc_proto::prelude::InnerInstructions> {
308 self.index_map.get(&outer_index).copied()
309 }
310}
311
312pub fn get_instruction_account_getter_indexed<'a>(
314 inner_index: &InnerInstructionsIndex<'a>,
315 transaction: &'a Option<Transaction>,
316 account_keys: Option<&'a Vec<Vec<u8>>>,
317 loaded_writable_addresses: &'a [Vec<u8>],
318 loaded_readonly_addresses: &'a [Vec<u8>],
319 index: &(i32, i32),
320) -> Option<impl Fn(usize) -> Pubkey + 'a> {
321 let accounts = if index.1 >= 0 {
322 inner_index.get(index.0 as u32)?.instructions.get(index.1 as usize)?.accounts.as_slice()
324 } else {
325 transaction
326 .as_ref()?
327 .message
328 .as_ref()?
329 .instructions
330 .get(index.0 as usize)?
331 .accounts
332 .as_slice()
333 };
334
335 Some(move |acc_index: usize| -> Pubkey {
336 let account_index = match accounts.get(acc_index) {
337 Some(&idx) => idx as usize,
338 None => return Pubkey::default(),
339 };
340 let Some(keys) = account_keys else {
341 return Pubkey::default();
342 };
343 if let Some(key_bytes) = keys.get(account_index) {
344 return read_pubkey_fast(key_bytes);
345 }
346 let writable_offset = account_index.saturating_sub(keys.len());
347 if let Some(key_bytes) = loaded_writable_addresses.get(writable_offset) {
348 return read_pubkey_fast(key_bytes);
349 }
350 let readonly_offset = writable_offset.saturating_sub(loaded_writable_addresses.len());
351 if let Some(key_bytes) = loaded_readonly_addresses.get(readonly_offset) {
352 return read_pubkey_fast(key_bytes);
353 }
354 Pubkey::default()
355 })
356}
357
358#[cfg(test)]
359mod option_bool_tests {
360 use super::*;
361
362 #[test]
363 fn read_option_bool_idl_strict() {
364 assert_eq!(read_option_bool_idl(&[0], 0), Some(false));
365 assert_eq!(read_option_bool_idl(&[1], 0), Some(true));
366 assert_eq!(read_option_bool_idl(&[2], 0), None);
367 }
368
369 #[test]
370 fn parse_create_v2_tail_matches_anchor_len() {
371 let mut p = Vec::new();
373 p.extend_from_slice(&(1u32.to_le_bytes()));
374 p.push(b'a');
375 p.extend_from_slice(&(1u32.to_le_bytes()));
376 p.push(b'b');
377 p.extend_from_slice(&(1u32.to_le_bytes()));
378 p.push(b'c');
379 p.extend_from_slice(&[0u8; 32]);
380 p.push(1u8); p.push(1u8); assert_eq!(p.len(), 49);
383 let (creator, mayhem, cb, creator_fee_bps, holder_reward) =
384 parse_create_v2_tail_fields(&p).expect("parse");
385 assert_eq!(creator, Pubkey::default());
386 assert!(mayhem);
387 assert!(cb);
388 assert_eq!(creator_fee_bps, 0);
389 assert!(!holder_reward);
390 }
391
392 #[test]
393 fn parse_create_v2_tail_reads_holder_rewards_fields() {
394 let mut p = Vec::new();
395 for value in ["a", "b", "c"] {
396 p.extend_from_slice(&(value.len() as u32).to_le_bytes());
397 p.extend_from_slice(value.as_bytes());
398 }
399 p.extend_from_slice(&[0u8; 32]);
400 p.push(0); p.push(0); p.extend_from_slice(&250u64.to_le_bytes());
403 p.push(1); let (_, mayhem, cashback, creator_fee_bps, holder_reward) =
406 parse_create_v2_tail_fields(&p).expect("parse");
407 assert!(!mayhem);
408 assert!(!cashback);
409 assert_eq!(creator_fee_bps, 250);
410 assert!(holder_reward);
411 }
412}