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