sol_parser_sdk/logs/
raydium_clmm.rs

1//! Raydium CLMM 日志解析器
2//!
3//! 使用 match discriminator 模式解析 Raydium CLMM 事件
4
5use solana_sdk::{pubkey::Pubkey, signature::Signature};
6use crate::core::events::*;
7use super::utils::*;
8
9/// Raydium CLMM discriminator 常量
10pub mod discriminators {
11    pub const SWAP: [u8; 8] = [248, 198, 158, 145, 225, 117, 135, 200];
12    pub const INCREASE_LIQUIDITY: [u8; 8] = [133, 29, 89, 223, 69, 238, 176, 10];
13    pub const DECREASE_LIQUIDITY: [u8; 8] = [160, 38, 208, 111, 104, 91, 44, 1];
14    pub const CREATE_POOL: [u8; 8] = [233, 146, 209, 142, 207, 104, 64, 188];
15    pub const COLLECT_FEE: [u8; 8] = [164, 152, 207, 99, 187, 104, 171, 119];
16}
17
18/// Raydium CLMM 程序 ID
19pub const PROGRAM_ID: &str = "CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK";
20
21/// 检查日志是否来自 Raydium CLMM 程序
22pub fn is_raydium_clmm_log(log: &str) -> bool {
23    log.contains(&format!("Program {} invoke", PROGRAM_ID)) ||
24    log.contains(&format!("Program {} success", PROGRAM_ID)) ||
25    log.contains("raydium") || log.contains("Raydium")
26}
27
28/// 主要的 Raydium CLMM 日志解析函数
29#[inline]
30pub fn parse_log(log: &str, signature: Signature, slot: u64, tx_index: u64, block_time: Option<i64>, grpc_recv_us: i64) -> Option<DexEvent> {
31    parse_structured_log(log, signature, slot, tx_index, block_time, grpc_recv_us)
32}
33
34/// 结构化日志解析(基于 Program data)
35fn parse_structured_log(
36    log: &str,
37    signature: Signature,
38    slot: u64,
39    tx_index: u64,
40    block_time: Option<i64>,
41    grpc_recv_us: i64,
42) -> Option<DexEvent> {
43    let program_data = extract_program_data(log)?;
44    if program_data.len() < 8 {
45        return None;
46    }
47
48    let discriminator: [u8; 8] = program_data[0..8].try_into().ok()?;
49    let data = &program_data[8..];
50
51    match discriminator {
52        discriminators::SWAP => {
53            parse_swap_event(data, signature, slot, tx_index, block_time, grpc_recv_us)
54        },
55        discriminators::INCREASE_LIQUIDITY => {
56            parse_increase_liquidity_event(data, signature, slot, tx_index, block_time, grpc_recv_us)
57        },
58        discriminators::DECREASE_LIQUIDITY => {
59            parse_decrease_liquidity_event(data, signature, slot, tx_index, block_time, grpc_recv_us)
60        },
61        discriminators::CREATE_POOL => {
62            parse_create_pool_event(data, signature, slot, tx_index, block_time, grpc_recv_us)
63        },
64        discriminators::COLLECT_FEE => {
65            parse_collect_fee_event(data, signature, slot, tx_index, block_time, grpc_recv_us)
66        },
67        _ => None,
68    }
69}
70
71/// 解析交换事件
72fn parse_swap_event(
73    data: &[u8],
74    signature: Signature,
75    slot: u64,
76    tx_index: u64,
77    block_time: Option<i64>,
78    grpc_recv_us: i64,
79) -> Option<DexEvent> {
80    let mut offset = 0;
81
82    let pool_state = read_pubkey(data, offset)?;
83    offset += 32;
84
85    let user = read_pubkey(data, offset)?;
86    offset += 32;
87
88    let amount = read_u64_le(data, offset)?;
89    offset += 8;
90
91    let other_amount_threshold = read_u64_le(data, offset)?;
92    offset += 8;
93
94    let sqrt_price_limit_x64 = read_u128_le(data, offset)?;
95    offset += 16;
96
97    let is_base_input = read_bool(data, offset)?;
98
99    let metadata = create_metadata_simple(signature, slot, tx_index, block_time, pool_state, grpc_recv_us);
100
101    Some(DexEvent::RaydiumClmmSwap(RaydiumClmmSwapEvent {
102        metadata,
103
104        // IDL SwapEvent 事件字段
105        pool_state,
106        sender: user,
107        token_account_0: Pubkey::default(),
108        token_account_1: Pubkey::default(),
109        amount_0: 0, // 从日志填充
110        transfer_fee_0: 0, // 从日志填充
111        amount_1: 0, // 从日志填充
112        transfer_fee_1: 0, // 从日志填充
113        zero_for_one: is_base_input,
114        sqrt_price_x64: sqrt_price_limit_x64,
115        // is_base_input,
116        liquidity: 0, // 从日志填充
117        tick: 0, // 从日志填充
118
119        // 暂时注释,以后会用,AI禁止改动
120        // 指令参数字段
121        // amount,
122        // other_amount_threshold,
123        // sqrt_price_limit_x64,
124    }))
125}
126
127/// 解析增加流动性事件
128fn parse_increase_liquidity_event(
129    data: &[u8],
130    signature: Signature,
131    slot: u64,
132    tx_index: u64,
133    block_time: Option<i64>,
134    grpc_recv_us: i64,
135) -> Option<DexEvent> {
136    let mut offset = 0;
137
138    let pool_state = read_pubkey(data, offset)?;
139    offset += 32;
140
141    let position_nft_mint = read_pubkey(data, offset)?;
142    offset += 32;
143
144    let liquidity = read_u128_le(data, offset)?;
145    offset += 16;
146
147    let amount0_max = read_u64_le(data, offset)?;
148    offset += 8;
149
150    let amount1_max = read_u64_le(data, offset)?;
151
152    let metadata = create_metadata_simple(signature, slot, tx_index, block_time, pool_state, grpc_recv_us);
153
154    Some(DexEvent::RaydiumClmmIncreaseLiquidity(RaydiumClmmIncreaseLiquidityEvent {
155        metadata,
156        pool: pool_state,
157        user: position_nft_mint, // Use NFT mint as user for now
158        liquidity,
159        amount0_max,
160        amount1_max,
161    }))
162}
163
164/// 解析减少流动性事件
165fn parse_decrease_liquidity_event(
166    data: &[u8],
167    signature: Signature,
168    slot: u64,
169    tx_index: u64,
170    block_time: Option<i64>,
171    grpc_recv_us: i64,
172) -> Option<DexEvent> {
173    let mut offset = 0;
174
175    let pool_state = read_pubkey(data, offset)?;
176    offset += 32;
177
178    let position_nft_mint = read_pubkey(data, offset)?;
179    offset += 32;
180
181    let liquidity = read_u128_le(data, offset)?;
182    offset += 16;
183
184    let amount0_min = read_u64_le(data, offset)?;
185    offset += 8;
186
187    let amount1_min = read_u64_le(data, offset)?;
188
189    let metadata = create_metadata_simple(signature, slot, tx_index, block_time, pool_state, grpc_recv_us);
190
191    Some(DexEvent::RaydiumClmmDecreaseLiquidity(RaydiumClmmDecreaseLiquidityEvent {
192        metadata,
193        pool: pool_state,
194        user: position_nft_mint, // Use NFT mint as user for now
195        liquidity,
196        amount0_min,
197        amount1_min,
198    }))
199}
200
201/// 解析池创建事件
202fn parse_create_pool_event(
203    data: &[u8],
204    signature: Signature,
205    slot: u64,
206    tx_index: u64,
207    block_time: Option<i64>,
208    grpc_recv_us: i64,
209) -> Option<DexEvent> {
210    let mut offset = 0;
211
212    let pool_state = read_pubkey(data, offset)?;
213    offset += 32;
214
215    let creator = read_pubkey(data, offset)?;
216    offset += 32;
217
218    let sqrt_price_x64 = read_u128_le(data, offset)?;
219    offset += 16;
220
221    let open_time = read_u64_le(data, offset)?;
222
223    let metadata = create_metadata_simple(signature, slot, tx_index, block_time, pool_state, grpc_recv_us);
224
225    Some(DexEvent::RaydiumClmmCreatePool(RaydiumClmmCreatePoolEvent {
226        metadata,
227        pool: pool_state,
228        creator,
229        sqrt_price_x64,
230        open_time,
231    }))
232}
233
234/// 解析费用收集事件
235fn parse_collect_fee_event(
236    data: &[u8],
237    signature: Signature,
238    slot: u64,
239    tx_index: u64,
240    block_time: Option<i64>,
241    grpc_recv_us: i64,
242) -> Option<DexEvent> {
243    let mut offset = 0;
244
245    let pool_state = read_pubkey(data, offset)?;
246    offset += 32;
247
248    let position_nft_mint = read_pubkey(data, offset)?;
249    offset += 32;
250
251    let amount_0 = read_u64_le(data, offset)?;
252    offset += 8;
253
254    let amount_1 = read_u64_le(data, offset)?;
255
256    let metadata = create_metadata_simple(signature, slot, tx_index, block_time, pool_state, grpc_recv_us);
257
258    Some(DexEvent::RaydiumClmmCollectFee(RaydiumClmmCollectFeeEvent {
259        metadata,
260        pool_state,
261        position_nft_mint,
262        amount_0,
263        amount_1,
264    }))
265}
266
267/// 文本回退解析
268fn parse_text_log(
269    log: &str,
270    signature: Signature,
271    slot: u64,
272    tx_index: u64,
273    block_time: Option<i64>,
274    grpc_recv_us: i64,
275) -> Option<DexEvent> {
276    use super::utils::text_parser::*;
277
278    if log.contains("swap") || log.contains("Swap") {
279        return parse_swap_from_text(log, signature, slot, tx_index, block_time, grpc_recv_us);
280    }
281
282    if log.contains("increase") && log.contains("liquidity") {
283        return parse_increase_liquidity_from_text(log, signature, slot, tx_index, block_time, grpc_recv_us);
284    }
285
286    if log.contains("decrease") && log.contains("liquidity") {
287        return parse_decrease_liquidity_from_text(log, signature, slot, tx_index, block_time, grpc_recv_us);
288    }
289
290    if log.contains("create") && log.contains("pool") {
291        return parse_create_pool_from_text(log, signature, slot, tx_index, block_time, grpc_recv_us);
292    }
293
294    if log.contains("collect") && log.contains("fee") {
295        return parse_collect_fee_from_text(log, signature, slot, tx_index, block_time, grpc_recv_us);
296    }
297
298    None
299}
300
301/// 从文本解析交换事件
302fn parse_swap_from_text(
303    log: &str,
304    signature: Signature,
305    slot: u64,
306    tx_index: u64,
307    block_time: Option<i64>,
308    grpc_recv_us: i64,
309) -> Option<DexEvent> {
310    use super::utils::text_parser::*;
311
312    let metadata = create_metadata_simple(signature, slot, tx_index, block_time, Pubkey::default(), grpc_recv_us);
313    let is_base_input = detect_trade_type(log).unwrap_or(true);
314
315    Some(DexEvent::RaydiumClmmSwap(RaydiumClmmSwapEvent {
316        metadata,
317
318        // IDL SwapEvent 事件字段
319        pool_state: Pubkey::default(),
320        sender: Pubkey::default(),
321        token_account_0: Pubkey::default(),
322        token_account_1: Pubkey::default(),
323        amount_0: 0,
324        transfer_fee_0: 0,
325        amount_1: 0,
326        transfer_fee_1: 0,
327        zero_for_one: is_base_input,
328        sqrt_price_x64: 0,
329        // is_base_input,
330        liquidity: 0,
331        tick: 0,
332
333        // 暂时注释,以后会用,AI禁止改动
334        // 指令参数字段
335        // amount: extract_number_from_text(log, "amount").unwrap_or(1_000_000_000),
336        // other_amount_threshold: extract_number_from_text(log, "threshold").unwrap_or(950_000_000),
337        // sqrt_price_limit_x64: 0,
338    }))
339}
340
341/// 从文本解析增加流动性事件
342fn parse_increase_liquidity_from_text(
343    log: &str,
344    signature: Signature,
345    slot: u64,
346    tx_index: u64,
347    block_time: Option<i64>,
348    grpc_recv_us: i64,
349) -> Option<DexEvent> {
350    use super::utils::text_parser::*;
351
352    let metadata = create_metadata_simple(signature, slot, tx_index, block_time, Pubkey::default(), grpc_recv_us);
353
354    Some(DexEvent::RaydiumClmmIncreaseLiquidity(RaydiumClmmIncreaseLiquidityEvent {
355        metadata,
356        pool: Pubkey::default(),
357        user: Pubkey::default(),
358        liquidity: extract_number_from_text(log, "liquidity").unwrap_or(1_000_000) as u128,
359        amount0_max: extract_number_from_text(log, "amount0_max").unwrap_or(1_000_000),
360        amount1_max: extract_number_from_text(log, "amount1_max").unwrap_or(1_000_000),
361    }))
362}
363
364/// 从文本解析减少流动性事件
365fn parse_decrease_liquidity_from_text(
366    log: &str,
367    signature: Signature,
368    slot: u64,
369    tx_index: u64,
370    block_time: Option<i64>,
371    grpc_recv_us: i64,
372) -> Option<DexEvent> {
373    use super::utils::text_parser::*;
374
375    let metadata = create_metadata_simple(signature, slot, tx_index, block_time, Pubkey::default(), grpc_recv_us);
376
377    Some(DexEvent::RaydiumClmmDecreaseLiquidity(RaydiumClmmDecreaseLiquidityEvent {
378        metadata,
379        pool: Pubkey::default(),
380        user: Pubkey::default(),
381        liquidity: extract_number_from_text(log, "liquidity").unwrap_or(1_000_000) as u128,
382        amount0_min: extract_number_from_text(log, "amount0_min").unwrap_or(1_000_000),
383        amount1_min: extract_number_from_text(log, "amount1_min").unwrap_or(1_000_000),
384    }))
385}
386
387/// 从文本解析池创建事件
388fn parse_create_pool_from_text(
389    log: &str,
390    signature: Signature,
391    slot: u64,
392    tx_index: u64,
393    block_time: Option<i64>,
394    grpc_recv_us: i64,
395) -> Option<DexEvent> {
396    use super::utils::text_parser::*;
397
398    let metadata = create_metadata_simple(signature, slot, tx_index, block_time, Pubkey::default(), grpc_recv_us);
399
400    Some(DexEvent::RaydiumClmmCreatePool(RaydiumClmmCreatePoolEvent {
401        metadata,
402        pool: Pubkey::default(),
403        creator: Pubkey::default(),
404        sqrt_price_x64: 0,
405        open_time: block_time.unwrap_or(0) as u64,
406    }))
407}
408
409/// 从文本解析费用收集事件
410fn parse_collect_fee_from_text(
411    log: &str,
412    signature: Signature,
413    slot: u64,
414    tx_index: u64,
415    block_time: Option<i64>,
416    grpc_recv_us: i64,
417) -> Option<DexEvent> {
418    use super::utils::text_parser::*;
419
420    let metadata = create_metadata_simple(signature, slot, tx_index, block_time, Pubkey::default(), grpc_recv_us);
421
422    Some(DexEvent::RaydiumClmmCollectFee(RaydiumClmmCollectFeeEvent {
423        metadata,
424        pool_state: Pubkey::default(),
425        position_nft_mint: Pubkey::default(),
426        amount_0: extract_number_from_text(log, "amount_0").unwrap_or(10_000),
427        amount_1: extract_number_from_text(log, "amount_1").unwrap_or(10_000),
428    }))
429}