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