sol_parser_sdk/logs/
utils.rs1use solana_sdk::{pubkey::Pubkey, signature::Signature};
6use crate::core::events::EventMetadata;
7#[cfg(target_os = "windows")]
8use crate::core::now_micros;
9use base64::{Engine as _, engine::general_purpose};
10use crate::core::clock::now_us;
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)
57 .map(|slice| u64::from_le_bytes(slice.try_into().unwrap()))
58}
59
60#[inline]
62pub fn read_u32_le(data: &[u8], offset: usize) -> Option<u32> {
63 data.get(offset..offset + 4)
64 .map(|slice| u32::from_le_bytes(slice.try_into().unwrap()))
65}
66
67pub fn read_i64_le(data: &[u8], offset: usize) -> Option<i64> {
69 data.get(offset..offset + 8)
70 .map(|slice| i64::from_le_bytes(slice.try_into().unwrap()))
71}
72
73pub fn read_i32_le(data: &[u8], offset: usize) -> Option<i32> {
75 data.get(offset..offset + 4)
76 .map(|slice| i32::from_le_bytes(slice.try_into().unwrap()))
77}
78
79pub 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
85pub fn read_u16_le(data: &[u8], offset: usize) -> Option<u16> {
87 data.get(offset..offset + 2)
88 .map(|slice| u16::from_le_bytes(slice.try_into().unwrap()))
89}
90
91pub fn read_u8(data: &[u8], offset: usize) -> Option<u8> {
93 data.get(offset).copied()
94}
95
96#[inline]
98pub fn read_pubkey(data: &[u8], offset: usize) -> Option<Pubkey> {
99 data.get(offset..offset + 32)
100 .and_then(|slice| {
101 let key_bytes: [u8; 32] = slice.try_into().ok()?;
102 Some(Pubkey::new_from_array(key_bytes))
103 })
104}
105
106pub fn read_string(data: &[u8], offset: usize) -> Option<(String, usize)> {
108 let (string_ref, consumed) = read_string_ref(data, offset)?;
109 Some((string_ref.to_string(), consumed))
110}
111
112#[inline(always)] pub fn read_string_ref(data: &[u8], offset: usize) -> Option<(&str, usize)> {
125 if data.len() < offset + 4 {
126 return None;
127 }
128
129 let len = read_u32_le(data, offset)? as usize;
130 if data.len() < offset + 4 + len {
131 return None;
132 }
133
134 let string_bytes = &data[offset + 4..offset + 4 + len];
135 let string_ref = std::str::from_utf8(string_bytes).ok()?; Some((string_ref, 4 + len))
137}
138
139pub fn read_bool(data: &[u8], offset: usize) -> Option<bool> {
141 if data.len() <= offset {
142 return None;
143 }
144 Some(data[offset] == 1)
145}
146
147pub fn timestamp_to_microseconds(ts: &prost_types::Timestamp) -> i128 {
149 ts.seconds as i128 * 1_000_000 + (ts.nanos as i128 / 1_000)
151}
152
153pub fn create_metadata_simple(
155 signature: Signature,
156 slot: u64,
157 tx_index: u64,
158 block_time_us: Option<i64>,
159 program_id: Pubkey,
160 grpc_recv_us: i64,
161) -> EventMetadata {
162 EventMetadata {
163 signature,
164 slot,
165 tx_index,
166 block_time_us: block_time_us.unwrap_or(0),
167 grpc_recv_us,
168 }
169}
170
171pub fn create_metadata_default(
173 signature: Signature,
174 slot: u64,
175 tx_index: u64,
176 block_time_us: Option<i64>,
177) -> EventMetadata {
178 let current_time = now_us();
179 EventMetadata {
180 signature,
181 slot,
182 tx_index,
183 block_time_us: block_time_us.unwrap_or(0),
184 grpc_recv_us: current_time,
185 }
186}
187
188pub mod text_parser {
190
191 pub fn extract_number_from_text(text: &str, field: &str) -> Option<u64> {
193 if let Some(start) = text.find(&format!("{}:", field)) {
194 let after_colon = &text[start + field.len() + 1..];
195 if let Some(end) = after_colon.find(' ').or_else(|| after_colon.find(',')) {
196 after_colon[..end].trim().parse().ok()
197 } else {
198 after_colon.trim().parse().ok()
199 }
200 } else {
201 None
202 }
203 }
204
205 pub fn extract_text_field(text: &str, field: &str) -> Option<String> {
207 extract_text_field_ref(text, field).map(|s| s.to_string())
208 }
209
210 #[inline(always)] pub fn extract_text_field_ref<'a>(text: &'a str, field: &str) -> Option<&'a str> {
222 let start = text.find(&format!("{}:", field))?;
223 let after_colon = &text[start + field.len() + 1..];
224 if let Some(end) = after_colon.find(',').or_else(|| after_colon.find(' ')) {
225 Some(after_colon[..end].trim())
226 } else {
227 Some(after_colon.trim())
228 }
229 }
230
231 pub fn detect_trade_type(log: &str) -> Option<bool> {
233 if log.contains("buy") || log.contains("Buy") {
234 Some(true)
235 } else if log.contains("sell") || log.contains("Sell") {
236 Some(false)
237 } else {
238 None
239 }
240 }
241}