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] = [64, 198, 205, 232, 38, 8, 113, 226];
12    pub const INCREASE_LIQUIDITY: [u8; 8] = [49, 79, 105, 212, 32, 34, 30, 84];
13    pub const DECREASE_LIQUIDITY: [u8; 8] = [58, 222, 86, 58, 68, 50, 85, 56];
14    pub const LIQUIDITY_CHANGE: [u8; 8] = [126, 240, 175, 206, 158, 88, 153, 107];
15    pub const CONFIG_CHANGE: [u8; 8] = [247, 189, 7, 119, 106, 112, 95, 151];
16    pub const CREATE_PERSONAL_POSITION: [u8; 8] = [100, 30, 87, 249, 196, 223, 154, 206];
17    pub const LIQUIDITY_CALCULATE: [u8; 8] = [237, 112, 148, 230, 57, 84, 180, 162];
18    pub const OPEN_LIMIT_ORDER: [u8; 8] = [106, 24, 71, 85, 57, 169, 158, 216];
19    pub const INCREASE_LIMIT_ORDER: [u8; 8] = [11, 120, 13, 204, 199, 87, 19, 200];
20    pub const DECREASE_LIMIT_ORDER: [u8; 8] = [70, 48, 40, 221, 219, 237, 212, 163];
21    pub const SETTLE_LIMIT_ORDER: [u8; 8] = [88, 119, 77, 164, 125, 124, 10, 194];
22    pub const UPDATE_REWARD_INFOS: [u8; 8] = [109, 127, 186, 78, 114, 65, 37, 236];
23    pub const CREATE_POOL: [u8; 8] = [25, 94, 75, 47, 112, 99, 53, 63];
24    pub const COLLECT_PERSONAL_FEE: [u8; 8] = [166, 174, 105, 192, 81, 161, 83, 105];
25    pub const COLLECT_PROTOCOL_FEE: [u8; 8] = [206, 87, 17, 79, 45, 41, 213, 61];
26}
27
28/// Raydium CLMM 程序 ID
29pub const PROGRAM_ID: &str = "CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK";
30
31/// 检查日志是否来自 Raydium CLMM 程序
32pub fn is_raydium_clmm_log(log: &str) -> bool {
33    log.contains(&format!("Program {} invoke", PROGRAM_ID))
34        || log.contains(&format!("Program {} success", PROGRAM_ID))
35        || log.contains("raydium")
36        || log.contains("Raydium")
37}
38
39/// 主要的 Raydium CLMM 日志解析函数
40#[inline]
41pub fn parse_log(
42    log: &str,
43    signature: Signature,
44    slot: u64,
45    tx_index: u64,
46    block_time_us: Option<i64>,
47    grpc_recv_us: i64,
48) -> Option<DexEvent> {
49    parse_structured_log(log, signature, slot, tx_index, block_time_us, grpc_recv_us)
50}
51
52/// 结构化日志解析(基于 Program data)
53fn parse_structured_log(
54    log: &str,
55    signature: Signature,
56    slot: u64,
57    tx_index: u64,
58    block_time_us: Option<i64>,
59    grpc_recv_us: i64,
60) -> Option<DexEvent> {
61    let program_data = extract_program_data(log)?;
62    if program_data.len() < 8 {
63        return None;
64    }
65
66    let discriminator: [u8; 8] = program_data[0..8].try_into().ok()?;
67    let data = &program_data[8..];
68
69    match discriminator {
70        discriminators::SWAP => {
71            parse_swap_event(data, signature, slot, tx_index, block_time_us, grpc_recv_us)
72        }
73        discriminators::INCREASE_LIQUIDITY => parse_increase_liquidity_event(
74            data,
75            signature,
76            slot,
77            tx_index,
78            block_time_us,
79            grpc_recv_us,
80        ),
81        discriminators::DECREASE_LIQUIDITY => parse_decrease_liquidity_event(
82            data,
83            signature,
84            slot,
85            tx_index,
86            block_time_us,
87            grpc_recv_us,
88        ),
89        discriminators::LIQUIDITY_CHANGE => parse_liquidity_change_event(
90            data,
91            signature,
92            slot,
93            tx_index,
94            block_time_us,
95            grpc_recv_us,
96        ),
97        discriminators::CONFIG_CHANGE => {
98            parse_config_change_event(data, signature, slot, tx_index, block_time_us, grpc_recv_us)
99        }
100        discriminators::CREATE_PERSONAL_POSITION => parse_create_personal_position_event(
101            data,
102            signature,
103            slot,
104            tx_index,
105            block_time_us,
106            grpc_recv_us,
107        ),
108        discriminators::LIQUIDITY_CALCULATE => parse_liquidity_calculate_event(
109            data,
110            signature,
111            slot,
112            tx_index,
113            block_time_us,
114            grpc_recv_us,
115        ),
116        discriminators::OPEN_LIMIT_ORDER => parse_open_limit_order_event(
117            data,
118            signature,
119            slot,
120            tx_index,
121            block_time_us,
122            grpc_recv_us,
123        ),
124        discriminators::INCREASE_LIMIT_ORDER => parse_increase_limit_order_event(
125            data,
126            signature,
127            slot,
128            tx_index,
129            block_time_us,
130            grpc_recv_us,
131        ),
132        discriminators::DECREASE_LIMIT_ORDER => parse_decrease_limit_order_event(
133            data,
134            signature,
135            slot,
136            tx_index,
137            block_time_us,
138            grpc_recv_us,
139        ),
140        discriminators::SETTLE_LIMIT_ORDER => parse_settle_limit_order_event(
141            data,
142            signature,
143            slot,
144            tx_index,
145            block_time_us,
146            grpc_recv_us,
147        ),
148        discriminators::UPDATE_REWARD_INFOS => parse_update_reward_infos_event(
149            data,
150            signature,
151            slot,
152            tx_index,
153            block_time_us,
154            grpc_recv_us,
155        ),
156        discriminators::CREATE_POOL => {
157            parse_create_pool_event(data, signature, slot, tx_index, block_time_us, grpc_recv_us)
158        }
159        discriminators::COLLECT_PERSONAL_FEE => parse_collect_personal_fee_event(
160            data,
161            signature,
162            slot,
163            tx_index,
164            block_time_us,
165            grpc_recv_us,
166        ),
167        discriminators::COLLECT_PROTOCOL_FEE => parse_collect_protocol_fee_event(
168            data,
169            signature,
170            slot,
171            tx_index,
172            block_time_us,
173            grpc_recv_us,
174        ),
175        _ => None,
176    }
177}
178
179/// 解析交换事件
180fn parse_swap_event(
181    data: &[u8],
182    signature: Signature,
183    slot: u64,
184    tx_index: u64,
185    block_time_us: Option<i64>,
186    grpc_recv_us: i64,
187) -> Option<DexEvent> {
188    let mut offset = 0;
189
190    let pool_state = read_pubkey(data, offset)?;
191    offset += 32;
192
193    let sender = read_pubkey(data, offset)?;
194    offset += 32;
195
196    let token_account_0 = read_pubkey(data, offset)?;
197    offset += 32;
198
199    let token_account_1 = read_pubkey(data, offset)?;
200    offset += 32;
201
202    let amount_0 = read_u64_le(data, offset)?;
203    offset += 8;
204
205    let transfer_fee_0 = read_u64_le(data, offset)?;
206    offset += 8;
207
208    let amount_1 = read_u64_le(data, offset)?;
209    offset += 8;
210
211    let transfer_fee_1 = read_u64_le(data, offset)?;
212    offset += 8;
213
214    let zero_for_one = read_bool(data, offset)?;
215    offset += 1;
216
217    let sqrt_price_x64 = read_u128_le(data, offset)?;
218    offset += 16;
219
220    let liquidity = read_u128_le(data, offset)?;
221    offset += 16;
222
223    let tick = read_i32_le(data, offset)?;
224
225    let metadata =
226        create_metadata_simple(signature, slot, tx_index, block_time_us, pool_state, grpc_recv_us);
227
228    Some(DexEvent::RaydiumClmmSwap(RaydiumClmmSwapEvent {
229        metadata,
230        pool_state,
231        sender,
232        token_account_0,
233        token_account_1,
234        amount_0,
235        transfer_fee_0,
236        amount_1,
237        transfer_fee_1,
238        zero_for_one,
239        sqrt_price_x64,
240        liquidity,
241        tick,
242        amm_config: Pubkey::default(),
243        input_vault: Pubkey::default(),
244        output_vault: Pubkey::default(),
245        observation_state: Pubkey::default(),
246        input_mint: Pubkey::default(),
247        output_mint: Pubkey::default(),
248        tick_array_bitmap_extension: None,
249        tick_arrays: Vec::new(),
250
251        ..Default::default()
252    }))
253}
254
255/// 解析增加流动性事件
256fn parse_increase_liquidity_event(
257    data: &[u8],
258    signature: Signature,
259    slot: u64,
260    tx_index: u64,
261    block_time_us: Option<i64>,
262    grpc_recv_us: i64,
263) -> Option<DexEvent> {
264    let mut offset = 0;
265
266    let position_nft_mint = read_pubkey(data, offset)?;
267    offset += 32;
268
269    let liquidity = read_u128_le(data, offset)?;
270    offset += 16;
271
272    let amount_0 = read_u64_le(data, offset)?;
273    offset += 8;
274
275    let amount_1 = read_u64_le(data, offset)?;
276    offset += 8;
277
278    let amount_0_transfer_fee = read_u64_le(data, offset)?;
279    offset += 8;
280
281    let amount_1_transfer_fee = read_u64_le(data, offset)?;
282
283    let metadata = create_metadata_simple(
284        signature,
285        slot,
286        tx_index,
287        block_time_us,
288        Pubkey::default(),
289        grpc_recv_us,
290    );
291
292    Some(DexEvent::RaydiumClmmIncreaseLiquidity(RaydiumClmmIncreaseLiquidityEvent {
293        metadata,
294        position_nft_mint,
295        liquidity,
296        amount_0,
297        amount_1,
298        amount_0_transfer_fee,
299        amount_1_transfer_fee,
300        pool: Pubkey::default(),
301        user: Pubkey::default(), // TODO: extract from instruction accounts
302        amount0_max: 0,
303        amount1_max: 0,
304    }))
305}
306
307/// 解析减少流动性事件
308fn parse_decrease_liquidity_event(
309    data: &[u8],
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    let mut offset = 0;
317
318    let position_nft_mint = read_pubkey(data, offset)?;
319    offset += 32;
320
321    let liquidity = read_u128_le(data, offset)?;
322    offset += 16;
323
324    let decrease_amount_0 = read_u64_le(data, offset)?;
325    offset += 8;
326
327    let decrease_amount_1 = read_u64_le(data, offset)?;
328    offset += 8;
329
330    let fee_amount_0 = read_u64_le(data, offset)?;
331    offset += 8;
332
333    let fee_amount_1 = read_u64_le(data, offset)?;
334    offset += 8;
335
336    let mut reward_amounts = [0u64; 3];
337    for reward_amount in &mut reward_amounts {
338        *reward_amount = read_u64_le(data, offset)?;
339        offset += 8;
340    }
341
342    let transfer_fee_0 = read_u64_le(data, offset)?;
343    offset += 8;
344
345    let transfer_fee_1 = read_u64_le(data, offset)?;
346
347    let metadata = create_metadata_simple(
348        signature,
349        slot,
350        tx_index,
351        block_time_us,
352        Pubkey::default(),
353        grpc_recv_us,
354    );
355
356    Some(DexEvent::RaydiumClmmDecreaseLiquidity(RaydiumClmmDecreaseLiquidityEvent {
357        metadata,
358        position_nft_mint,
359        liquidity,
360        decrease_amount_0,
361        decrease_amount_1,
362        fee_amount_0,
363        fee_amount_1,
364        reward_amounts,
365        transfer_fee_0,
366        transfer_fee_1,
367        pool: Pubkey::default(),
368        user: Pubkey::default(), // TODO: extract from instruction accounts
369        amount0_min: 0,
370        amount1_min: 0,
371    }))
372}
373
374/// 解析流动性变化事件
375fn parse_liquidity_change_event(
376    data: &[u8],
377    signature: Signature,
378    slot: u64,
379    tx_index: u64,
380    block_time_us: Option<i64>,
381    grpc_recv_us: i64,
382) -> Option<DexEvent> {
383    let mut offset = 0;
384
385    let pool_state = read_pubkey(data, offset)?;
386    offset += 32;
387
388    let tick = read_i32_le(data, offset)?;
389    offset += 4;
390
391    let tick_lower = read_i32_le(data, offset)?;
392    offset += 4;
393
394    let tick_upper = read_i32_le(data, offset)?;
395    offset += 4;
396
397    let liquidity_before = read_u128_le(data, offset)?;
398    offset += 16;
399
400    let liquidity_after = read_u128_le(data, offset)?;
401
402    let metadata =
403        create_metadata_simple(signature, slot, tx_index, block_time_us, pool_state, grpc_recv_us);
404
405    Some(DexEvent::RaydiumClmmLiquidityChange(RaydiumClmmLiquidityChangeEvent {
406        metadata,
407        pool_state,
408        tick,
409        tick_lower,
410        tick_upper,
411        liquidity_before,
412        liquidity_after,
413    }))
414}
415
416fn parse_config_change_event(
417    data: &[u8],
418    signature: Signature,
419    slot: u64,
420    tx_index: u64,
421    block_time_us: Option<i64>,
422    grpc_recv_us: i64,
423) -> Option<DexEvent> {
424    let metadata = create_metadata_simple(
425        signature,
426        slot,
427        tx_index,
428        block_time_us,
429        Pubkey::default(),
430        grpc_recv_us,
431    );
432    parse_config_change_from_data(data, metadata)
433}
434
435fn parse_create_personal_position_event(
436    data: &[u8],
437    signature: Signature,
438    slot: u64,
439    tx_index: u64,
440    block_time_us: Option<i64>,
441    grpc_recv_us: i64,
442) -> Option<DexEvent> {
443    let pool_state = read_pubkey(data, 0).unwrap_or_default();
444    let metadata =
445        create_metadata_simple(signature, slot, tx_index, block_time_us, pool_state, grpc_recv_us);
446    parse_create_personal_position_from_data(data, metadata)
447}
448
449fn parse_liquidity_calculate_event(
450    data: &[u8],
451    signature: Signature,
452    slot: u64,
453    tx_index: u64,
454    block_time_us: Option<i64>,
455    grpc_recv_us: i64,
456) -> Option<DexEvent> {
457    let metadata = create_metadata_simple(
458        signature,
459        slot,
460        tx_index,
461        block_time_us,
462        Pubkey::default(),
463        grpc_recv_us,
464    );
465    parse_liquidity_calculate_from_data(data, metadata)
466}
467
468fn parse_open_limit_order_event(
469    data: &[u8],
470    signature: Signature,
471    slot: u64,
472    tx_index: u64,
473    block_time_us: Option<i64>,
474    grpc_recv_us: i64,
475) -> Option<DexEvent> {
476    let pool_id = read_pubkey(data, 0).unwrap_or_default();
477    let metadata =
478        create_metadata_simple(signature, slot, tx_index, block_time_us, pool_id, grpc_recv_us);
479    parse_open_limit_order_from_data(data, metadata)
480}
481
482fn parse_increase_limit_order_event(
483    data: &[u8],
484    signature: Signature,
485    slot: u64,
486    tx_index: u64,
487    block_time_us: Option<i64>,
488    grpc_recv_us: i64,
489) -> Option<DexEvent> {
490    let pool_id = read_pubkey(data, 0).unwrap_or_default();
491    let metadata =
492        create_metadata_simple(signature, slot, tx_index, block_time_us, pool_id, grpc_recv_us);
493    parse_increase_limit_order_from_data(data, metadata)
494}
495
496fn parse_decrease_limit_order_event(
497    data: &[u8],
498    signature: Signature,
499    slot: u64,
500    tx_index: u64,
501    block_time_us: Option<i64>,
502    grpc_recv_us: i64,
503) -> Option<DexEvent> {
504    let pool_id = read_pubkey(data, 0).unwrap_or_default();
505    let metadata =
506        create_metadata_simple(signature, slot, tx_index, block_time_us, pool_id, grpc_recv_us);
507    parse_decrease_limit_order_from_data(data, metadata)
508}
509
510fn parse_settle_limit_order_event(
511    data: &[u8],
512    signature: Signature,
513    slot: u64,
514    tx_index: u64,
515    block_time_us: Option<i64>,
516    grpc_recv_us: i64,
517) -> Option<DexEvent> {
518    let pool_id = read_pubkey(data, 0).unwrap_or_default();
519    let metadata =
520        create_metadata_simple(signature, slot, tx_index, block_time_us, pool_id, grpc_recv_us);
521    parse_settle_limit_order_from_data(data, metadata)
522}
523
524fn parse_update_reward_infos_event(
525    data: &[u8],
526    signature: Signature,
527    slot: u64,
528    tx_index: u64,
529    block_time_us: Option<i64>,
530    grpc_recv_us: i64,
531) -> Option<DexEvent> {
532    let metadata = create_metadata_simple(
533        signature,
534        slot,
535        tx_index,
536        block_time_us,
537        Pubkey::default(),
538        grpc_recv_us,
539    );
540    parse_update_reward_infos_from_data(data, metadata)
541}
542
543/// 解析池创建事件
544fn parse_create_pool_event(
545    data: &[u8],
546    signature: Signature,
547    slot: u64,
548    tx_index: u64,
549    block_time_us: Option<i64>,
550    grpc_recv_us: i64,
551) -> Option<DexEvent> {
552    let mut offset = 0;
553
554    let token_0_mint = read_pubkey(data, offset)?;
555    offset += 32;
556
557    let token_1_mint = read_pubkey(data, offset)?;
558    offset += 32;
559
560    let tick_spacing = read_u16_le(data, offset)?;
561    offset += 2;
562
563    let pool_state = read_pubkey(data, offset)?;
564    offset += 32;
565
566    let sqrt_price_x64 = read_u128_le(data, offset)?;
567    offset += 16;
568
569    let tick = read_i32_le(data, offset)?;
570    offset += 4;
571
572    let token_vault_0 = read_pubkey(data, offset)?;
573    offset += 32;
574
575    let token_vault_1 = read_pubkey(data, offset)?;
576
577    let metadata =
578        create_metadata_simple(signature, slot, tx_index, block_time_us, pool_state, grpc_recv_us);
579
580    Some(DexEvent::RaydiumClmmCreatePool(RaydiumClmmCreatePoolEvent {
581        metadata,
582        pool: pool_state,
583        token_0_mint,
584        token_1_mint,
585        tick_spacing,
586        sqrt_price_x64,
587        tick,
588        token_vault_0,
589        token_vault_1,
590        fee_rate: 0,
591        creator: Pubkey::default(),
592        open_time: 0,
593    }))
594}
595
596/// 解析个人费用收集事件
597fn parse_collect_personal_fee_event(
598    data: &[u8],
599    signature: Signature,
600    slot: u64,
601    tx_index: u64,
602    block_time_us: Option<i64>,
603    grpc_recv_us: i64,
604) -> Option<DexEvent> {
605    let mut offset = 0;
606
607    let position_nft_mint = read_pubkey(data, offset)?;
608    offset += 32;
609
610    let recipient_token_account_0 = read_pubkey(data, offset)?;
611    offset += 32;
612
613    let recipient_token_account_1 = read_pubkey(data, offset)?;
614    offset += 32;
615
616    let amount_0 = read_u64_le(data, offset)?;
617    offset += 8;
618
619    let amount_1 = read_u64_le(data, offset)?;
620
621    let metadata = create_metadata_simple(
622        signature,
623        slot,
624        tx_index,
625        block_time_us,
626        Pubkey::default(),
627        grpc_recv_us,
628    );
629
630    Some(DexEvent::RaydiumClmmCollectFee(RaydiumClmmCollectFeeEvent {
631        metadata,
632        pool_state: Pubkey::default(),
633        position_nft_mint,
634        recipient_token_account_0,
635        recipient_token_account_1,
636        amount_0,
637        amount_1,
638    }))
639}
640
641/// 解析协议费用收集事件
642fn parse_collect_protocol_fee_event(
643    data: &[u8],
644    signature: Signature,
645    slot: u64,
646    tx_index: u64,
647    block_time_us: Option<i64>,
648    grpc_recv_us: i64,
649) -> Option<DexEvent> {
650    let mut offset = 0;
651
652    let pool_state = read_pubkey(data, offset)?;
653    offset += 32;
654
655    let recipient_token_account_0 = read_pubkey(data, offset)?;
656    offset += 32;
657
658    let recipient_token_account_1 = read_pubkey(data, offset)?;
659    offset += 32;
660
661    let amount_0 = read_u64_le(data, offset)?;
662    offset += 8;
663
664    let amount_1 = read_u64_le(data, offset)?;
665
666    let metadata =
667        create_metadata_simple(signature, slot, tx_index, block_time_us, pool_state, grpc_recv_us);
668
669    Some(DexEvent::RaydiumClmmCollectFee(RaydiumClmmCollectFeeEvent {
670        metadata,
671        pool_state,
672        position_nft_mint: Pubkey::default(),
673        recipient_token_account_0,
674        recipient_token_account_1,
675        amount_0,
676        amount_1,
677    }))
678}
679
680/// 文本回退解析
681fn parse_text_log(
682    log: &str,
683    signature: Signature,
684    slot: u64,
685    tx_index: u64,
686    block_time_us: Option<i64>,
687    grpc_recv_us: i64,
688) -> Option<DexEvent> {
689    use super::utils::text_parser::*;
690
691    if log.contains("swap") || log.contains("Swap") {
692        return parse_swap_from_text(log, signature, slot, tx_index, block_time_us, grpc_recv_us);
693    }
694
695    if log.contains("increase") && log.contains("liquidity") {
696        return parse_increase_liquidity_from_text(
697            log,
698            signature,
699            slot,
700            tx_index,
701            block_time_us,
702            grpc_recv_us,
703        );
704    }
705
706    if log.contains("decrease") && log.contains("liquidity") {
707        return parse_decrease_liquidity_from_text(
708            log,
709            signature,
710            slot,
711            tx_index,
712            block_time_us,
713            grpc_recv_us,
714        );
715    }
716
717    if log.contains("create") && log.contains("pool") {
718        return parse_create_pool_from_text(
719            log,
720            signature,
721            slot,
722            tx_index,
723            block_time_us,
724            grpc_recv_us,
725        );
726    }
727
728    if log.contains("collect") && log.contains("fee") {
729        return parse_collect_fee_from_text(
730            log,
731            signature,
732            slot,
733            tx_index,
734            block_time_us,
735            grpc_recv_us,
736        );
737    }
738
739    None
740}
741
742/// 从文本解析交换事件
743fn parse_swap_from_text(
744    log: &str,
745    signature: Signature,
746    slot: u64,
747    tx_index: u64,
748    block_time_us: Option<i64>,
749    grpc_recv_us: i64,
750) -> Option<DexEvent> {
751    use super::utils::text_parser::*;
752
753    let metadata = create_metadata_simple(
754        signature,
755        slot,
756        tx_index,
757        block_time_us,
758        Pubkey::default(),
759        grpc_recv_us,
760    );
761    let is_base_input = detect_trade_type(log).unwrap_or(true);
762
763    Some(DexEvent::RaydiumClmmSwap(RaydiumClmmSwapEvent {
764        metadata,
765
766        // IDL SwapEvent 事件字段
767        pool_state: Pubkey::default(),
768        sender: Pubkey::default(),
769        token_account_0: Pubkey::default(),
770        token_account_1: Pubkey::default(),
771        amount_0: 0,
772        transfer_fee_0: 0,
773        amount_1: 0,
774        transfer_fee_1: 0,
775        zero_for_one: is_base_input,
776        sqrt_price_x64: 0,
777        // is_base_input,
778        liquidity: 0,
779        tick: 0,
780        // 暂时注释,以后会用,AI禁止改动
781        // 指令参数字段
782        // amount: extract_number_from_text(log, "amount").unwrap_or(1_000_000_000),
783        // other_amount_threshold: extract_number_from_text(log, "threshold").unwrap_or(950_000_000),
784        // sqrt_price_limit_x64: 0,
785        ..Default::default()
786    }))
787}
788
789/// 从文本解析增加流动性事件
790fn parse_increase_liquidity_from_text(
791    log: &str,
792    signature: Signature,
793    slot: u64,
794    tx_index: u64,
795    block_time_us: Option<i64>,
796    grpc_recv_us: i64,
797) -> Option<DexEvent> {
798    use super::utils::text_parser::*;
799
800    let metadata = create_metadata_simple(
801        signature,
802        slot,
803        tx_index,
804        block_time_us,
805        Pubkey::default(),
806        grpc_recv_us,
807    );
808
809    Some(DexEvent::RaydiumClmmIncreaseLiquidity(RaydiumClmmIncreaseLiquidityEvent {
810        metadata,
811        position_nft_mint: Pubkey::default(),
812        liquidity: extract_number_from_text(log, "liquidity").unwrap_or(1_000_000) as u128,
813        amount_0: 0,
814        amount_1: 0,
815        amount_0_transfer_fee: 0,
816        amount_1_transfer_fee: 0,
817        pool: Pubkey::default(),
818        amount0_max: extract_number_from_text(log, "amount0_max").unwrap_or(1_000_000),
819        amount1_max: extract_number_from_text(log, "amount1_max").unwrap_or(1_000_000),
820        user: Pubkey::default(),
821    }))
822}
823
824/// 从文本解析减少流动性事件
825fn parse_decrease_liquidity_from_text(
826    log: &str,
827    signature: Signature,
828    slot: u64,
829    tx_index: u64,
830    block_time_us: Option<i64>,
831    grpc_recv_us: i64,
832) -> Option<DexEvent> {
833    use super::utils::text_parser::*;
834
835    let metadata = create_metadata_simple(
836        signature,
837        slot,
838        tx_index,
839        block_time_us,
840        Pubkey::default(),
841        grpc_recv_us,
842    );
843
844    Some(DexEvent::RaydiumClmmDecreaseLiquidity(RaydiumClmmDecreaseLiquidityEvent {
845        metadata,
846        position_nft_mint: Pubkey::default(),
847        liquidity: extract_number_from_text(log, "liquidity").unwrap_or(1_000_000) as u128,
848        decrease_amount_0: 0,
849        decrease_amount_1: 0,
850        fee_amount_0: 0,
851        fee_amount_1: 0,
852        reward_amounts: [0; 3],
853        transfer_fee_0: 0,
854        transfer_fee_1: 0,
855        pool: Pubkey::default(),
856        amount0_min: extract_number_from_text(log, "amount0_min").unwrap_or(1_000_000),
857        amount1_min: extract_number_from_text(log, "amount1_min").unwrap_or(1_000_000),
858        user: Pubkey::default(),
859    }))
860}
861
862/// 从文本解析池创建事件
863fn parse_create_pool_from_text(
864    log: &str,
865    signature: Signature,
866    slot: u64,
867    tx_index: u64,
868    block_time_us: Option<i64>,
869    grpc_recv_us: i64,
870) -> Option<DexEvent> {
871    use super::utils::text_parser::*;
872
873    let metadata = create_metadata_simple(
874        signature,
875        slot,
876        tx_index,
877        block_time_us,
878        Pubkey::default(),
879        grpc_recv_us,
880    );
881
882    Some(DexEvent::RaydiumClmmCreatePool(RaydiumClmmCreatePoolEvent {
883        metadata,
884        pool: Pubkey::default(),
885        token_0_mint: Pubkey::default(),
886        token_1_mint: Pubkey::default(),
887        tick_spacing: 0,
888        sqrt_price_x64: 0,
889        tick: 0,
890        token_vault_0: Pubkey::default(),
891        token_vault_1: Pubkey::default(),
892        fee_rate: 0,
893        creator: Pubkey::default(),
894        open_time: 0,
895    }))
896}
897
898/// 从文本解析费用收集事件
899fn parse_collect_fee_from_text(
900    log: &str,
901    signature: Signature,
902    slot: u64,
903    tx_index: u64,
904    block_time_us: Option<i64>,
905    grpc_recv_us: i64,
906) -> Option<DexEvent> {
907    use super::utils::text_parser::*;
908
909    let metadata = create_metadata_simple(
910        signature,
911        slot,
912        tx_index,
913        block_time_us,
914        Pubkey::default(),
915        grpc_recv_us,
916    );
917
918    Some(DexEvent::RaydiumClmmCollectFee(RaydiumClmmCollectFeeEvent {
919        metadata,
920        pool_state: Pubkey::default(),
921        position_nft_mint: Pubkey::default(),
922        recipient_token_account_0: Pubkey::default(),
923        recipient_token_account_1: Pubkey::default(),
924        amount_0: extract_number_from_text(log, "amount_0").unwrap_or(10_000),
925        amount_1: extract_number_from_text(log, "amount_1").unwrap_or(10_000),
926    }))
927}
928
929// ============================================================================
930// Public API for optimized parsing from pre-decoded data
931// These functions accept already-decoded data (without discriminator)
932// ============================================================================
933
934/// Parse Raydium CLMM Swap event from pre-decoded data
935#[inline(always)]
936pub fn parse_swap_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
937    let mut offset = 0;
938
939    let pool_state = read_pubkey(data, offset)?;
940    offset += 32;
941
942    let sender = read_pubkey(data, offset)?;
943    offset += 32;
944
945    let token_account_0 = read_pubkey(data, offset)?;
946    offset += 32;
947
948    let token_account_1 = read_pubkey(data, offset)?;
949    offset += 32;
950
951    let amount_0 = read_u64_le(data, offset)?;
952    offset += 8;
953
954    let transfer_fee_0 = read_u64_le(data, offset)?;
955    offset += 8;
956
957    let amount_1 = read_u64_le(data, offset)?;
958    offset += 8;
959
960    let transfer_fee_1 = read_u64_le(data, offset)?;
961    offset += 8;
962
963    let zero_for_one = read_bool(data, offset)?;
964    offset += 1;
965
966    let sqrt_price_x64 = read_u128_le(data, offset)?;
967    offset += 16;
968
969    let liquidity = read_u128_le(data, offset)?;
970    offset += 16;
971
972    let tick = read_i32_le(data, offset)?;
973
974    Some(DexEvent::RaydiumClmmSwap(RaydiumClmmSwapEvent {
975        metadata,
976        pool_state,
977        sender,
978        token_account_0,
979        token_account_1,
980        amount_0,
981        transfer_fee_0,
982        amount_1,
983        transfer_fee_1,
984        zero_for_one,
985        sqrt_price_x64,
986        liquidity,
987        tick,
988        amm_config: Pubkey::default(),
989        input_vault: Pubkey::default(),
990        output_vault: Pubkey::default(),
991        observation_state: Pubkey::default(),
992        input_mint: Pubkey::default(),
993        output_mint: Pubkey::default(),
994        tick_array_bitmap_extension: None,
995        tick_arrays: Vec::new(),
996
997        ..Default::default()
998    }))
999}
1000
1001/// Parse Raydium CLMM IncreaseLiquidity event from pre-decoded data
1002#[inline(always)]
1003pub fn parse_increase_liquidity_from_data(
1004    data: &[u8],
1005    metadata: EventMetadata,
1006) -> Option<DexEvent> {
1007    let mut offset = 0;
1008
1009    let position_nft_mint = read_pubkey(data, offset)?;
1010    offset += 32;
1011
1012    let liquidity = read_u128_le(data, offset)?;
1013    offset += 16;
1014
1015    let amount_0 = read_u64_le(data, offset)?;
1016    offset += 8;
1017
1018    let amount_1 = read_u64_le(data, offset)?;
1019    offset += 8;
1020
1021    let amount_0_transfer_fee = read_u64_le(data, offset)?;
1022    offset += 8;
1023
1024    let amount_1_transfer_fee = read_u64_le(data, offset)?;
1025
1026    Some(DexEvent::RaydiumClmmIncreaseLiquidity(RaydiumClmmIncreaseLiquidityEvent {
1027        metadata,
1028        position_nft_mint,
1029        liquidity,
1030        amount_0,
1031        amount_1,
1032        amount_0_transfer_fee,
1033        amount_1_transfer_fee,
1034        pool: Pubkey::default(),
1035        amount0_max: 0,
1036        amount1_max: 0,
1037        user: Pubkey::default(),
1038    }))
1039}
1040
1041/// Parse Raydium CLMM DecreaseLiquidity event from pre-decoded data
1042#[inline(always)]
1043pub fn parse_decrease_liquidity_from_data(
1044    data: &[u8],
1045    metadata: EventMetadata,
1046) -> Option<DexEvent> {
1047    let mut offset = 0;
1048
1049    let position_nft_mint = read_pubkey(data, offset)?;
1050    offset += 32;
1051
1052    let liquidity = read_u128_le(data, offset)?;
1053    offset += 16;
1054
1055    let decrease_amount_0 = read_u64_le(data, offset)?;
1056    offset += 8;
1057
1058    let decrease_amount_1 = read_u64_le(data, offset)?;
1059    offset += 8;
1060
1061    let fee_amount_0 = read_u64_le(data, offset)?;
1062    offset += 8;
1063
1064    let fee_amount_1 = read_u64_le(data, offset)?;
1065    offset += 8;
1066
1067    let mut reward_amounts = [0u64; 3];
1068    for reward_amount in &mut reward_amounts {
1069        *reward_amount = read_u64_le(data, offset)?;
1070        offset += 8;
1071    }
1072
1073    let transfer_fee_0 = read_u64_le(data, offset)?;
1074    offset += 8;
1075
1076    let transfer_fee_1 = read_u64_le(data, offset)?;
1077
1078    Some(DexEvent::RaydiumClmmDecreaseLiquidity(RaydiumClmmDecreaseLiquidityEvent {
1079        metadata,
1080        position_nft_mint,
1081        liquidity,
1082        decrease_amount_0,
1083        decrease_amount_1,
1084        fee_amount_0,
1085        fee_amount_1,
1086        reward_amounts,
1087        transfer_fee_0,
1088        transfer_fee_1,
1089        pool: Pubkey::default(),
1090        amount0_min: 0,
1091        amount1_min: 0,
1092        user: Pubkey::default(),
1093    }))
1094}
1095
1096/// Parse Raydium CLMM LiquidityChange event from pre-decoded data
1097#[inline(always)]
1098pub fn parse_liquidity_change_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
1099    let mut offset = 0;
1100
1101    let pool_state = read_pubkey(data, offset)?;
1102    offset += 32;
1103
1104    let tick = read_i32_le(data, offset)?;
1105    offset += 4;
1106
1107    let tick_lower = read_i32_le(data, offset)?;
1108    offset += 4;
1109
1110    let tick_upper = read_i32_le(data, offset)?;
1111    offset += 4;
1112
1113    let liquidity_before = read_u128_le(data, offset)?;
1114    offset += 16;
1115
1116    let liquidity_after = read_u128_le(data, offset)?;
1117
1118    Some(DexEvent::RaydiumClmmLiquidityChange(RaydiumClmmLiquidityChangeEvent {
1119        metadata,
1120        pool_state,
1121        tick,
1122        tick_lower,
1123        tick_upper,
1124        liquidity_before,
1125        liquidity_after,
1126    }))
1127}
1128
1129/// Parse Raydium CLMM ConfigChange event from pre-decoded data
1130#[inline(always)]
1131pub fn parse_config_change_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
1132    let mut offset = 0;
1133
1134    let index = read_u16_le(data, offset)?;
1135    offset += 2;
1136
1137    let owner = read_pubkey(data, offset)?;
1138    offset += 32;
1139
1140    let protocol_fee_rate = read_u32_le(data, offset)?;
1141    offset += 4;
1142
1143    let trade_fee_rate = read_u32_le(data, offset)?;
1144    offset += 4;
1145
1146    let tick_spacing = read_u16_le(data, offset)?;
1147    offset += 2;
1148
1149    let fund_fee_rate = read_u32_le(data, offset)?;
1150    offset += 4;
1151
1152    let fund_owner = read_pubkey(data, offset)?;
1153
1154    Some(DexEvent::RaydiumClmmConfigChange(RaydiumClmmConfigChangeEvent {
1155        metadata,
1156        index,
1157        owner,
1158        protocol_fee_rate,
1159        trade_fee_rate,
1160        tick_spacing,
1161        fund_fee_rate,
1162        fund_owner,
1163    }))
1164}
1165
1166/// Parse Raydium CLMM CreatePersonalPosition event from pre-decoded data
1167#[inline(always)]
1168pub fn parse_create_personal_position_from_data(
1169    data: &[u8],
1170    metadata: EventMetadata,
1171) -> Option<DexEvent> {
1172    let mut offset = 0;
1173
1174    let pool_state = read_pubkey(data, offset)?;
1175    offset += 32;
1176
1177    let minter = read_pubkey(data, offset)?;
1178    offset += 32;
1179
1180    let nft_owner = read_pubkey(data, offset)?;
1181    offset += 32;
1182
1183    let tick_lower_index = read_i32_le(data, offset)?;
1184    offset += 4;
1185
1186    let tick_upper_index = read_i32_le(data, offset)?;
1187    offset += 4;
1188
1189    let liquidity = read_u128_le(data, offset)?;
1190    offset += 16;
1191
1192    let deposit_amount_0 = read_u64_le(data, offset)?;
1193    offset += 8;
1194
1195    let deposit_amount_1 = read_u64_le(data, offset)?;
1196    offset += 8;
1197
1198    let deposit_amount_0_transfer_fee = read_u64_le(data, offset)?;
1199    offset += 8;
1200
1201    let deposit_amount_1_transfer_fee = read_u64_le(data, offset)?;
1202
1203    Some(DexEvent::RaydiumClmmCreatePersonalPosition(RaydiumClmmCreatePersonalPositionEvent {
1204        metadata,
1205        pool_state,
1206        minter,
1207        nft_owner,
1208        tick_lower_index,
1209        tick_upper_index,
1210        liquidity,
1211        deposit_amount_0,
1212        deposit_amount_1,
1213        deposit_amount_0_transfer_fee,
1214        deposit_amount_1_transfer_fee,
1215    }))
1216}
1217
1218/// Parse Raydium CLMM LiquidityCalculate event from pre-decoded data
1219#[inline(always)]
1220pub fn parse_liquidity_calculate_from_data(
1221    data: &[u8],
1222    metadata: EventMetadata,
1223) -> Option<DexEvent> {
1224    let mut offset = 0;
1225
1226    let pool_liquidity = read_u128_le(data, offset)?;
1227    offset += 16;
1228
1229    let pool_sqrt_price_x64 = read_u128_le(data, offset)?;
1230    offset += 16;
1231
1232    let pool_tick = read_i32_le(data, offset)?;
1233    offset += 4;
1234
1235    let calc_amount_0 = read_u64_le(data, offset)?;
1236    offset += 8;
1237
1238    let calc_amount_1 = read_u64_le(data, offset)?;
1239    offset += 8;
1240
1241    let trade_fee_owed_0 = read_u64_le(data, offset)?;
1242    offset += 8;
1243
1244    let trade_fee_owed_1 = read_u64_le(data, offset)?;
1245    offset += 8;
1246
1247    let transfer_fee_0 = read_u64_le(data, offset)?;
1248    offset += 8;
1249
1250    let transfer_fee_1 = read_u64_le(data, offset)?;
1251
1252    Some(DexEvent::RaydiumClmmLiquidityCalculate(RaydiumClmmLiquidityCalculateEvent {
1253        metadata,
1254        pool_liquidity,
1255        pool_sqrt_price_x64,
1256        pool_tick,
1257        calc_amount_0,
1258        calc_amount_1,
1259        trade_fee_owed_0,
1260        trade_fee_owed_1,
1261        transfer_fee_0,
1262        transfer_fee_1,
1263    }))
1264}
1265
1266/// Parse Raydium CLMM OpenLimitOrder event from pre-decoded data
1267#[inline(always)]
1268pub fn parse_open_limit_order_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
1269    let mut offset = 0;
1270
1271    let pool_id = read_pubkey(data, offset)?;
1272    offset += 32;
1273
1274    let limit_order = read_pubkey(data, offset)?;
1275    offset += 32;
1276
1277    let zero_for_one = read_bool(data, offset)?;
1278    offset += 1;
1279
1280    let tick_index = read_i32_le(data, offset)?;
1281    offset += 4;
1282
1283    let total_amount = read_u64_le(data, offset)?;
1284    offset += 8;
1285
1286    let transfer_fee = read_u64_le(data, offset)?;
1287
1288    Some(DexEvent::RaydiumClmmOpenLimitOrder(RaydiumClmmOpenLimitOrderEvent {
1289        metadata,
1290        pool_id,
1291        limit_order,
1292        zero_for_one,
1293        tick_index,
1294        total_amount,
1295        transfer_fee,
1296    }))
1297}
1298
1299/// Parse Raydium CLMM IncreaseLimitOrder event from pre-decoded data
1300#[inline(always)]
1301pub fn parse_increase_limit_order_from_data(
1302    data: &[u8],
1303    metadata: EventMetadata,
1304) -> Option<DexEvent> {
1305    let mut offset = 0;
1306
1307    let pool_id = read_pubkey(data, offset)?;
1308    offset += 32;
1309
1310    let limit_order = read_pubkey(data, offset)?;
1311    offset += 32;
1312
1313    let zero_for_one = read_bool(data, offset)?;
1314    offset += 1;
1315
1316    let tick_index = read_i32_le(data, offset)?;
1317    offset += 4;
1318
1319    let total_amount = read_u64_le(data, offset)?;
1320    offset += 8;
1321
1322    let increased_amount = read_u64_le(data, offset)?;
1323    offset += 8;
1324
1325    let transfer_fee = read_u64_le(data, offset)?;
1326
1327    Some(DexEvent::RaydiumClmmIncreaseLimitOrder(RaydiumClmmIncreaseLimitOrderEvent {
1328        metadata,
1329        pool_id,
1330        limit_order,
1331        zero_for_one,
1332        tick_index,
1333        total_amount,
1334        increased_amount,
1335        transfer_fee,
1336    }))
1337}
1338
1339/// Parse Raydium CLMM DecreaseLimitOrder event from pre-decoded data
1340#[inline(always)]
1341pub fn parse_decrease_limit_order_from_data(
1342    data: &[u8],
1343    metadata: EventMetadata,
1344) -> Option<DexEvent> {
1345    let mut offset = 0;
1346
1347    let pool_id = read_pubkey(data, offset)?;
1348    offset += 32;
1349
1350    let limit_order = read_pubkey(data, offset)?;
1351    offset += 32;
1352
1353    let zero_for_one = read_bool(data, offset)?;
1354    offset += 1;
1355
1356    let tick_index = read_i32_le(data, offset)?;
1357    offset += 4;
1358
1359    let total_amount = read_u64_le(data, offset)?;
1360    offset += 8;
1361
1362    let filled_amount = read_u64_le(data, offset)?;
1363    offset += 8;
1364
1365    let settled_output_amount = read_u64_le(data, offset)?;
1366    offset += 8;
1367
1368    let decreased_amount = read_u64_le(data, offset)?;
1369
1370    Some(DexEvent::RaydiumClmmDecreaseLimitOrder(RaydiumClmmDecreaseLimitOrderEvent {
1371        metadata,
1372        pool_id,
1373        limit_order,
1374        zero_for_one,
1375        tick_index,
1376        total_amount,
1377        filled_amount,
1378        settled_output_amount,
1379        decreased_amount,
1380    }))
1381}
1382
1383/// Parse Raydium CLMM SettleLimitOrder event from pre-decoded data
1384#[inline(always)]
1385pub fn parse_settle_limit_order_from_data(
1386    data: &[u8],
1387    metadata: EventMetadata,
1388) -> Option<DexEvent> {
1389    let mut offset = 0;
1390
1391    let pool_id = read_pubkey(data, offset)?;
1392    offset += 32;
1393
1394    let limit_order = read_pubkey(data, offset)?;
1395    offset += 32;
1396
1397    let zero_for_one = read_bool(data, offset)?;
1398    offset += 1;
1399
1400    let tick_index = read_i32_le(data, offset)?;
1401    offset += 4;
1402
1403    let total_amount = read_u64_le(data, offset)?;
1404    offset += 8;
1405
1406    let filled_amount = read_u64_le(data, offset)?;
1407    offset += 8;
1408
1409    let settled_amount_out = read_u64_le(data, offset)?;
1410
1411    Some(DexEvent::RaydiumClmmSettleLimitOrder(RaydiumClmmSettleLimitOrderEvent {
1412        metadata,
1413        pool_id,
1414        limit_order,
1415        zero_for_one,
1416        tick_index,
1417        total_amount,
1418        filled_amount,
1419        settled_amount_out,
1420    }))
1421}
1422
1423/// Parse Raydium CLMM UpdateRewardInfos event from pre-decoded data
1424#[inline(always)]
1425pub fn parse_update_reward_infos_from_data(
1426    data: &[u8],
1427    metadata: EventMetadata,
1428) -> Option<DexEvent> {
1429    let mut offset = 0;
1430    let mut reward_growth_global_x64 = [0u128; 3];
1431    for reward_growth in &mut reward_growth_global_x64 {
1432        *reward_growth = read_u128_le(data, offset)?;
1433        offset += 16;
1434    }
1435
1436    Some(DexEvent::RaydiumClmmUpdateRewardInfos(RaydiumClmmUpdateRewardInfosEvent {
1437        metadata,
1438        reward_growth_global_x64,
1439    }))
1440}
1441
1442/// Parse Raydium CLMM CreatePool event from pre-decoded data
1443#[inline(always)]
1444pub fn parse_create_pool_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
1445    let mut offset = 0;
1446
1447    let token_0_mint = read_pubkey(data, offset)?;
1448    offset += 32;
1449
1450    let token_1_mint = read_pubkey(data, offset)?;
1451    offset += 32;
1452
1453    let tick_spacing = read_u16_le(data, offset)?;
1454    offset += 2;
1455
1456    let pool = read_pubkey(data, offset)?;
1457    offset += 32;
1458
1459    let sqrt_price_x64 = read_u128_le(data, offset)?;
1460    offset += 16;
1461
1462    let tick = read_i32_le(data, offset)?;
1463    offset += 4;
1464
1465    let token_vault_0 = read_pubkey(data, offset)?;
1466    offset += 32;
1467
1468    let token_vault_1 = read_pubkey(data, offset)?;
1469
1470    Some(DexEvent::RaydiumClmmCreatePool(RaydiumClmmCreatePoolEvent {
1471        metadata,
1472        pool,
1473        token_0_mint,
1474        token_1_mint,
1475        tick_spacing,
1476        sqrt_price_x64,
1477        tick,
1478        token_vault_0,
1479        token_vault_1,
1480        fee_rate: 0,
1481        creator: Pubkey::default(),
1482        open_time: 0,
1483    }))
1484}
1485
1486/// Parse Raydium CLMM CollectPersonalFee event from pre-decoded data
1487#[inline(always)]
1488pub fn parse_collect_personal_fee_from_data(
1489    data: &[u8],
1490    metadata: EventMetadata,
1491) -> Option<DexEvent> {
1492    let mut offset = 0;
1493
1494    let position_nft_mint = read_pubkey(data, offset)?;
1495    offset += 32;
1496
1497    let recipient_token_account_0 = read_pubkey(data, offset)?;
1498    offset += 32;
1499
1500    let recipient_token_account_1 = read_pubkey(data, offset)?;
1501    offset += 32;
1502
1503    let amount_0 = read_u64_le(data, offset)?;
1504    offset += 8;
1505
1506    let amount_1 = read_u64_le(data, offset)?;
1507
1508    Some(DexEvent::RaydiumClmmCollectFee(RaydiumClmmCollectFeeEvent {
1509        metadata,
1510        pool_state: Pubkey::default(),
1511        position_nft_mint,
1512        recipient_token_account_0,
1513        recipient_token_account_1,
1514        amount_0,
1515        amount_1,
1516    }))
1517}
1518
1519/// Parse Raydium CLMM CollectProtocolFee event from pre-decoded data
1520#[inline(always)]
1521pub fn parse_collect_protocol_fee_from_data(
1522    data: &[u8],
1523    metadata: EventMetadata,
1524) -> Option<DexEvent> {
1525    let mut offset = 0;
1526
1527    let pool_state = read_pubkey(data, offset)?;
1528    offset += 32;
1529
1530    let recipient_token_account_0 = read_pubkey(data, offset)?;
1531    offset += 32;
1532
1533    let recipient_token_account_1 = read_pubkey(data, offset)?;
1534    offset += 32;
1535
1536    let amount_0 = read_u64_le(data, offset)?;
1537    offset += 8;
1538
1539    let amount_1 = read_u64_le(data, offset)?;
1540
1541    Some(DexEvent::RaydiumClmmCollectFee(RaydiumClmmCollectFeeEvent {
1542        metadata,
1543        pool_state,
1544        position_nft_mint: Pubkey::default(),
1545        recipient_token_account_0,
1546        recipient_token_account_1,
1547        amount_0,
1548        amount_1,
1549    }))
1550}
1551
1552/// Backward-compatible alias for callers compiled against the older parser API.
1553#[inline(always)]
1554pub fn parse_collect_fee_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
1555    parse_collect_personal_fee_from_data(data, metadata)
1556}
1557
1558#[cfg(test)]
1559mod tests {
1560    use super::*;
1561    use base64::{engine::general_purpose, Engine as _};
1562
1563    fn metadata() -> EventMetadata {
1564        EventMetadata {
1565            signature: Signature::default(),
1566            slot: 1,
1567            tx_index: 0,
1568            block_time_us: 0,
1569            grpc_recv_us: 0,
1570            recent_blockhash: None,
1571        }
1572    }
1573
1574    fn pk(seed: u8) -> Pubkey {
1575        Pubkey::new_from_array([seed; 32])
1576    }
1577
1578    fn push_pk(out: &mut Vec<u8>, key: Pubkey) {
1579        out.extend_from_slice(key.as_ref());
1580    }
1581
1582    fn program_data_log(raw: &[u8]) -> String {
1583        format!("Program data: {}", general_purpose::STANDARD.encode(raw))
1584    }
1585
1586    #[test]
1587    fn official_swap_event_discriminator_and_body_parse() {
1588        let pool = pk(1);
1589        let sender = pk(2);
1590        let token_account_0 = pk(3);
1591        let token_account_1 = pk(4);
1592        let mut raw = Vec::new();
1593        raw.extend_from_slice(&discriminators::SWAP);
1594        push_pk(&mut raw, pool);
1595        push_pk(&mut raw, sender);
1596        push_pk(&mut raw, token_account_0);
1597        push_pk(&mut raw, token_account_1);
1598        raw.extend_from_slice(&10u64.to_le_bytes());
1599        raw.extend_from_slice(&1u64.to_le_bytes());
1600        raw.extend_from_slice(&20u64.to_le_bytes());
1601        raw.extend_from_slice(&2u64.to_le_bytes());
1602        raw.push(1);
1603        raw.extend_from_slice(&123u128.to_le_bytes());
1604        raw.extend_from_slice(&456u128.to_le_bytes());
1605        raw.extend_from_slice(&(-77i32).to_le_bytes());
1606
1607        let event = parse_log(&program_data_log(&raw), Signature::default(), 1, 0, None, 0)
1608            .expect("swap event");
1609
1610        let DexEvent::RaydiumClmmSwap(swap) = event else {
1611            panic!("expected swap");
1612        };
1613        assert_eq!(swap.pool_state, pool);
1614        assert_eq!(swap.sender, sender);
1615        assert_eq!(swap.token_account_0, token_account_0);
1616        assert_eq!(swap.token_account_1, token_account_1);
1617        assert_eq!(swap.amount_0, 10);
1618        assert_eq!(swap.transfer_fee_0, 1);
1619        assert_eq!(swap.amount_1, 20);
1620        assert_eq!(swap.transfer_fee_1, 2);
1621        assert!(swap.zero_for_one);
1622        assert_eq!(swap.sqrt_price_x64, 123);
1623        assert_eq!(swap.liquidity, 456);
1624        assert_eq!(swap.tick, -77);
1625    }
1626
1627    #[test]
1628    fn instruction_swap_discriminator_is_not_treated_as_log_event() {
1629        let mut raw = Vec::new();
1630        raw.extend_from_slice(&[248, 198, 158, 145, 225, 117, 135, 200]);
1631        raw.resize(8 + 32 + 32 + 32 + 32 + 8 + 8 + 8 + 8 + 1 + 16 + 16 + 4, 0);
1632
1633        assert!(parse_log(&program_data_log(&raw), Signature::default(), 1, 0, None, 0).is_none());
1634    }
1635
1636    #[test]
1637    fn official_liquidity_and_create_events_parse() {
1638        let mut inc = Vec::new();
1639        push_pk(&mut inc, pk(5));
1640        inc.extend_from_slice(&100u128.to_le_bytes());
1641        inc.extend_from_slice(&11u64.to_le_bytes());
1642        inc.extend_from_slice(&22u64.to_le_bytes());
1643        inc.extend_from_slice(&3u64.to_le_bytes());
1644        inc.extend_from_slice(&4u64.to_le_bytes());
1645
1646        let DexEvent::RaydiumClmmIncreaseLiquidity(inc_event) =
1647            parse_increase_liquidity_from_data(&inc, metadata()).expect("increase")
1648        else {
1649            panic!("expected increase");
1650        };
1651        assert_eq!(inc_event.position_nft_mint, pk(5));
1652        assert_eq!(inc_event.liquidity, 100);
1653        assert_eq!(inc_event.amount_0, 11);
1654        assert_eq!(inc_event.amount_1, 22);
1655        assert_eq!(inc_event.amount_0_transfer_fee, 3);
1656        assert_eq!(inc_event.amount_1_transfer_fee, 4);
1657
1658        let mut dec = Vec::new();
1659        push_pk(&mut dec, pk(6));
1660        dec.extend_from_slice(&200u128.to_le_bytes());
1661        dec.extend_from_slice(&31u64.to_le_bytes());
1662        dec.extend_from_slice(&32u64.to_le_bytes());
1663        dec.extend_from_slice(&5u64.to_le_bytes());
1664        dec.extend_from_slice(&6u64.to_le_bytes());
1665        dec.extend_from_slice(
1666            &[7u64.to_le_bytes(), 8u64.to_le_bytes(), 9u64.to_le_bytes()].concat(),
1667        );
1668        dec.extend_from_slice(&10u64.to_le_bytes());
1669        dec.extend_from_slice(&11u64.to_le_bytes());
1670
1671        let DexEvent::RaydiumClmmDecreaseLiquidity(dec_event) =
1672            parse_decrease_liquidity_from_data(&dec, metadata()).expect("decrease")
1673        else {
1674            panic!("expected decrease");
1675        };
1676        assert_eq!(dec_event.position_nft_mint, pk(6));
1677        assert_eq!(dec_event.liquidity, 200);
1678        assert_eq!(dec_event.decrease_amount_0, 31);
1679        assert_eq!(dec_event.decrease_amount_1, 32);
1680        assert_eq!(dec_event.fee_amount_0, 5);
1681        assert_eq!(dec_event.fee_amount_1, 6);
1682        assert_eq!(dec_event.reward_amounts, [7, 8, 9]);
1683        assert_eq!(dec_event.transfer_fee_0, 10);
1684        assert_eq!(dec_event.transfer_fee_1, 11);
1685
1686        let mut create = Vec::new();
1687        push_pk(&mut create, pk(7));
1688        push_pk(&mut create, pk(8));
1689        create.extend_from_slice(&64u16.to_le_bytes());
1690        push_pk(&mut create, pk(9));
1691        create.extend_from_slice(&333u128.to_le_bytes());
1692        create.extend_from_slice(&(-12i32).to_le_bytes());
1693        push_pk(&mut create, pk(10));
1694        push_pk(&mut create, pk(11));
1695
1696        let DexEvent::RaydiumClmmCreatePool(create_event) =
1697            parse_create_pool_from_data(&create, metadata()).expect("create")
1698        else {
1699            panic!("expected create");
1700        };
1701        assert_eq!(create_event.token_0_mint, pk(7));
1702        assert_eq!(create_event.token_1_mint, pk(8));
1703        assert_eq!(create_event.tick_spacing, 64);
1704        assert_eq!(create_event.pool, pk(9));
1705        assert_eq!(create_event.sqrt_price_x64, 333);
1706        assert_eq!(create_event.tick, -12);
1707        assert_eq!(create_event.token_vault_0, pk(10));
1708        assert_eq!(create_event.token_vault_1, pk(11));
1709    }
1710
1711    #[test]
1712    fn official_collect_and_liquidity_change_events_parse() {
1713        let mut personal = Vec::new();
1714        push_pk(&mut personal, pk(12));
1715        push_pk(&mut personal, pk(13));
1716        push_pk(&mut personal, pk(14));
1717        personal.extend_from_slice(&70u64.to_le_bytes());
1718        personal.extend_from_slice(&80u64.to_le_bytes());
1719
1720        let DexEvent::RaydiumClmmCollectFee(personal_event) =
1721            parse_collect_personal_fee_from_data(&personal, metadata()).expect("personal")
1722        else {
1723            panic!("expected personal collect");
1724        };
1725        assert_eq!(personal_event.position_nft_mint, pk(12));
1726        assert_eq!(personal_event.recipient_token_account_0, pk(13));
1727        assert_eq!(personal_event.recipient_token_account_1, pk(14));
1728
1729        let mut protocol = Vec::new();
1730        push_pk(&mut protocol, pk(15));
1731        push_pk(&mut protocol, pk(16));
1732        push_pk(&mut protocol, pk(17));
1733        protocol.extend_from_slice(&90u64.to_le_bytes());
1734        protocol.extend_from_slice(&100u64.to_le_bytes());
1735
1736        let DexEvent::RaydiumClmmCollectFee(protocol_event) =
1737            parse_collect_protocol_fee_from_data(&protocol, metadata()).expect("protocol")
1738        else {
1739            panic!("expected protocol collect");
1740        };
1741        assert_eq!(protocol_event.pool_state, pk(15));
1742        assert_eq!(protocol_event.recipient_token_account_0, pk(16));
1743        assert_eq!(protocol_event.recipient_token_account_1, pk(17));
1744
1745        let mut change = Vec::new();
1746        push_pk(&mut change, pk(18));
1747        change.extend_from_slice(&1i32.to_le_bytes());
1748        change.extend_from_slice(&(-10i32).to_le_bytes());
1749        change.extend_from_slice(&10i32.to_le_bytes());
1750        change.extend_from_slice(&1234u128.to_le_bytes());
1751        change.extend_from_slice(&5678u128.to_le_bytes());
1752
1753        let DexEvent::RaydiumClmmLiquidityChange(change_event) =
1754            parse_liquidity_change_from_data(&change, metadata()).expect("liquidity change")
1755        else {
1756            panic!("expected liquidity change");
1757        };
1758        assert_eq!(change_event.pool_state, pk(18));
1759        assert_eq!(change_event.tick, 1);
1760        assert_eq!(change_event.tick_lower, -10);
1761        assert_eq!(change_event.tick_upper, 10);
1762        assert_eq!(change_event.liquidity_before, 1234);
1763        assert_eq!(change_event.liquidity_after, 5678);
1764    }
1765
1766    #[test]
1767    fn official_dynamic_fee_and_limit_order_events_parse() {
1768        let mut config = Vec::new();
1769        config.extend_from_slice(&3u16.to_le_bytes());
1770        push_pk(&mut config, pk(21));
1771        config.extend_from_slice(&100u32.to_le_bytes());
1772        config.extend_from_slice(&200u32.to_le_bytes());
1773        config.extend_from_slice(&64u16.to_le_bytes());
1774        config.extend_from_slice(&300u32.to_le_bytes());
1775        push_pk(&mut config, pk(22));
1776
1777        let DexEvent::RaydiumClmmConfigChange(config_event) =
1778            parse_config_change_from_data(&config, metadata()).expect("config")
1779        else {
1780            panic!("expected config change");
1781        };
1782        assert_eq!(config_event.index, 3);
1783        assert_eq!(config_event.owner, pk(21));
1784        assert_eq!(config_event.fund_owner, pk(22));
1785
1786        let mut personal_position = Vec::new();
1787        push_pk(&mut personal_position, pk(23));
1788        push_pk(&mut personal_position, pk(24));
1789        push_pk(&mut personal_position, pk(25));
1790        personal_position.extend_from_slice(&(-4i32).to_le_bytes());
1791        personal_position.extend_from_slice(&8i32.to_le_bytes());
1792        personal_position.extend_from_slice(&900u128.to_le_bytes());
1793        personal_position.extend_from_slice(&31u64.to_le_bytes());
1794        personal_position.extend_from_slice(&32u64.to_le_bytes());
1795        personal_position.extend_from_slice(&1u64.to_le_bytes());
1796        personal_position.extend_from_slice(&2u64.to_le_bytes());
1797
1798        let DexEvent::RaydiumClmmCreatePersonalPosition(position_event) =
1799            parse_create_personal_position_from_data(&personal_position, metadata())
1800                .expect("personal position")
1801        else {
1802            panic!("expected personal position");
1803        };
1804        assert_eq!(position_event.pool_state, pk(23));
1805        assert_eq!(position_event.minter, pk(24));
1806        assert_eq!(position_event.nft_owner, pk(25));
1807        assert_eq!(position_event.liquidity, 900);
1808
1809        let mut open_order = Vec::new();
1810        push_pk(&mut open_order, pk(26));
1811        push_pk(&mut open_order, pk(27));
1812        open_order.push(1);
1813        open_order.extend_from_slice(&(-40i32).to_le_bytes());
1814        open_order.extend_from_slice(&1_000u64.to_le_bytes());
1815        open_order.extend_from_slice(&5u64.to_le_bytes());
1816
1817        let DexEvent::RaydiumClmmOpenLimitOrder(open_event) =
1818            parse_open_limit_order_from_data(&open_order, metadata()).expect("open order")
1819        else {
1820            panic!("expected open limit order");
1821        };
1822        assert_eq!(open_event.pool_id, pk(26));
1823        assert_eq!(open_event.limit_order, pk(27));
1824        assert!(open_event.zero_for_one);
1825        assert_eq!(open_event.tick_index, -40);
1826        assert_eq!(open_event.total_amount, 1_000);
1827        assert_eq!(open_event.transfer_fee, 5);
1828
1829        let mut settle_order = Vec::new();
1830        push_pk(&mut settle_order, pk(28));
1831        push_pk(&mut settle_order, pk(29));
1832        settle_order.push(0);
1833        settle_order.extend_from_slice(&41i32.to_le_bytes());
1834        settle_order.extend_from_slice(&2_000u64.to_le_bytes());
1835        settle_order.extend_from_slice(&1_500u64.to_le_bytes());
1836        settle_order.extend_from_slice(&3_000u64.to_le_bytes());
1837
1838        let DexEvent::RaydiumClmmSettleLimitOrder(settle_event) =
1839            parse_settle_limit_order_from_data(&settle_order, metadata()).expect("settle order")
1840        else {
1841            panic!("expected settle limit order");
1842        };
1843        assert_eq!(settle_event.pool_id, pk(28));
1844        assert!(!settle_event.zero_for_one);
1845        assert_eq!(settle_event.settled_amount_out, 3_000);
1846
1847        let mut reward = Vec::new();
1848        reward.extend_from_slice(&10u128.to_le_bytes());
1849        reward.extend_from_slice(&20u128.to_le_bytes());
1850        reward.extend_from_slice(&30u128.to_le_bytes());
1851
1852        let DexEvent::RaydiumClmmUpdateRewardInfos(reward_event) =
1853            parse_update_reward_infos_from_data(&reward, metadata()).expect("reward")
1854        else {
1855            panic!("expected reward infos");
1856        };
1857        assert_eq!(reward_event.reward_growth_global_x64, [10, 20, 30]);
1858    }
1859}