sol_parser_sdk/logs/
utils.rs1use solana_sdk::{pubkey::Pubkey, signature::Signature};
6use crate::core::events::EventMetadata;
7use base64::{Engine as _, engine::general_purpose};
8
9#[inline]
11pub fn extract_program_data(log: &str) -> Option<Vec<u8>> {
12 use memchr::memmem;
13
14 let log_bytes = log.as_bytes();
15 let pos = memmem::find(log_bytes, b"Program data: ")?;
16
17 let data_part = &log[pos + 14..];
18 general_purpose::STANDARD.decode(data_part.trim()).ok()
19}
20
21#[inline]
23pub fn extract_discriminator_fast(log: &str) -> Option<[u8; 8]> {
24 use memchr::memmem;
25
26 let log_bytes = log.as_bytes();
27 let pos = memmem::find(log_bytes, b"Program data: ")?;
28
29 let data_part = log[pos + 14..].trim();
30
31 if data_part.len() < 12 {
34 return None;
35 }
36
37 let prefix = &data_part[..16];
39
40 let mut buf = [0u8; 12];
41 let decoded_len = general_purpose::STANDARD.decode_slice(prefix.as_bytes(), &mut buf).ok()?;
42
43 if decoded_len >= 8 {
44 Some(buf[0..8].try_into().unwrap())
45 } else {
46 None
47 }
48}
49
50#[inline]
52pub fn read_u64_le(data: &[u8], offset: usize) -> Option<u64> {
53 data.get(offset..offset + 8)
54 .map(|slice| u64::from_le_bytes(slice.try_into().unwrap()))
55}
56
57#[inline]
59pub fn read_u32_le(data: &[u8], offset: usize) -> Option<u32> {
60 data.get(offset..offset + 4)
61 .map(|slice| u32::from_le_bytes(slice.try_into().unwrap()))
62}
63
64pub fn read_i64_le(data: &[u8], offset: usize) -> Option<i64> {
66 data.get(offset..offset + 8)
67 .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)
73 .map(|slice| i32::from_le_bytes(slice.try_into().unwrap()))
74}
75
76pub fn read_u128_le(data: &[u8], offset: usize) -> Option<u128> {
78 data.get(offset..offset + 16)
79 .map(|slice| u128::from_le_bytes(slice.try_into().unwrap()))
80}
81
82pub fn read_u16_le(data: &[u8], offset: usize) -> Option<u16> {
84 data.get(offset..offset + 2)
85 .map(|slice| u16::from_le_bytes(slice.try_into().unwrap()))
86}
87
88pub fn read_u8(data: &[u8], offset: usize) -> Option<u8> {
90 data.get(offset).copied()
91}
92
93#[inline]
95pub fn read_pubkey(data: &[u8], offset: usize) -> Option<Pubkey> {
96 data.get(offset..offset + 32)
97 .and_then(|slice| {
98 let key_bytes: [u8; 32] = slice.try_into().ok()?;
99 Some(Pubkey::new_from_array(key_bytes))
100 })
101}
102
103pub fn read_string(data: &[u8], offset: usize) -> Option<(String, usize)> {
105 if data.len() < offset + 4 {
106 return None;
107 }
108
109 let len = read_u32_le(data, offset)? as usize;
110 if data.len() < offset + 4 + len {
111 return None;
112 }
113
114 let string_bytes = &data[offset + 4..offset + 4 + len];
115 let string = std::str::from_utf8(string_bytes).ok()?.to_string();
117 Some((string, 4 + len))
118}
119
120pub fn read_bool(data: &[u8], offset: usize) -> Option<bool> {
122 if data.len() <= offset {
123 return None;
124 }
125 Some(data[offset] == 1)
126}
127
128pub fn create_metadata_simple(
130 signature: Signature,
131 slot: u64,
132 tx_index: u64,
133 block_time: Option<i64>,
134 program_id: Pubkey,
135 grpc_recv_us: i64,
136) -> EventMetadata {
137 EventMetadata {
138 signature,
139 slot,
140 tx_index,
141 block_time_us: block_time.unwrap_or(0) * 1_000_000,
142 grpc_recv_us,
143 }
144}
145
146pub fn create_metadata_default(
148 signature: Signature,
149 slot: u64,
150 tx_index: u64,
151 block_time: Option<i64>,
152) -> EventMetadata {
153 let current_time = unsafe {
155 let mut ts = libc::timespec {
156 tv_sec: 0,
157 tv_nsec: 0,
158 };
159 #[cfg(target_os = "linux")]
160 libc::clock_gettime(libc::CLOCK_REALTIME_COARSE, &mut ts);
161 #[cfg(not(target_os = "linux"))]
162 libc::clock_gettime(libc::CLOCK_REALTIME, &mut ts);
163 (ts.tv_sec as i64 * 1_000_000) + (ts.tv_nsec as i64 / 1_000)
164 };
165
166 EventMetadata {
167 signature,
168 slot,
169 tx_index,
170 block_time_us: block_time.unwrap_or(0) * 1_000_000,
171 grpc_recv_us: current_time,
172 }
173}
174
175pub mod text_parser {
177
178 pub fn extract_number_from_text(text: &str, field: &str) -> Option<u64> {
180 if let Some(start) = text.find(&format!("{}:", field)) {
181 let after_colon = &text[start + field.len() + 1..];
182 if let Some(end) = after_colon.find(' ').or_else(|| after_colon.find(',')) {
183 after_colon[..end].trim().parse().ok()
184 } else {
185 after_colon.trim().parse().ok()
186 }
187 } else {
188 None
189 }
190 }
191
192 pub fn extract_text_field(text: &str, field: &str) -> Option<String> {
194 if let Some(start) = text.find(&format!("{}:", field)) {
195 let after_colon = &text[start + field.len() + 1..];
196 if let Some(end) = after_colon.find(',').or_else(|| after_colon.find(' ')) {
197 Some(after_colon[..end].trim().to_string())
198 } else {
199 Some(after_colon.trim().to_string())
200 }
201 } else {
202 None
203 }
204 }
205
206 pub fn detect_trade_type(log: &str) -> Option<bool> {
208 if log.contains("buy") || log.contains("Buy") {
209 Some(true)
210 } else if log.contains("sell") || log.contains("Sell") {
211 Some(false)
212 } else {
213 None
214 }
215 }
216}