Skip to main content

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_us: Option<i64>, grpc_recv_us: i64) -> Option<DexEvent> {
31    parse_structured_log(log, signature, slot, tx_index, block_time_us, 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_us: 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_us, grpc_recv_us)
54        },
55        discriminators::INCREASE_LIQUIDITY => {
56            parse_increase_liquidity_event(data, signature, slot, tx_index, block_time_us, grpc_recv_us)
57        },
58        discriminators::DECREASE_LIQUIDITY => {
59            parse_decrease_liquidity_event(data, signature, slot, tx_index, block_time_us, grpc_recv_us)
60        },
61        discriminators::CREATE_POOL => {
62            parse_create_pool_event(data, signature, slot, tx_index, block_time_us, grpc_recv_us)
63        },
64        discriminators::COLLECT_FEE => {
65            parse_collect_fee_event(data, signature, slot, tx_index, block_time_us, 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_us: 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_us, 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_us: 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_us, pool_state, grpc_recv_us);
153
154    Some(DexEvent::RaydiumClmmIncreaseLiquidity(RaydiumClmmIncreaseLiquidityEvent {
155        metadata,
156        pool: pool_state,
157        position_nft_mint,
158        user: Pubkey::default(), // TODO: extract from instruction accounts
159        liquidity,
160        amount0_max,
161        amount1_max,
162    }))
163}
164
165/// 解析减少流动性事件
166fn parse_decrease_liquidity_event(
167    data: &[u8],
168    signature: Signature,
169    slot: u64,
170    tx_index: u64,
171    block_time_us: Option<i64>,
172    grpc_recv_us: i64,
173) -> Option<DexEvent> {
174    let mut offset = 0;
175
176    let pool_state = read_pubkey(data, offset)?;
177    offset += 32;
178
179    let position_nft_mint = read_pubkey(data, offset)?;
180    offset += 32;
181
182    let liquidity = read_u128_le(data, offset)?;
183    offset += 16;
184
185    let amount0_min = read_u64_le(data, offset)?;
186    offset += 8;
187
188    let amount1_min = read_u64_le(data, offset)?;
189
190    let metadata = create_metadata_simple(signature, slot, tx_index, block_time_us, pool_state, grpc_recv_us);
191
192    Some(DexEvent::RaydiumClmmDecreaseLiquidity(RaydiumClmmDecreaseLiquidityEvent {
193        metadata,
194        pool: pool_state,
195        position_nft_mint,
196        user: Pubkey::default(), // TODO: extract from instruction accounts
197        liquidity,
198        amount0_min,
199        amount1_min,
200    }))
201}
202
203/// 解析池创建事件
204fn parse_create_pool_event(
205    data: &[u8],
206    signature: Signature,
207    slot: u64,
208    tx_index: u64,
209    block_time_us: Option<i64>,
210    grpc_recv_us: i64,
211) -> Option<DexEvent> {
212    let mut offset = 0;
213
214    let pool_state = read_pubkey(data, offset)?;
215    offset += 32;
216
217    let creator = read_pubkey(data, offset)?;
218    offset += 32;
219
220    let sqrt_price_x64 = read_u128_le(data, offset)?;
221    offset += 16;
222
223    let open_time = read_u64_le(data, offset)?;
224
225    let metadata = create_metadata_simple(signature, slot, tx_index, block_time_us, pool_state, grpc_recv_us);
226
227    Some(DexEvent::RaydiumClmmCreatePool(RaydiumClmmCreatePoolEvent {
228        metadata,
229        pool: pool_state,
230        token_0_mint: Pubkey::default(), // TODO: extract from pool account data
231        token_1_mint: Pubkey::default(), // TODO: extract from pool account data
232        tick_spacing: 0, // TODO: extract from pool account data
233        fee_rate: 0,     // TODO: extract from pool account data
234        creator,
235        sqrt_price_x64,
236        open_time,
237    }))
238}
239
240/// 解析费用收集事件
241fn parse_collect_fee_event(
242    data: &[u8],
243    signature: Signature,
244    slot: u64,
245    tx_index: u64,
246    block_time_us: Option<i64>,
247    grpc_recv_us: i64,
248) -> Option<DexEvent> {
249    let mut offset = 0;
250
251    let pool_state = read_pubkey(data, offset)?;
252    offset += 32;
253
254    let position_nft_mint = read_pubkey(data, offset)?;
255    offset += 32;
256
257    let amount_0 = read_u64_le(data, offset)?;
258    offset += 8;
259
260    let amount_1 = read_u64_le(data, offset)?;
261
262    let metadata = create_metadata_simple(signature, slot, tx_index, block_time_us, pool_state, grpc_recv_us);
263
264    Some(DexEvent::RaydiumClmmCollectFee(RaydiumClmmCollectFeeEvent {
265        metadata,
266        pool_state,
267        position_nft_mint,
268        amount_0,
269        amount_1,
270    }))
271}
272
273/// 文本回退解析
274fn parse_text_log(
275    log: &str,
276    signature: Signature,
277    slot: u64,
278    tx_index: u64,
279    block_time_us: Option<i64>,
280    grpc_recv_us: i64,
281) -> Option<DexEvent> {
282    use super::utils::text_parser::*;
283
284    if log.contains("swap") || log.contains("Swap") {
285        return parse_swap_from_text(log, signature, slot, tx_index, block_time_us, grpc_recv_us);
286    }
287
288    if log.contains("increase") && log.contains("liquidity") {
289        return parse_increase_liquidity_from_text(log, signature, slot, tx_index, block_time_us, grpc_recv_us);
290    }
291
292    if log.contains("decrease") && log.contains("liquidity") {
293        return parse_decrease_liquidity_from_text(log, signature, slot, tx_index, block_time_us, grpc_recv_us);
294    }
295
296    if log.contains("create") && log.contains("pool") {
297        return parse_create_pool_from_text(log, signature, slot, tx_index, block_time_us, grpc_recv_us);
298    }
299
300    if log.contains("collect") && log.contains("fee") {
301        return parse_collect_fee_from_text(log, signature, slot, tx_index, block_time_us, grpc_recv_us);
302    }
303
304    None
305}
306
307/// 从文本解析交换事件
308fn parse_swap_from_text(
309    log: &str,
310    signature: Signature,
311    slot: u64,
312    tx_index: u64,
313    block_time_us: Option<i64>,
314    grpc_recv_us: i64,
315) -> Option<DexEvent> {
316    use super::utils::text_parser::*;
317
318    let metadata = create_metadata_simple(signature, slot, tx_index, block_time_us, Pubkey::default(), grpc_recv_us);
319    let is_base_input = detect_trade_type(log).unwrap_or(true);
320
321    Some(DexEvent::RaydiumClmmSwap(RaydiumClmmSwapEvent {
322        metadata,
323
324        // IDL SwapEvent 事件字段
325        pool_state: Pubkey::default(),
326        sender: Pubkey::default(),
327        token_account_0: Pubkey::default(),
328        token_account_1: Pubkey::default(),
329        amount_0: 0,
330        transfer_fee_0: 0,
331        amount_1: 0,
332        transfer_fee_1: 0,
333        zero_for_one: is_base_input,
334        sqrt_price_x64: 0,
335        // is_base_input,
336        liquidity: 0,
337        tick: 0,
338
339        // 暂时注释,以后会用,AI禁止改动
340        // 指令参数字段
341        // amount: extract_number_from_text(log, "amount").unwrap_or(1_000_000_000),
342        // other_amount_threshold: extract_number_from_text(log, "threshold").unwrap_or(950_000_000),
343        // sqrt_price_limit_x64: 0,
344    }))
345}
346
347/// 从文本解析增加流动性事件
348fn parse_increase_liquidity_from_text(
349    log: &str,
350    signature: Signature,
351    slot: u64,
352    tx_index: u64,
353    block_time_us: Option<i64>,
354    grpc_recv_us: i64,
355) -> Option<DexEvent> {
356    use super::utils::text_parser::*;
357
358    let metadata = create_metadata_simple(signature, slot, tx_index, block_time_us, Pubkey::default(), grpc_recv_us);
359
360    Some(DexEvent::RaydiumClmmIncreaseLiquidity(RaydiumClmmIncreaseLiquidityEvent {
361        metadata,
362        pool: Pubkey::default(),
363        position_nft_mint: Pubkey::default(),
364        user: Pubkey::default(),
365        liquidity: extract_number_from_text(log, "liquidity").unwrap_or(1_000_000) as u128,
366        amount0_max: extract_number_from_text(log, "amount0_max").unwrap_or(1_000_000),
367        amount1_max: extract_number_from_text(log, "amount1_max").unwrap_or(1_000_000),
368    }))
369}
370
371/// 从文本解析减少流动性事件
372fn parse_decrease_liquidity_from_text(
373    log: &str,
374    signature: Signature,
375    slot: u64,
376    tx_index: u64,
377    block_time_us: Option<i64>,
378    grpc_recv_us: i64,
379) -> Option<DexEvent> {
380    use super::utils::text_parser::*;
381
382    let metadata = create_metadata_simple(signature, slot, tx_index, block_time_us, Pubkey::default(), grpc_recv_us);
383
384    Some(DexEvent::RaydiumClmmDecreaseLiquidity(RaydiumClmmDecreaseLiquidityEvent {
385        metadata,
386        pool: Pubkey::default(),
387        position_nft_mint: Pubkey::default(),
388        user: Pubkey::default(),
389        liquidity: extract_number_from_text(log, "liquidity").unwrap_or(1_000_000) as u128,
390        amount0_min: extract_number_from_text(log, "amount0_min").unwrap_or(1_000_000),
391        amount1_min: extract_number_from_text(log, "amount1_min").unwrap_or(1_000_000),
392    }))
393}
394
395/// 从文本解析池创建事件
396fn parse_create_pool_from_text(
397    log: &str,
398    signature: Signature,
399    slot: u64,
400    tx_index: u64,
401    block_time_us: Option<i64>,
402    grpc_recv_us: i64,
403) -> Option<DexEvent> {
404    use super::utils::text_parser::*;
405
406    let metadata = create_metadata_simple(signature, slot, tx_index, block_time_us, Pubkey::default(), grpc_recv_us);
407
408    Some(DexEvent::RaydiumClmmCreatePool(RaydiumClmmCreatePoolEvent {
409        metadata,
410        pool: Pubkey::default(),
411        token_0_mint: Pubkey::default(),
412        token_1_mint: Pubkey::default(),
413        tick_spacing: 0,
414        fee_rate: 0,
415        creator: Pubkey::default(),
416        sqrt_price_x64: 0,
417        open_time: 0,
418    }))
419}
420
421/// 从文本解析费用收集事件
422fn parse_collect_fee_from_text(
423    log: &str,
424    signature: Signature,
425    slot: u64,
426    tx_index: u64,
427    block_time_us: Option<i64>,
428    grpc_recv_us: i64,
429) -> Option<DexEvent> {
430    use super::utils::text_parser::*;
431
432    let metadata = create_metadata_simple(signature, slot, tx_index, block_time_us, Pubkey::default(), grpc_recv_us);
433
434    Some(DexEvent::RaydiumClmmCollectFee(RaydiumClmmCollectFeeEvent {
435        metadata,
436        pool_state: Pubkey::default(),
437        position_nft_mint: Pubkey::default(),
438        amount_0: extract_number_from_text(log, "amount_0").unwrap_or(10_000),
439        amount_1: extract_number_from_text(log, "amount_1").unwrap_or(10_000),
440    }))
441}
442
443// ============================================================================
444// Public API for optimized parsing from pre-decoded data
445// These functions accept already-decoded data (without discriminator)
446// ============================================================================
447
448/// Parse Raydium CLMM Swap event from pre-decoded data
449#[inline(always)]
450pub fn parse_swap_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
451    let mut offset = 0;
452
453    let pool_state = read_pubkey(data, offset)?;
454    offset += 32;
455
456    let user = read_pubkey(data, offset)?;
457    offset += 32;
458
459    let _amount = read_u64_le(data, offset)?;
460    offset += 8;
461
462    let _other_amount_threshold = read_u64_le(data, offset)?;
463    offset += 8;
464
465    let sqrt_price_limit_x64 = read_u128_le(data, offset)?;
466    offset += 16;
467
468    let is_base_input = read_bool(data, offset)?;
469
470    Some(DexEvent::RaydiumClmmSwap(RaydiumClmmSwapEvent {
471        metadata,
472        pool_state,
473        sender: user,
474        token_account_0: Pubkey::default(),
475        token_account_1: Pubkey::default(),
476        amount_0: 0,
477        transfer_fee_0: 0,
478        amount_1: 0,
479        transfer_fee_1: 0,
480        zero_for_one: is_base_input,
481        sqrt_price_x64: sqrt_price_limit_x64,
482        liquidity: 0,
483        tick: 0,
484    }))
485}
486
487/// Parse Raydium CLMM IncreaseLiquidity event from pre-decoded data
488#[inline(always)]
489pub fn parse_increase_liquidity_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
490    let mut offset = 0;
491
492    let pool = read_pubkey(data, offset)?;
493    offset += 32;
494
495    let user = read_pubkey(data, offset)?;
496    offset += 32;
497
498    let liquidity = read_u128_le(data, offset)?;
499    offset += 16;
500
501    let amount0_max = read_u64_le(data, offset)?;
502    offset += 8;
503
504    let amount1_max = read_u64_le(data, offset)?;
505
506    Some(DexEvent::RaydiumClmmIncreaseLiquidity(RaydiumClmmIncreaseLiquidityEvent {
507        metadata,
508        pool,
509        position_nft_mint: Pubkey::default(), // Not available in this data format
510        user,
511        liquidity,
512        amount0_max,
513        amount1_max,
514    }))
515}
516
517/// Parse Raydium CLMM DecreaseLiquidity event from pre-decoded data
518#[inline(always)]
519pub fn parse_decrease_liquidity_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
520    let mut offset = 0;
521
522    let pool = read_pubkey(data, offset)?;
523    offset += 32;
524
525    let user = read_pubkey(data, offset)?;
526    offset += 32;
527
528    let liquidity = read_u128_le(data, offset)?;
529    offset += 16;
530
531    let amount0_min = read_u64_le(data, offset)?;
532    offset += 8;
533
534    let amount1_min = read_u64_le(data, offset)?;
535
536    Some(DexEvent::RaydiumClmmDecreaseLiquidity(RaydiumClmmDecreaseLiquidityEvent {
537        metadata,
538        pool,
539        position_nft_mint: Pubkey::default(), // Not available in this data format
540        user,
541        liquidity,
542        amount0_min,
543        amount1_min,
544    }))
545}
546
547/// Parse Raydium CLMM CreatePool event from pre-decoded data
548#[inline(always)]
549pub fn parse_create_pool_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
550    let mut offset = 0;
551
552    let pool = read_pubkey(data, offset)?;
553    offset += 32;
554
555    let creator = read_pubkey(data, offset)?;
556    offset += 32;
557
558    let sqrt_price_x64 = read_u128_le(data, offset)?;
559    offset += 16;
560
561    let open_time = read_u64_le(data, offset)?;
562
563    Some(DexEvent::RaydiumClmmCreatePool(RaydiumClmmCreatePoolEvent {
564        metadata,
565        pool,
566        token_0_mint: Pubkey::default(), // Not available in this data format
567        token_1_mint: Pubkey::default(), // Not available in this data format
568        tick_spacing: 0, // Not available in this data format
569        fee_rate: 0,     // Not available in this data format
570        creator,
571        sqrt_price_x64,
572        open_time,
573    }))
574}
575
576/// Parse Raydium CLMM CollectFee event from pre-decoded data
577#[inline(always)]
578pub fn parse_collect_fee_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
579    let mut offset = 0;
580
581    let pool_state = read_pubkey(data, offset)?;
582    offset += 32;
583
584    let position_nft_mint = read_pubkey(data, offset)?;
585    offset += 32;
586
587    let amount_0 = read_u64_le(data, offset)?;
588    offset += 8;
589
590    let amount_1 = read_u64_le(data, offset)?;
591
592    Some(DexEvent::RaydiumClmmCollectFee(RaydiumClmmCollectFeeEvent {
593        metadata,
594        pool_state,
595        position_nft_mint,
596        amount_0,
597        amount_1,
598    }))
599}