sol_parser_sdk/logs/
utils.rs1use crate::core::clock::now_us;
6use crate::core::events::EventMetadata;
7#[cfg(target_os = "windows")]
8use crate::core::now_micros;
9use base64::{engine::general_purpose, Engine as _};
10use solana_sdk::{pubkey::Pubkey, signature::Signature};
11
12#[inline]
14pub fn extract_program_data(log: &str) -> Option<Vec<u8>> {
15 use memchr::memmem;
16
17 let log_bytes = log.as_bytes();
18 let pos = memmem::find(log_bytes, b"Program data: ")?;
19
20 let data_part = &log[pos + 14..];
21 general_purpose::STANDARD.decode(data_part.trim()).ok()
22}
23
24#[inline]
26pub fn extract_discriminator_fast(log: &str) -> Option<[u8; 8]> {
27 use memchr::memmem;
28
29 let log_bytes = log.as_bytes();
30 let pos = memmem::find(log_bytes, b"Program data: ")?;
31
32 let data_part = log[pos + 14..].trim();
33
34 if data_part.len() < 12 {
37 return None;
38 }
39
40 let prefix = &data_part[..16];
42
43 let mut buf = [0u8; 12];
44 let decoded_len = general_purpose::STANDARD.decode_slice(prefix.as_bytes(), &mut buf).ok()?;
45
46 if decoded_len >= 8 {
47 Some(buf[0..8].try_into().unwrap())
48 } else {
49 None
50 }
51}
52
53#[inline]
55pub fn read_u64_le(data: &[u8], offset: usize) -> Option<u64> {
56 data.get(offset..offset + 8).map(|slice| u64::from_le_bytes(slice.try_into().unwrap()))
57}
58
59#[inline]
61pub fn read_u32_le(data: &[u8], offset: usize) -> Option<u32> {
62 data.get(offset..offset + 4).map(|slice| u32::from_le_bytes(slice.try_into().unwrap()))
63}
64
65pub fn read_i64_le(data: &[u8], offset: usize) -> Option<i64> {
67 data.get(offset..offset + 8).map(|slice| i64::from_le_bytes(slice.try_into().unwrap()))
68}
69
70pub 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
75pub fn read_u128_le(data: &[u8], offset: usize) -> Option<u128> {
77 data.get(offset..offset + 16).map(|slice| u128::from_le_bytes(slice.try_into().unwrap()))
78}
79
80pub fn read_u16_le(data: &[u8], offset: usize) -> Option<u16> {
82 data.get(offset..offset + 2).map(|slice| u16::from_le_bytes(slice.try_into().unwrap()))
83}
84
85pub fn read_u8(data: &[u8], offset: usize) -> Option<u8> {
87 data.get(offset).copied()
88}
89
90#[inline]
92pub fn read_pubkey(data: &[u8], offset: usize) -> Option<Pubkey> {
93 data.get(offset..offset + 32).and_then(|slice| {
94 let key_bytes: [u8; 32] = slice.try_into().ok()?;
95 Some(Pubkey::new_from_array(key_bytes))
96 })
97}
98
99pub fn read_string(data: &[u8], offset: usize) -> Option<(String, usize)> {
101 let (string_ref, consumed) = read_string_ref(data, offset)?;
102 Some((string_ref.to_string(), consumed))
103}
104
105#[inline(always)] pub fn read_string_ref(data: &[u8], offset: usize) -> Option<(&str, usize)> {
118 if data.len() < offset + 4 {
119 return None;
120 }
121
122 let len = read_u32_le(data, offset)? as usize;
123 if data.len() < offset + 4 + len {
124 return None;
125 }
126
127 let string_bytes = &data[offset + 4..offset + 4 + len];
128 let string_ref = std::str::from_utf8(string_bytes).ok()?; Some((string_ref, 4 + len))
130}
131
132pub fn read_bool(data: &[u8], offset: usize) -> Option<bool> {
134 if data.len() <= offset {
135 return None;
136 }
137 Some(data[offset] == 1)
138}
139
140pub fn timestamp_to_microseconds(seconds: i64, nanos: i32) -> i128 {
142 seconds as i128 * 1_000_000 + (nanos as i128 / 1_000)
144}
145
146pub fn create_metadata_simple(
148 signature: Signature,
149 slot: u64,
150 tx_index: u64,
151 block_time_us: Option<i64>,
152 program_id: Pubkey,
153 grpc_recv_us: i64,
154) -> EventMetadata {
155 EventMetadata {
156 signature,
157 slot,
158 tx_index,
159 block_time_us: block_time_us.unwrap_or(0),
160 grpc_recv_us,
161 recent_blockhash: None,
162 }
163}
164
165pub fn create_metadata_default(
167 signature: Signature,
168 slot: u64,
169 tx_index: u64,
170 block_time_us: Option<i64>,
171) -> EventMetadata {
172 let current_time = now_us();
173 EventMetadata {
174 signature,
175 slot,
176 tx_index,
177 block_time_us: block_time_us.unwrap_or(0),
178 grpc_recv_us: current_time,
179 recent_blockhash: None,
180 }
181}
182
183pub mod text_parser {
185
186 pub fn extract_number_from_text(text: &str, field: &str) -> Option<u64> {
188 if let Some(start) = text.find(&format!("{}:", field)) {
189 let after_colon = &text[start + field.len() + 1..];
190 if let Some(end) = after_colon.find(' ').or_else(|| after_colon.find(',')) {
191 after_colon[..end].trim().parse().ok()
192 } else {
193 after_colon.trim().parse().ok()
194 }
195 } else {
196 None
197 }
198 }
199
200 pub fn extract_text_field(text: &str, field: &str) -> Option<String> {
202 extract_text_field_ref(text, field).map(|s| s.to_string())
203 }
204
205 #[inline(always)] pub fn extract_text_field_ref<'a>(text: &'a str, field: &str) -> Option<&'a str> {
217 let start = text.find(&format!("{}:", field))?;
218 let after_colon = &text[start + field.len() + 1..];
219 if let Some(end) = after_colon.find(',').or_else(|| after_colon.find(' ')) {
220 Some(after_colon[..end].trim())
221 } else {
222 Some(after_colon.trim())
223 }
224 }
225
226 pub fn detect_trade_type(log: &str) -> Option<bool> {
228 if log.contains("buy") || log.contains("Buy") {
229 Some(true)
230 } else if log.contains("sell") || log.contains("Sell") {
231 Some(false)
232 } else {
233 None
234 }
235 }
236}