1use crate::core::events::DexEvent;
6use crate::grpc::types::{EventType, EventTypeFilter};
7use solana_sdk::signature::Signature;
8use memchr::memmem;
9use once_cell::sync::Lazy;
10use super::perf_hints::{likely, unlikely};
11
12static PUMPFUN_FINDER: Lazy<memmem::Finder> = Lazy::new(|| memmem::Finder::new(b"6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"));
14static RAYDIUM_AMM_FINDER: Lazy<memmem::Finder> = Lazy::new(|| memmem::Finder::new(b"675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8"));
15static RAYDIUM_CLMM_FINDER: Lazy<memmem::Finder> = Lazy::new(|| memmem::Finder::new(b"CAMMCzo5YL8w4VFF8KVHrK22GGUQpMdRBFSzKNT3t4ivN6"));
16static RAYDIUM_CPMM_FINDER: Lazy<memmem::Finder> = Lazy::new(|| memmem::Finder::new(b"CPMDWBwJDtYax9qKcQP3CtKz7tHjJsN3H8hGrYVD9mZD"));
17static BONK_FINDER: Lazy<memmem::Finder> = Lazy::new(|| memmem::Finder::new(b"Bxby5A7E8xPDGGc3FyJw7m5eK5aqNVLU83H2zLTQDH1b"));
18static PROGRAM_FINDER: Lazy<memmem::Finder> = Lazy::new(|| memmem::Finder::new(b"Program"));
19static PROGRAM_DATA_FINDER: Lazy<memmem::Finder> = Lazy::new(|| memmem::Finder::new(b"Program data: "));
20static PUMPFUN_CREATE_FINDER: Lazy<memmem::Finder> = Lazy::new(|| memmem::Finder::new(b"Program data: GB7IKAUcB3c"));
21static WHIRL_FINDER: Lazy<memmem::Finder> = Lazy::new(|| memmem::Finder::new(b"whirL"));
22static METEORA_FINDER: Lazy<memmem::Finder> = Lazy::new(|| memmem::Finder::new(b"meteora"));
23static METEORA_LB_FINDER: Lazy<memmem::Finder> = Lazy::new(|| memmem::Finder::new(b"LB"));
24static METEORA_DLMM_FINDER: Lazy<memmem::Finder> = Lazy::new(|| memmem::Finder::new(b"DLMM"));
25static PUMPSWAP_LOWER_FINDER: Lazy<memmem::Finder> = Lazy::new(|| memmem::Finder::new(b"pumpswap"));
26static PUMPSWAP_UPPER_FINDER: Lazy<memmem::Finder> = Lazy::new(|| memmem::Finder::new(b"PumpSwap"));
27
28pub mod program_id_strings {
30 pub const PUMPFUN_INVOKE: &str = "Program 6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P invoke";
31 pub const PUMPFUN_SUCCESS: &str = "Program 6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P success";
32 pub const PUMPFUN_ID: &str = "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P";
33
34 pub const BONK_INVOKE: &str = "Program Bxby5A7E8xPDGGc3FyJw7m5eK5aqNVLU83H2zLTQDH1b invoke";
35 pub const BONK_SUCCESS: &str = "Program Bxby5A7E8xPDGGc3FyJw7m5eK5aqNVLU83H2zLTQDH1b success";
36 pub const BONK_ID: &str = "Bxby5A7E8xPDGGc3FyJw7m5eK5aqNVLU83H2zLTQDH1b";
37
38 pub const RAYDIUM_CLMM_INVOKE: &str = "Program CAMMCzo5YL8w4VFF8KVHrK22GGUQpMdRBFSzKNT3t4ivN6 invoke";
39 pub const RAYDIUM_CLMM_SUCCESS: &str = "Program CAMMCzo5YL8w4VFF8KVHrK22GGUQpMdRBFSzKNT3t4ivN6 success";
40 pub const RAYDIUM_CLMM_ID: &str = "CAMMCzo5YL8w4VFF8KVHrK22GGUQpMdRBFSzKNT3t4ivN6";
41
42 pub const RAYDIUM_CPMM_INVOKE: &str = "Program CPMDWBwJDtYax9qKcQP3CtKz7tHjJsN3H8hGrYVD9mZD invoke";
43 pub const RAYDIUM_CPMM_SUCCESS: &str = "Program CPMDWBwJDtYax9qKcQP3CtKz7tHjJsN3H8hGrYVD9mZD success";
44 pub const RAYDIUM_CPMM_ID: &str = "CPMDWBwJDtYax9qKcQP3CtKz7tHjJsN3H8hGrYVD9mZD";
45
46 pub const RAYDIUM_AMM_V4_ID: &str = "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8";
47
48 pub const PROGRAM_DATA: &str = "Program data: ";
50 pub const PROGRAM_LOG: &str = "Program log: ";
51
52 pub const PUMPFUN_CREATE_DISCRIMINATOR: &str = "GB7IKAUcB3c"; }
55
56#[derive(Debug, Copy, Clone, PartialEq)]
58pub enum LogType {
59 PumpFun,
60 RaydiumLaunchpad,
61 PumpAmm,
62 RaydiumClmm,
63 RaydiumCpmm,
64 RaydiumAmm,
65 OrcaWhirlpool,
66 MeteoraAmm,
67 MeteoraDamm,
68 MeteoraDlmm,
69 Unknown,
70}
71
72#[inline(always)]
74pub fn detect_log_type(log: &str) -> LogType {
75 let log_bytes = log.as_bytes();
76
77 if log_bytes.len() < 20 {
79 return LogType::Unknown;
80 }
81
82 let has_program_data = PROGRAM_DATA_FINDER.find(log_bytes).is_some();
84
85 if unlikely(!has_program_data) {
87 return LogType::Unknown;
88 }
89
90 if likely(RAYDIUM_AMM_FINDER.find(log_bytes).is_some()) {
93 return LogType::RaydiumAmm;
94 }
95
96 if RAYDIUM_CLMM_FINDER.find(log_bytes).is_some() {
98 return LogType::RaydiumClmm;
99 }
100
101 if RAYDIUM_CPMM_FINDER.find(log_bytes).is_some() {
103 return LogType::RaydiumCpmm;
104 }
105
106 if BONK_FINDER.find(log_bytes).is_some() {
108 return LogType::RaydiumLaunchpad;
109 }
110
111 if WHIRL_FINDER.find(log_bytes).is_some() {
113 return LogType::OrcaWhirlpool;
114 }
115
116 if let Some(pos) = METEORA_FINDER.find(log_bytes) {
118 let rest = &log_bytes[pos..];
119 if METEORA_LB_FINDER.find(rest).is_some() {
120 return LogType::MeteoraDamm;
121 } else if METEORA_DLMM_FINDER.find(rest).is_some() {
122 return LogType::MeteoraDlmm;
123 } else {
124 return LogType::MeteoraAmm;
125 }
126 }
127
128 if PUMPSWAP_LOWER_FINDER.find(log_bytes).is_some() || PUMPSWAP_UPPER_FINDER.find(log_bytes).is_some() {
130 return LogType::PumpAmm;
131 }
132
133 if likely(PUMPFUN_FINDER.find(log_bytes).is_some()) {
136 return LogType::PumpFun;
137 }
138
139 if log.len() > 30 {
143 return LogType::PumpFun;
144 }
145
146 LogType::Unknown
147}
148
149#[inline(always)]
151pub fn parse_log_optimized(
152 log: &str,
153 signature: Signature,
154 slot: u64,
155 tx_index: u64,
156 block_time: Option<i64>,
157 grpc_recv_us: i64,
158 event_type_filter: Option<&EventTypeFilter>,
159 is_created_buy: bool,
160) -> Option<DexEvent> {
161 let log_type = detect_log_type(log);
163
164 if let Some(filter) = event_type_filter {
166 if let Some(ref include_only) = filter.include_only {
167 if likely(include_only.len() == 1 && include_only[0] == EventType::PumpFunTrade) {
169 if likely(log_type == LogType::PumpFun) {
170 return crate::logs::parse_pumpfun_trade(
172 log, signature, slot, tx_index, block_time, grpc_recv_us, is_created_buy
173 );
174 } else {
175 return None;
176 }
177 }
178
179 let should_parse = match log_type {
181 LogType::PumpFun => include_only.iter().any(|t| matches!(t,
182 EventType::PumpFunTrade | EventType::PumpFunCreate |
183 EventType::PumpFunComplete | EventType::PumpFunMigrate)),
184 LogType::RaydiumAmm => include_only.iter().any(|t| matches!(t,
185 EventType::RaydiumAmmV4Swap | EventType::RaydiumAmmV4Deposit |
186 EventType::RaydiumAmmV4Withdraw | EventType::RaydiumAmmV4Initialize2 |
187 EventType::RaydiumAmmV4WithdrawPnl)),
188 LogType::RaydiumClmm => include_only.iter().any(|t| matches!(t,
189 EventType::RaydiumClmmSwap | EventType::RaydiumClmmCreatePool |
190 EventType::RaydiumClmmOpenPosition | EventType::RaydiumClmmClosePosition |
191 EventType::RaydiumClmmIncreaseLiquidity | EventType::RaydiumClmmDecreaseLiquidity |
192 EventType::RaydiumClmmOpenPositionWithTokenExtNft | EventType::RaydiumClmmCollectFee)),
193 LogType::RaydiumCpmm => include_only.iter().any(|t| matches!(t,
194 EventType::RaydiumCpmmSwap | EventType::RaydiumCpmmDeposit |
195 EventType::RaydiumCpmmWithdraw | EventType::RaydiumCpmmInitialize)),
196 _ => true,
197 };
198
199 if unlikely(!should_parse) {
200 return None;
201 }
202 }
203 }
204
205 let event = match log_type {
207 LogType::PumpFun => crate::logs::parse_pumpfun_log(log, signature, slot, tx_index, block_time, grpc_recv_us, is_created_buy),
208 LogType::RaydiumLaunchpad => crate::logs::parse_raydium_launchpad_log(log, signature, slot, tx_index, block_time, grpc_recv_us),
209 LogType::PumpAmm => crate::logs::parse_pump_amm_log(log, signature, slot, tx_index, block_time, grpc_recv_us),
210 LogType::RaydiumClmm => crate::logs::parse_raydium_clmm_log(log, signature, slot, tx_index, block_time, grpc_recv_us),
211 LogType::RaydiumCpmm => crate::logs::parse_raydium_cpmm_log(log, signature, slot, tx_index, block_time, grpc_recv_us),
212 LogType::RaydiumAmm => crate::logs::parse_raydium_amm_log(log, signature, slot, tx_index, block_time, grpc_recv_us),
213 LogType::OrcaWhirlpool => crate::logs::parse_orca_whirlpool_log(log, signature, slot, tx_index, block_time, grpc_recv_us),
214 LogType::MeteoraAmm => crate::logs::parse_meteora_amm_log(log, signature, slot, tx_index, block_time, grpc_recv_us),
215 LogType::MeteoraDamm => crate::logs::parse_meteora_damm_log(log, signature, slot, tx_index, block_time, grpc_recv_us),
216 LogType::MeteoraDlmm => crate::logs::parse_meteora_dlmm_log(log, signature, slot, tx_index, block_time, grpc_recv_us),
217 LogType::Unknown => None,
218 };
219
220 if let Some(event) = event {
222 if let Some(filter) = event_type_filter {
223 let event_type = match &event {
224 DexEvent::PumpFunTrade(_) => EventType::PumpFunTrade,
225 DexEvent::PumpFunCreate(_) => EventType::PumpFunCreate,
226 DexEvent::PumpFunComplete(_) => EventType::PumpFunComplete,
227 DexEvent::PumpFunMigrate(_) => EventType::PumpFunMigrate,
228 DexEvent::RaydiumAmmV4Swap(_) => EventType::RaydiumAmmV4Swap,
229 DexEvent::RaydiumClmmSwap(_) => EventType::RaydiumClmmSwap,
230 DexEvent::RaydiumCpmmSwap(_) => EventType::RaydiumCpmmSwap,
231 _ => return Some(event),
232 };
233
234 if likely(filter.should_include(event_type)) {
235 return Some(event);
236 } else {
237 return None;
238 }
239 }
240 Some(event)
241 } else {
242 None
243 }
244}
245
246#[inline]
248pub fn detect_pumpfun_create(logs: &[String]) -> bool {
249 logs.iter().any(|log| {
250 PUMPFUN_CREATE_FINDER.find(log.as_bytes()).is_some()
251 })
252}
253
254#[cfg(test)]
256pub mod performance_tests {
257 use super::*;
258 use std::time::Instant;
259
260 pub fn benchmark_log_detection(log: &str, iterations: usize) -> (u128, u128) {
261 let start = Instant::now();
263 for _ in 0..iterations {
264 let _ = crate::logs::parse_log_unified(log, Default::default(), 0, None);
265 }
266 let old_time = start.elapsed().as_nanos();
267
268 let start = Instant::now();
270 for _ in 0..iterations {
271 let _ = parse_log_optimized(log, Default::default(), 0, None);
272 }
273 let new_time = start.elapsed().as_nanos();
274
275 (old_time, new_time)
276 }
277}