use crate::core::events::EventMetadata;
use solana_sdk::{pubkey::Pubkey, signature::Signature};
use yellowstone_grpc_proto::prelude::{Transaction, TransactionStatusMeta};
pub fn create_metadata(
signature: Signature,
slot: u64,
tx_index: u64,
block_time_us: i64,
grpc_recv_us: i64,
) -> EventMetadata {
EventMetadata { signature, slot, tx_index, block_time_us, grpc_recv_us, recent_blockhash: None }
}
#[inline(always)]
pub fn create_metadata_simple(
signature: Signature,
slot: u64,
tx_index: u64,
block_time_us: Option<i64>,
_program_id: Pubkey,
) -> EventMetadata {
let current_time = now_us();
EventMetadata {
signature,
slot,
tx_index,
block_time_us: block_time_us.unwrap_or(0),
grpc_recv_us: current_time,
recent_blockhash: None,
}
}
#[inline(always)]
pub fn read_u64_le(data: &[u8], offset: usize) -> Option<u64> {
data.get(offset..offset + 8).map(|slice| u64::from_le_bytes(slice.try_into().unwrap()))
}
#[inline(always)]
pub fn read_u32_le(data: &[u8], offset: usize) -> Option<u32> {
data.get(offset..offset + 4).map(|slice| u32::from_le_bytes(slice.try_into().unwrap()))
}
#[inline(always)]
pub fn read_u16_le(data: &[u8], offset: usize) -> Option<u16> {
data.get(offset..offset + 2).map(|slice| u16::from_le_bytes(slice.try_into().unwrap()))
}
#[inline(always)]
pub fn read_u8(data: &[u8], offset: usize) -> Option<u8> {
data.get(offset).copied()
}
#[inline(always)]
pub fn read_i32_le(data: &[u8], offset: usize) -> Option<i32> {
data.get(offset..offset + 4).map(|slice| i32::from_le_bytes(slice.try_into().unwrap()))
}
#[inline(always)]
pub fn read_u128_le(data: &[u8], offset: usize) -> Option<u128> {
data.get(offset..offset + 16).map(|slice| u128::from_le_bytes(slice.try_into().unwrap()))
}
#[inline(always)]
pub fn read_bool(data: &[u8], offset: usize) -> Option<bool> {
data.get(offset).map(|&b| b != 0)
}
#[inline(always)]
pub fn read_pubkey(data: &[u8], offset: usize) -> Option<Pubkey> {
data.get(offset..offset + 32).and_then(|slice| Pubkey::try_from(slice).ok())
}
#[inline(always)]
pub fn get_account(accounts: &[Pubkey], index: usize) -> Option<Pubkey> {
accounts.get(index).copied()
}
pub fn calculate_slippage_bps(amount_in: u64, amount_out_min: u64) -> u16 {
if amount_in == 0 {
return 0;
}
let slippage = ((amount_in.saturating_sub(amount_out_min)) * 10000) / amount_in;
slippage.min(10000) as u16
}
pub fn calculate_price_impact_bps(_amount_in: u64, amount_out: u64, expected_out: u64) -> u16 {
if expected_out == 0 {
return 0;
}
let impact = ((expected_out.saturating_sub(amount_out)) * 10000) / expected_out;
impact.min(10000) as u16
}
pub fn read_bytes(data: &[u8], offset: usize, length: usize) -> Option<&[u8]> {
if data.len() < offset + length {
return None;
}
Some(&data[offset..offset + length])
}
#[inline]
pub fn parse_create_v2_tail_fields(data_after_discriminator: &[u8]) -> Option<(Pubkey, bool, bool)> {
let mut offset = 0usize;
let (_, l) = read_str_unchecked(data_after_discriminator, offset)?;
offset += l;
let (_, l) = read_str_unchecked(data_after_discriminator, offset)?;
offset += l;
let (_, l) = read_str_unchecked(data_after_discriminator, offset)?;
offset += l;
if data_after_discriminator.len() < offset + 32 + 1 {
return None;
}
let creator = read_pubkey(data_after_discriminator, offset)?;
offset += 32;
let is_mayhem_mode = read_bool(data_after_discriminator, offset)?;
offset += 1;
let is_cashback_enabled = if offset < data_after_discriminator.len() {
read_bool(data_after_discriminator, offset).unwrap_or(false)
} else {
false
};
Some((creator, is_mayhem_mode, is_cashback_enabled))
}
#[inline]
pub fn read_str_unchecked(data: &[u8], offset: usize) -> Option<(&str, usize)> {
if data.len() < offset + 4 {
return None;
}
let len = u32::from_le_bytes(data[offset..offset + 4].try_into().ok()?) as usize;
if data.len() < offset + 4 + len {
return None;
}
let string_bytes = &data[offset + 4..offset + 4 + len];
let s = std::str::from_utf8(string_bytes).ok()?;
Some((s, 4 + len))
}
pub fn read_vec_u64(_data: &[u8], _offset: usize) -> Option<Vec<u64>> {
Some(vec![0, 0])
}
#[inline(always)]
pub fn read_pubkey_fast(bytes: &[u8]) -> Pubkey {
crate::logs::utils::read_pubkey(bytes, 0).unwrap_or_default()
}
pub fn get_instruction_account_getter<'a>(
meta: &'a TransactionStatusMeta,
transaction: &'a Option<Transaction>,
account_keys: Option<&'a Vec<Vec<u8>>>,
loaded_writable_addresses: &'a Vec<Vec<u8>>,
loaded_readonly_addresses: &'a Vec<Vec<u8>>,
index: &(i32, i32), ) -> Option<impl Fn(usize) -> Pubkey + 'a> {
let accounts = if index.1 >= 0 {
let outer_idx = index.0 as u32;
meta.inner_instructions
.binary_search_by_key(&outer_idx, |i| i.index)
.ok()
.and_then(|pos| meta.inner_instructions.get(pos))
.or_else(|| {
meta.inner_instructions.iter().find(|i| i.index == outer_idx)
})?
.instructions
.get(index.1 as usize)?
.accounts
.as_slice()
} else {
transaction
.as_ref()?
.message
.as_ref()?
.instructions
.get(index.0 as usize)?
.accounts
.as_slice()
};
Some(move |acc_index: usize| -> Pubkey {
let account_index = match accounts.get(acc_index) {
Some(&idx) => idx as usize,
None => return Pubkey::default(),
};
let Some(keys) = account_keys else {
return Pubkey::default();
};
if let Some(key_bytes) = keys.get(account_index) {
return read_pubkey_fast(key_bytes);
}
let writable_offset = account_index.saturating_sub(keys.len());
if let Some(key_bytes) = loaded_writable_addresses.get(writable_offset) {
return read_pubkey_fast(key_bytes);
}
let readonly_offset = writable_offset.saturating_sub(loaded_writable_addresses.len());
if let Some(key_bytes) = loaded_readonly_addresses.get(readonly_offset) {
return read_pubkey_fast(key_bytes);
}
Pubkey::default()
})
}
use crate::core::clock::now_us;
use std::collections::HashMap;
pub struct InnerInstructionsIndex<'a> {
index_map: HashMap<u32, &'a yellowstone_grpc_proto::prelude::InnerInstructions>,
}
impl<'a> InnerInstructionsIndex<'a> {
#[inline]
pub fn new(meta: &'a TransactionStatusMeta) -> Self {
let mut index_map = HashMap::with_capacity(meta.inner_instructions.len());
for inner in &meta.inner_instructions {
index_map.insert(inner.index, inner);
}
Self { index_map }
}
#[inline]
pub fn get(
&self,
outer_index: u32,
) -> Option<&'a yellowstone_grpc_proto::prelude::InnerInstructions> {
self.index_map.get(&outer_index).copied()
}
}
pub fn get_instruction_account_getter_indexed<'a>(
inner_index: &InnerInstructionsIndex<'a>,
transaction: &'a Option<Transaction>,
account_keys: Option<&'a Vec<Vec<u8>>>,
loaded_writable_addresses: &'a Vec<Vec<u8>>,
loaded_readonly_addresses: &'a Vec<Vec<u8>>,
index: &(i32, i32),
) -> Option<impl Fn(usize) -> Pubkey + 'a> {
let accounts = if index.1 >= 0 {
inner_index.get(index.0 as u32)?.instructions.get(index.1 as usize)?.accounts.as_slice()
} else {
transaction
.as_ref()?
.message
.as_ref()?
.instructions
.get(index.0 as usize)?
.accounts
.as_slice()
};
Some(move |acc_index: usize| -> Pubkey {
let account_index = match accounts.get(acc_index) {
Some(&idx) => idx as usize,
None => return Pubkey::default(),
};
let Some(keys) = account_keys else {
return Pubkey::default();
};
if let Some(key_bytes) = keys.get(account_index) {
return read_pubkey_fast(key_bytes);
}
let writable_offset = account_index.saturating_sub(keys.len());
if let Some(key_bytes) = loaded_writable_addresses.get(writable_offset) {
return read_pubkey_fast(key_bytes);
}
let readonly_offset = writable_offset.saturating_sub(loaded_writable_addresses.len());
if let Some(key_bytes) = loaded_readonly_addresses.get(readonly_offset) {
return read_pubkey_fast(key_bytes);
}
Pubkey::default()
})
}