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