Skip to main content

sol_parser_sdk/logs/
raydium_clmm.rs

1//! Raydium CLMM 日志解析器
2//!
3//! 使用 match discriminator 模式解析 Raydium CLMM 事件
4
5use super::utils::*;
6use crate::core::events::*;
7use solana_sdk::{pubkey::Pubkey, signature::Signature};
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")
26        || log.contains("Raydium")
27}
28
29/// 主要的 Raydium CLMM 日志解析函数
30#[inline]
31pub fn parse_log(
32    log: &str,
33    signature: Signature,
34    slot: u64,
35    tx_index: u64,
36    block_time_us: Option<i64>,
37    grpc_recv_us: i64,
38) -> Option<DexEvent> {
39    parse_structured_log(log, signature, slot, tx_index, block_time_us, grpc_recv_us)
40}
41
42/// 结构化日志解析(基于 Program data)
43fn parse_structured_log(
44    log: &str,
45    signature: Signature,
46    slot: u64,
47    tx_index: u64,
48    block_time_us: Option<i64>,
49    grpc_recv_us: i64,
50) -> Option<DexEvent> {
51    let program_data = extract_program_data(log)?;
52    if program_data.len() < 8 {
53        return None;
54    }
55
56    let discriminator: [u8; 8] = program_data[0..8].try_into().ok()?;
57    let data = &program_data[8..];
58
59    match discriminator {
60        discriminators::SWAP => {
61            parse_swap_event(data, signature, slot, tx_index, block_time_us, grpc_recv_us)
62        }
63        discriminators::INCREASE_LIQUIDITY => parse_increase_liquidity_event(
64            data,
65            signature,
66            slot,
67            tx_index,
68            block_time_us,
69            grpc_recv_us,
70        ),
71        discriminators::DECREASE_LIQUIDITY => parse_decrease_liquidity_event(
72            data,
73            signature,
74            slot,
75            tx_index,
76            block_time_us,
77            grpc_recv_us,
78        ),
79        discriminators::CREATE_POOL => {
80            parse_create_pool_event(data, signature, slot, tx_index, block_time_us, grpc_recv_us)
81        }
82        discriminators::COLLECT_FEE => {
83            parse_collect_fee_event(data, signature, slot, tx_index, block_time_us, grpc_recv_us)
84        }
85        _ => None,
86    }
87}
88
89/// 解析交换事件
90fn parse_swap_event(
91    data: &[u8],
92    signature: Signature,
93    slot: u64,
94    tx_index: u64,
95    block_time_us: Option<i64>,
96    grpc_recv_us: i64,
97) -> Option<DexEvent> {
98    let mut offset = 0;
99
100    let pool_state = read_pubkey(data, offset)?;
101    offset += 32;
102
103    let user = read_pubkey(data, offset)?;
104    offset += 32;
105
106    let amount = read_u64_le(data, offset)?;
107    offset += 8;
108
109    let other_amount_threshold = read_u64_le(data, offset)?;
110    offset += 8;
111
112    let sqrt_price_limit_x64 = read_u128_le(data, offset)?;
113    offset += 16;
114
115    let is_base_input = read_bool(data, offset)?;
116
117    let metadata =
118        create_metadata_simple(signature, slot, tx_index, block_time_us, pool_state, grpc_recv_us);
119
120    Some(DexEvent::RaydiumClmmSwap(RaydiumClmmSwapEvent {
121        metadata,
122
123        // IDL SwapEvent 事件字段
124        pool_state,
125        sender: user,
126        token_account_0: Pubkey::default(),
127        token_account_1: Pubkey::default(),
128        amount_0: 0,       // 从日志填充
129        transfer_fee_0: 0, // 从日志填充
130        amount_1: 0,       // 从日志填充
131        transfer_fee_1: 0, // 从日志填充
132        zero_for_one: is_base_input,
133        sqrt_price_x64: sqrt_price_limit_x64,
134        // is_base_input,
135        liquidity: 0, // 从日志填充
136        tick: 0,      // 从日志填充
137
138                      // 暂时注释,以后会用,AI禁止改动
139                      // 指令参数字段
140                      // amount,
141                      // other_amount_threshold,
142                      // sqrt_price_limit_x64,
143    }))
144}
145
146/// 解析增加流动性事件
147fn parse_increase_liquidity_event(
148    data: &[u8],
149    signature: Signature,
150    slot: u64,
151    tx_index: u64,
152    block_time_us: Option<i64>,
153    grpc_recv_us: i64,
154) -> Option<DexEvent> {
155    let mut offset = 0;
156
157    let pool_state = read_pubkey(data, offset)?;
158    offset += 32;
159
160    let position_nft_mint = read_pubkey(data, offset)?;
161    offset += 32;
162
163    let liquidity = read_u128_le(data, offset)?;
164    offset += 16;
165
166    let amount0_max = read_u64_le(data, offset)?;
167    offset += 8;
168
169    let amount1_max = read_u64_le(data, offset)?;
170
171    let metadata =
172        create_metadata_simple(signature, slot, tx_index, block_time_us, pool_state, grpc_recv_us);
173
174    Some(DexEvent::RaydiumClmmIncreaseLiquidity(RaydiumClmmIncreaseLiquidityEvent {
175        metadata,
176        pool: pool_state,
177        position_nft_mint,
178        user: Pubkey::default(), // TODO: extract from instruction accounts
179        liquidity,
180        amount0_max,
181        amount1_max,
182    }))
183}
184
185/// 解析减少流动性事件
186fn parse_decrease_liquidity_event(
187    data: &[u8],
188    signature: Signature,
189    slot: u64,
190    tx_index: u64,
191    block_time_us: Option<i64>,
192    grpc_recv_us: i64,
193) -> Option<DexEvent> {
194    let mut offset = 0;
195
196    let pool_state = read_pubkey(data, offset)?;
197    offset += 32;
198
199    let position_nft_mint = read_pubkey(data, offset)?;
200    offset += 32;
201
202    let liquidity = read_u128_le(data, offset)?;
203    offset += 16;
204
205    let amount0_min = read_u64_le(data, offset)?;
206    offset += 8;
207
208    let amount1_min = read_u64_le(data, offset)?;
209
210    let metadata =
211        create_metadata_simple(signature, slot, tx_index, block_time_us, pool_state, grpc_recv_us);
212
213    Some(DexEvent::RaydiumClmmDecreaseLiquidity(RaydiumClmmDecreaseLiquidityEvent {
214        metadata,
215        pool: pool_state,
216        position_nft_mint,
217        user: Pubkey::default(), // TODO: extract from instruction accounts
218        liquidity,
219        amount0_min,
220        amount1_min,
221    }))
222}
223
224/// 解析池创建事件
225fn parse_create_pool_event(
226    data: &[u8],
227    signature: Signature,
228    slot: u64,
229    tx_index: u64,
230    block_time_us: Option<i64>,
231    grpc_recv_us: i64,
232) -> Option<DexEvent> {
233    let mut offset = 0;
234
235    let pool_state = read_pubkey(data, offset)?;
236    offset += 32;
237
238    let creator = read_pubkey(data, offset)?;
239    offset += 32;
240
241    let sqrt_price_x64 = read_u128_le(data, offset)?;
242    offset += 16;
243
244    let open_time = read_u64_le(data, offset)?;
245
246    let metadata =
247        create_metadata_simple(signature, slot, tx_index, block_time_us, pool_state, grpc_recv_us);
248
249    Some(DexEvent::RaydiumClmmCreatePool(RaydiumClmmCreatePoolEvent {
250        metadata,
251        pool: pool_state,
252        token_0_mint: Pubkey::default(), // TODO: extract from pool account data
253        token_1_mint: Pubkey::default(), // TODO: extract from pool account data
254        tick_spacing: 0,                 // TODO: extract from pool account data
255        fee_rate: 0,                     // TODO: extract from pool account data
256        creator,
257        sqrt_price_x64,
258        open_time,
259    }))
260}
261
262/// 解析费用收集事件
263fn parse_collect_fee_event(
264    data: &[u8],
265    signature: Signature,
266    slot: u64,
267    tx_index: u64,
268    block_time_us: Option<i64>,
269    grpc_recv_us: i64,
270) -> Option<DexEvent> {
271    let mut offset = 0;
272
273    let pool_state = read_pubkey(data, offset)?;
274    offset += 32;
275
276    let position_nft_mint = read_pubkey(data, offset)?;
277    offset += 32;
278
279    let amount_0 = read_u64_le(data, offset)?;
280    offset += 8;
281
282    let amount_1 = read_u64_le(data, offset)?;
283
284    let metadata =
285        create_metadata_simple(signature, slot, tx_index, block_time_us, pool_state, grpc_recv_us);
286
287    Some(DexEvent::RaydiumClmmCollectFee(RaydiumClmmCollectFeeEvent {
288        metadata,
289        pool_state,
290        position_nft_mint,
291        amount_0,
292        amount_1,
293    }))
294}
295
296/// 文本回退解析
297fn parse_text_log(
298    log: &str,
299    signature: Signature,
300    slot: u64,
301    tx_index: u64,
302    block_time_us: Option<i64>,
303    grpc_recv_us: i64,
304) -> Option<DexEvent> {
305    use super::utils::text_parser::*;
306
307    if log.contains("swap") || log.contains("Swap") {
308        return parse_swap_from_text(log, signature, slot, tx_index, block_time_us, grpc_recv_us);
309    }
310
311    if log.contains("increase") && log.contains("liquidity") {
312        return parse_increase_liquidity_from_text(
313            log,
314            signature,
315            slot,
316            tx_index,
317            block_time_us,
318            grpc_recv_us,
319        );
320    }
321
322    if log.contains("decrease") && log.contains("liquidity") {
323        return parse_decrease_liquidity_from_text(
324            log,
325            signature,
326            slot,
327            tx_index,
328            block_time_us,
329            grpc_recv_us,
330        );
331    }
332
333    if log.contains("create") && log.contains("pool") {
334        return parse_create_pool_from_text(
335            log,
336            signature,
337            slot,
338            tx_index,
339            block_time_us,
340            grpc_recv_us,
341        );
342    }
343
344    if log.contains("collect") && log.contains("fee") {
345        return parse_collect_fee_from_text(
346            log,
347            signature,
348            slot,
349            tx_index,
350            block_time_us,
351            grpc_recv_us,
352        );
353    }
354
355    None
356}
357
358/// 从文本解析交换事件
359fn parse_swap_from_text(
360    log: &str,
361    signature: Signature,
362    slot: u64,
363    tx_index: u64,
364    block_time_us: Option<i64>,
365    grpc_recv_us: i64,
366) -> Option<DexEvent> {
367    use super::utils::text_parser::*;
368
369    let metadata = create_metadata_simple(
370        signature,
371        slot,
372        tx_index,
373        block_time_us,
374        Pubkey::default(),
375        grpc_recv_us,
376    );
377    let is_base_input = detect_trade_type(log).unwrap_or(true);
378
379    Some(DexEvent::RaydiumClmmSwap(RaydiumClmmSwapEvent {
380        metadata,
381
382        // IDL SwapEvent 事件字段
383        pool_state: Pubkey::default(),
384        sender: Pubkey::default(),
385        token_account_0: Pubkey::default(),
386        token_account_1: Pubkey::default(),
387        amount_0: 0,
388        transfer_fee_0: 0,
389        amount_1: 0,
390        transfer_fee_1: 0,
391        zero_for_one: is_base_input,
392        sqrt_price_x64: 0,
393        // is_base_input,
394        liquidity: 0,
395        tick: 0,
396        // 暂时注释,以后会用,AI禁止改动
397        // 指令参数字段
398        // amount: extract_number_from_text(log, "amount").unwrap_or(1_000_000_000),
399        // other_amount_threshold: extract_number_from_text(log, "threshold").unwrap_or(950_000_000),
400        // sqrt_price_limit_x64: 0,
401    }))
402}
403
404/// 从文本解析增加流动性事件
405fn parse_increase_liquidity_from_text(
406    log: &str,
407    signature: Signature,
408    slot: u64,
409    tx_index: u64,
410    block_time_us: Option<i64>,
411    grpc_recv_us: i64,
412) -> Option<DexEvent> {
413    use super::utils::text_parser::*;
414
415    let metadata = create_metadata_simple(
416        signature,
417        slot,
418        tx_index,
419        block_time_us,
420        Pubkey::default(),
421        grpc_recv_us,
422    );
423
424    Some(DexEvent::RaydiumClmmIncreaseLiquidity(RaydiumClmmIncreaseLiquidityEvent {
425        metadata,
426        pool: Pubkey::default(),
427        position_nft_mint: Pubkey::default(),
428        user: Pubkey::default(),
429        liquidity: extract_number_from_text(log, "liquidity").unwrap_or(1_000_000) as u128,
430        amount0_max: extract_number_from_text(log, "amount0_max").unwrap_or(1_000_000),
431        amount1_max: extract_number_from_text(log, "amount1_max").unwrap_or(1_000_000),
432    }))
433}
434
435/// 从文本解析减少流动性事件
436fn parse_decrease_liquidity_from_text(
437    log: &str,
438    signature: Signature,
439    slot: u64,
440    tx_index: u64,
441    block_time_us: Option<i64>,
442    grpc_recv_us: i64,
443) -> Option<DexEvent> {
444    use super::utils::text_parser::*;
445
446    let metadata = create_metadata_simple(
447        signature,
448        slot,
449        tx_index,
450        block_time_us,
451        Pubkey::default(),
452        grpc_recv_us,
453    );
454
455    Some(DexEvent::RaydiumClmmDecreaseLiquidity(RaydiumClmmDecreaseLiquidityEvent {
456        metadata,
457        pool: Pubkey::default(),
458        position_nft_mint: Pubkey::default(),
459        user: Pubkey::default(),
460        liquidity: extract_number_from_text(log, "liquidity").unwrap_or(1_000_000) as u128,
461        amount0_min: extract_number_from_text(log, "amount0_min").unwrap_or(1_000_000),
462        amount1_min: extract_number_from_text(log, "amount1_min").unwrap_or(1_000_000),
463    }))
464}
465
466/// 从文本解析池创建事件
467fn parse_create_pool_from_text(
468    log: &str,
469    signature: Signature,
470    slot: u64,
471    tx_index: u64,
472    block_time_us: Option<i64>,
473    grpc_recv_us: i64,
474) -> Option<DexEvent> {
475    use super::utils::text_parser::*;
476
477    let metadata = create_metadata_simple(
478        signature,
479        slot,
480        tx_index,
481        block_time_us,
482        Pubkey::default(),
483        grpc_recv_us,
484    );
485
486    Some(DexEvent::RaydiumClmmCreatePool(RaydiumClmmCreatePoolEvent {
487        metadata,
488        pool: Pubkey::default(),
489        token_0_mint: Pubkey::default(),
490        token_1_mint: Pubkey::default(),
491        tick_spacing: 0,
492        fee_rate: 0,
493        creator: Pubkey::default(),
494        sqrt_price_x64: 0,
495        open_time: 0,
496    }))
497}
498
499/// 从文本解析费用收集事件
500fn parse_collect_fee_from_text(
501    log: &str,
502    signature: Signature,
503    slot: u64,
504    tx_index: u64,
505    block_time_us: Option<i64>,
506    grpc_recv_us: i64,
507) -> Option<DexEvent> {
508    use super::utils::text_parser::*;
509
510    let metadata = create_metadata_simple(
511        signature,
512        slot,
513        tx_index,
514        block_time_us,
515        Pubkey::default(),
516        grpc_recv_us,
517    );
518
519    Some(DexEvent::RaydiumClmmCollectFee(RaydiumClmmCollectFeeEvent {
520        metadata,
521        pool_state: Pubkey::default(),
522        position_nft_mint: Pubkey::default(),
523        amount_0: extract_number_from_text(log, "amount_0").unwrap_or(10_000),
524        amount_1: extract_number_from_text(log, "amount_1").unwrap_or(10_000),
525    }))
526}
527
528// ============================================================================
529// Public API for optimized parsing from pre-decoded data
530// These functions accept already-decoded data (without discriminator)
531// ============================================================================
532
533/// Parse Raydium CLMM Swap event from pre-decoded data
534#[inline(always)]
535pub fn parse_swap_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
536    let mut offset = 0;
537
538    let pool_state = read_pubkey(data, offset)?;
539    offset += 32;
540
541    let user = read_pubkey(data, offset)?;
542    offset += 32;
543
544    let _amount = read_u64_le(data, offset)?;
545    offset += 8;
546
547    let _other_amount_threshold = read_u64_le(data, offset)?;
548    offset += 8;
549
550    let sqrt_price_limit_x64 = read_u128_le(data, offset)?;
551    offset += 16;
552
553    let is_base_input = read_bool(data, offset)?;
554
555    Some(DexEvent::RaydiumClmmSwap(RaydiumClmmSwapEvent {
556        metadata,
557        pool_state,
558        sender: user,
559        token_account_0: Pubkey::default(),
560        token_account_1: Pubkey::default(),
561        amount_0: 0,
562        transfer_fee_0: 0,
563        amount_1: 0,
564        transfer_fee_1: 0,
565        zero_for_one: is_base_input,
566        sqrt_price_x64: sqrt_price_limit_x64,
567        liquidity: 0,
568        tick: 0,
569    }))
570}
571
572/// Parse Raydium CLMM IncreaseLiquidity event from pre-decoded data
573#[inline(always)]
574pub fn parse_increase_liquidity_from_data(
575    data: &[u8],
576    metadata: EventMetadata,
577) -> Option<DexEvent> {
578    let mut offset = 0;
579
580    let pool = read_pubkey(data, offset)?;
581    offset += 32;
582
583    let user = read_pubkey(data, offset)?;
584    offset += 32;
585
586    let liquidity = read_u128_le(data, offset)?;
587    offset += 16;
588
589    let amount0_max = read_u64_le(data, offset)?;
590    offset += 8;
591
592    let amount1_max = read_u64_le(data, offset)?;
593
594    Some(DexEvent::RaydiumClmmIncreaseLiquidity(RaydiumClmmIncreaseLiquidityEvent {
595        metadata,
596        pool,
597        position_nft_mint: Pubkey::default(), // Not available in this data format
598        user,
599        liquidity,
600        amount0_max,
601        amount1_max,
602    }))
603}
604
605/// Parse Raydium CLMM DecreaseLiquidity event from pre-decoded data
606#[inline(always)]
607pub fn parse_decrease_liquidity_from_data(
608    data: &[u8],
609    metadata: EventMetadata,
610) -> Option<DexEvent> {
611    let mut offset = 0;
612
613    let pool = read_pubkey(data, offset)?;
614    offset += 32;
615
616    let user = read_pubkey(data, offset)?;
617    offset += 32;
618
619    let liquidity = read_u128_le(data, offset)?;
620    offset += 16;
621
622    let amount0_min = read_u64_le(data, offset)?;
623    offset += 8;
624
625    let amount1_min = read_u64_le(data, offset)?;
626
627    Some(DexEvent::RaydiumClmmDecreaseLiquidity(RaydiumClmmDecreaseLiquidityEvent {
628        metadata,
629        pool,
630        position_nft_mint: Pubkey::default(), // Not available in this data format
631        user,
632        liquidity,
633        amount0_min,
634        amount1_min,
635    }))
636}
637
638/// Parse Raydium CLMM CreatePool event from pre-decoded data
639#[inline(always)]
640pub fn parse_create_pool_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
641    let mut offset = 0;
642
643    let pool = read_pubkey(data, offset)?;
644    offset += 32;
645
646    let creator = read_pubkey(data, offset)?;
647    offset += 32;
648
649    let sqrt_price_x64 = read_u128_le(data, offset)?;
650    offset += 16;
651
652    let open_time = read_u64_le(data, offset)?;
653
654    Some(DexEvent::RaydiumClmmCreatePool(RaydiumClmmCreatePoolEvent {
655        metadata,
656        pool,
657        token_0_mint: Pubkey::default(), // Not available in this data format
658        token_1_mint: Pubkey::default(), // Not available in this data format
659        tick_spacing: 0,                 // Not available in this data format
660        fee_rate: 0,                     // Not available in this data format
661        creator,
662        sqrt_price_x64,
663        open_time,
664    }))
665}
666
667/// Parse Raydium CLMM CollectFee event from pre-decoded data
668#[inline(always)]
669pub fn parse_collect_fee_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
670    let mut offset = 0;
671
672    let pool_state = read_pubkey(data, offset)?;
673    offset += 32;
674
675    let position_nft_mint = read_pubkey(data, offset)?;
676    offset += 32;
677
678    let amount_0 = read_u64_le(data, offset)?;
679    offset += 8;
680
681    let amount_1 = read_u64_le(data, offset)?;
682
683    Some(DexEvent::RaydiumClmmCollectFee(RaydiumClmmCollectFeeEvent {
684        metadata,
685        pool_state,
686        position_nft_mint,
687        amount_0,
688        amount_1,
689    }))
690}