Skip to main content

sol_parser_sdk/logs/
raydium_cpmm.rs

1//! Raydium CPMM 日志解析器
2//!
3//! 使用 match discriminator 模式解析 Raydium CPMM 事件
4
5use super::utils::*;
6use crate::core::events::*;
7use solana_sdk::{pubkey::Pubkey, signature::Signature};
8
9/// Raydium CPMM discriminator 常量
10pub mod discriminators {
11    pub const SWAP_BASE_IN: [u8; 8] = [143, 190, 90, 218, 196, 30, 51, 222];
12    pub const SWAP_BASE_OUT: [u8; 8] = [55, 217, 98, 86, 163, 74, 180, 173];
13    pub const CREATE_POOL: [u8; 8] = [233, 146, 209, 142, 207, 104, 64, 188];
14    pub const DEPOSIT: [u8; 8] = [242, 35, 198, 137, 82, 225, 242, 182];
15    pub const WITHDRAW: [u8; 8] = [183, 18, 70, 156, 148, 109, 161, 34];
16}
17
18/// Raydium CPMM 程序 ID
19pub const PROGRAM_ID: &str = "CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C";
20
21/// 检查日志是否来自 Raydium CPMM 程序
22pub fn is_raydium_cpmm_log(log: &str) -> bool {
23    log.contains(&format!("Program {} invoke", PROGRAM_ID))
24        || log.contains(&format!("Program {} success", PROGRAM_ID))
25        || (log.contains("raydium") && log.contains("cpmm"))
26}
27
28/// 主要的 Raydium CPMM 日志解析函数
29#[inline(always)] // 零延迟优化:内联热路径
30pub fn parse_log(
31    log: &str,
32    signature: Signature,
33    slot: u64,
34    tx_index: u64,
35    block_time_us: Option<i64>,
36    grpc_recv_us: i64,
37) -> Option<DexEvent> {
38    parse_structured_log(log, signature, slot, tx_index, block_time_us, grpc_recv_us)
39}
40
41/// 结构化日志解析(基于 Program data)
42fn parse_structured_log(
43    log: &str,
44    signature: Signature,
45    slot: u64,
46    tx_index: u64,
47    block_time_us: Option<i64>,
48    grpc_recv_us: i64,
49) -> Option<DexEvent> {
50    let program_data = extract_program_data(log)?;
51    if program_data.len() < 8 {
52        return None;
53    }
54
55    let discriminator: [u8; 8] = program_data[0..8].try_into().ok()?;
56    let data = &program_data[8..];
57
58    match discriminator {
59        discriminators::SWAP_BASE_IN => {
60            parse_swap_base_in_event(data, signature, slot, tx_index, block_time_us, grpc_recv_us)
61        }
62        discriminators::SWAP_BASE_OUT => {
63            parse_swap_base_out_event(data, signature, slot, tx_index, block_time_us, grpc_recv_us)
64        }
65        discriminators::CREATE_POOL => {
66            parse_create_pool_event(data, signature, slot, tx_index, block_time_us, grpc_recv_us)
67        }
68        discriminators::DEPOSIT => {
69            parse_deposit_event(data, signature, slot, tx_index, block_time_us, grpc_recv_us)
70        }
71        discriminators::WITHDRAW => {
72            parse_withdraw_event(data, signature, slot, tx_index, block_time_us, grpc_recv_us)
73        }
74        _ => None,
75    }
76}
77
78// =============================================================================
79// Public from_data parsers - Accept pre-decoded data, eliminate double decode
80// =============================================================================
81
82/// Parse Raydium CPMM SwapBaseIn event from pre-decoded data
83#[inline(always)]
84pub fn parse_swap_base_in_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
85    let mut offset = 0;
86
87    let pool_state = read_pubkey(data, offset)?;
88    offset += 32;
89
90    let _user = read_pubkey(data, offset)?;
91    offset += 32;
92
93    let amount_in = read_u64_le(data, offset)?;
94    offset += 8;
95
96    let _minimum_amount_out = read_u64_le(data, offset)?;
97    offset += 8;
98
99    let amount_out = read_u64_le(data, offset)?;
100    offset += 8;
101
102    let is_base_input = read_bool(data, offset)?;
103
104    Some(DexEvent::RaydiumCpmmSwap(RaydiumCpmmSwapEvent {
105        metadata,
106        pool_id: pool_state,
107        input_vault_before: 0,
108        output_vault_before: 0,
109        input_amount: amount_in,
110        output_amount: amount_out,
111        input_transfer_fee: 0,
112        output_transfer_fee: 0,
113        base_input: is_base_input,
114    }))
115}
116
117/// Parse Raydium CPMM SwapBaseOut event from pre-decoded data
118#[inline(always)]
119pub fn parse_swap_base_out_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
120    let mut offset = 0;
121
122    let pool_state = read_pubkey(data, offset)?;
123    offset += 32;
124
125    let _user = read_pubkey(data, offset)?;
126    offset += 32;
127
128    let _maximum_amount_in = read_u64_le(data, offset)?;
129    offset += 8;
130
131    let amount_out = read_u64_le(data, offset)?;
132    offset += 8;
133
134    let amount_in = read_u64_le(data, offset)?;
135    offset += 8;
136
137    let is_base_output = read_bool(data, offset)?;
138
139    Some(DexEvent::RaydiumCpmmSwap(RaydiumCpmmSwapEvent {
140        metadata,
141        pool_id: pool_state,
142        input_vault_before: 0,
143        output_vault_before: 0,
144        input_amount: amount_in,
145        output_amount: amount_out,
146        input_transfer_fee: 0,
147        output_transfer_fee: 0,
148        base_input: !is_base_output,
149    }))
150}
151
152/// Parse Raydium CPMM CreatePool event from pre-decoded data
153#[inline(always)]
154pub fn parse_create_pool_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
155    let mut offset = 0;
156
157    let pool_state = read_pubkey(data, offset)?;
158    offset += 32;
159
160    let _token_0_mint = read_pubkey(data, offset)?;
161    offset += 32;
162
163    let _token_1_mint = read_pubkey(data, offset)?;
164    offset += 32;
165
166    let creator = read_pubkey(data, offset)?;
167    offset += 32;
168
169    let initial_amount_0 = read_u64_le(data, offset)?;
170    offset += 8;
171
172    let initial_amount_1 = read_u64_le(data, offset)?;
173
174    Some(DexEvent::RaydiumCpmmInitialize(RaydiumCpmmInitializeEvent {
175        metadata,
176        pool: pool_state,
177        creator,
178        init_amount0: initial_amount_0,
179        init_amount1: initial_amount_1,
180    }))
181}
182
183/// Parse Raydium CPMM Deposit event from pre-decoded data
184#[inline(always)]
185pub fn parse_deposit_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
186    let mut offset = 0;
187
188    let pool_state = read_pubkey(data, offset)?;
189    offset += 32;
190
191    let user = read_pubkey(data, offset)?;
192    offset += 32;
193
194    let lp_token_amount = read_u64_le(data, offset)?;
195    offset += 8;
196
197    let token_0_amount = read_u64_le(data, offset)?;
198    offset += 8;
199
200    let token_1_amount = read_u64_le(data, offset)?;
201
202    Some(DexEvent::RaydiumCpmmDeposit(RaydiumCpmmDepositEvent {
203        metadata,
204        pool: pool_state,
205        user,
206        lp_token_amount,
207        token0_amount: token_0_amount,
208        token1_amount: token_1_amount,
209    }))
210}
211
212/// Parse Raydium CPMM Withdraw event from pre-decoded data
213#[inline(always)]
214pub fn parse_withdraw_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
215    let mut offset = 0;
216
217    let pool_state = read_pubkey(data, offset)?;
218    offset += 32;
219
220    let user = read_pubkey(data, offset)?;
221    offset += 32;
222
223    let lp_token_amount = read_u64_le(data, offset)?;
224    offset += 8;
225
226    let token_0_amount = read_u64_le(data, offset)?;
227    offset += 8;
228
229    let token_1_amount = read_u64_le(data, offset)?;
230
231    Some(DexEvent::RaydiumCpmmWithdraw(RaydiumCpmmWithdrawEvent {
232        metadata,
233        pool: pool_state,
234        user,
235        lp_token_amount,
236        token0_amount: token_0_amount,
237        token1_amount: token_1_amount,
238    }))
239}
240
241/// 解析 Base In 交换事件
242fn parse_swap_base_in_event(
243    data: &[u8],
244    signature: Signature,
245    slot: u64,
246    tx_index: u64,
247    block_time_us: Option<i64>,
248    grpc_recv_us: i64,
249) -> Option<DexEvent> {
250    let mut offset = 0;
251
252    let pool_state = read_pubkey(data, offset)?;
253    offset += 32;
254
255    let user = read_pubkey(data, offset)?;
256    offset += 32;
257
258    let amount_in = read_u64_le(data, offset)?;
259    offset += 8;
260
261    let minimum_amount_out = read_u64_le(data, offset)?;
262    offset += 8;
263
264    let amount_out = read_u64_le(data, offset)?;
265    offset += 8;
266
267    let is_base_input = read_bool(data, offset)?;
268
269    let metadata =
270        create_metadata_simple(signature, slot, tx_index, block_time_us, pool_state, grpc_recv_us);
271
272    Some(DexEvent::RaydiumCpmmSwap(RaydiumCpmmSwapEvent {
273        metadata,
274
275        // IDL SwapEvent 事件字段
276        pool_id: pool_state,
277        input_vault_before: 0,  // 需要从日志数据解析
278        output_vault_before: 0, // 需要从日志数据解析
279        input_amount: amount_in,
280        output_amount: amount_out,
281        input_transfer_fee: 0,  // 需要从日志数据解析
282        output_transfer_fee: 0, // 需要从日志数据解析
283        base_input: is_base_input,
284        // 暂时注释,以后会用,AI禁止改动
285        // 指令参数字段
286        // amount_in,
287        // minimum_amount_out,
288
289        // 指令账户字段 - 从account_filler填充
290        // payer: user,
291        // authority: Pubkey::default(),
292        // amm_config: Pubkey::default(),
293        // pool_state,
294        // input_token_account: Pubkey::default(),
295        // output_token_account: Pubkey::default(),
296        // input_vault: Pubkey::default(),
297        // output_vault: Pubkey::default(),
298        // input_token_mint: Pubkey::default(),
299        // output_token_mint: Pubkey::default(),
300    }))
301}
302
303/// 解析 Base Out 交换事件
304fn parse_swap_base_out_event(
305    data: &[u8],
306    signature: Signature,
307    slot: u64,
308    tx_index: u64,
309    block_time_us: Option<i64>,
310    grpc_recv_us: i64,
311) -> Option<DexEvent> {
312    let mut offset = 0;
313
314    let pool_state = read_pubkey(data, offset)?;
315    offset += 32;
316
317    let user = read_pubkey(data, offset)?;
318    offset += 32;
319
320    let maximum_amount_in = read_u64_le(data, offset)?;
321    offset += 8;
322
323    let amount_out = read_u64_le(data, offset)?;
324    offset += 8;
325
326    let amount_in = read_u64_le(data, offset)?;
327    offset += 8;
328
329    let is_base_output = read_bool(data, offset)?;
330
331    let metadata =
332        create_metadata_simple(signature, slot, tx_index, block_time_us, pool_state, grpc_recv_us);
333
334    Some(DexEvent::RaydiumCpmmSwap(RaydiumCpmmSwapEvent {
335        metadata,
336
337        // IDL SwapEvent 事件字段
338        pool_id: pool_state,
339        input_vault_before: 0,  // 需要从日志数据解析
340        output_vault_before: 0, // 需要从日志数据解析
341        input_amount: amount_in,
342        output_amount: amount_out,
343        input_transfer_fee: 0,  // 需要从日志数据解析
344        output_transfer_fee: 0, // 需要从日志数据解析
345        base_input: !is_base_output,
346        // 暂时注释,以后会用,AI禁止改动
347        // 指令参数字段
348        // amount_in: maximum_amount_in,
349        // minimum_amount_out: amount_out,
350
351        // 指令账户字段 - 从account_filler填充
352        // payer: user,
353        // authority: Pubkey::default(),
354        // amm_config: Pubkey::default(),
355        // pool_state,
356        // input_token_account: Pubkey::default(),
357        // output_token_account: Pubkey::default(),
358        // input_vault: Pubkey::default(),
359        // output_vault: Pubkey::default(),
360        // input_token_mint: Pubkey::default(),
361        // output_token_mint: Pubkey::default(),
362    }))
363}
364
365/// 解析池创建事件
366fn parse_create_pool_event(
367    data: &[u8],
368    signature: Signature,
369    slot: u64,
370    tx_index: u64,
371    block_time_us: Option<i64>,
372    grpc_recv_us: i64,
373) -> Option<DexEvent> {
374    let mut offset = 0;
375
376    let pool_state = read_pubkey(data, offset)?;
377    offset += 32;
378
379    let token_0_mint = read_pubkey(data, offset)?;
380    offset += 32;
381
382    let token_1_mint = read_pubkey(data, offset)?;
383    offset += 32;
384
385    let creator = read_pubkey(data, offset)?;
386    offset += 32;
387
388    let initial_amount_0 = read_u64_le(data, offset)?;
389    offset += 8;
390
391    let initial_amount_1 = read_u64_le(data, offset)?;
392
393    let metadata =
394        create_metadata_simple(signature, slot, tx_index, block_time_us, pool_state, grpc_recv_us);
395
396    Some(DexEvent::RaydiumCpmmInitialize(RaydiumCpmmInitializeEvent {
397        metadata,
398        pool: pool_state,
399        creator,
400        init_amount0: initial_amount_0,
401        init_amount1: initial_amount_1,
402    }))
403}
404
405/// 解析存款事件
406fn parse_deposit_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 mut offset = 0;
415
416    let pool_state = read_pubkey(data, offset)?;
417    offset += 32;
418
419    let user = read_pubkey(data, offset)?;
420    offset += 32;
421
422    let lp_token_amount = read_u64_le(data, offset)?;
423    offset += 8;
424
425    let token_0_amount = read_u64_le(data, offset)?;
426    offset += 8;
427
428    let token_1_amount = read_u64_le(data, offset)?;
429
430    let metadata =
431        create_metadata_simple(signature, slot, tx_index, block_time_us, pool_state, grpc_recv_us);
432
433    Some(DexEvent::RaydiumCpmmDeposit(RaydiumCpmmDepositEvent {
434        metadata,
435        pool: pool_state,
436        user,
437        lp_token_amount,
438        token0_amount: token_0_amount,
439        token1_amount: token_1_amount,
440    }))
441}
442
443/// 解析提款事件
444fn parse_withdraw_event(
445    data: &[u8],
446    signature: Signature,
447    slot: u64,
448    tx_index: u64,
449    block_time_us: Option<i64>,
450    grpc_recv_us: i64,
451) -> Option<DexEvent> {
452    let mut offset = 0;
453
454    let pool_state = read_pubkey(data, offset)?;
455    offset += 32;
456
457    let user = read_pubkey(data, offset)?;
458    offset += 32;
459
460    let lp_token_amount = read_u64_le(data, offset)?;
461    offset += 8;
462
463    let token_0_amount = read_u64_le(data, offset)?;
464    offset += 8;
465
466    let token_1_amount = read_u64_le(data, offset)?;
467
468    let metadata =
469        create_metadata_simple(signature, slot, tx_index, block_time_us, pool_state, grpc_recv_us);
470
471    Some(DexEvent::RaydiumCpmmWithdraw(RaydiumCpmmWithdrawEvent {
472        metadata,
473        pool: pool_state,
474        user,
475        lp_token_amount,
476        token0_amount: token_0_amount,
477        token1_amount: token_1_amount,
478    }))
479}
480
481/// 文本回退解析
482fn parse_text_log(
483    log: &str,
484    signature: Signature,
485    slot: u64,
486    tx_index: u64,
487    block_time_us: Option<i64>,
488    grpc_recv_us: i64,
489) -> Option<DexEvent> {
490    use super::utils::text_parser::*;
491
492    if log.contains("swap") || log.contains("Swap") {
493        if log.contains("base_in") {
494            return parse_swap_base_in_from_text(
495                log,
496                signature,
497                slot,
498                tx_index,
499                block_time_us,
500                grpc_recv_us,
501            );
502        } else if log.contains("base_out") {
503            return parse_swap_base_out_from_text(
504                log,
505                signature,
506                slot,
507                tx_index,
508                block_time_us,
509                grpc_recv_us,
510            );
511        } else {
512            return parse_swap_base_in_from_text(
513                log,
514                signature,
515                slot,
516                tx_index,
517                block_time_us,
518                grpc_recv_us,
519            );
520        }
521    }
522
523    if log.contains("deposit") || log.contains("Deposit") {
524        return parse_deposit_from_text(
525            log,
526            signature,
527            slot,
528            tx_index,
529            block_time_us,
530            grpc_recv_us,
531        );
532    }
533
534    if log.contains("withdraw") || log.contains("Withdraw") {
535        return parse_withdraw_from_text(
536            log,
537            signature,
538            slot,
539            tx_index,
540            block_time_us,
541            grpc_recv_us,
542        );
543    }
544
545    if log.contains("create") && log.contains("pool") {
546        return parse_create_pool_from_text(
547            log,
548            signature,
549            slot,
550            tx_index,
551            block_time_us,
552            grpc_recv_us,
553        );
554    }
555
556    None
557}
558
559/// 从文本解析 Base In 交换事件
560fn parse_swap_base_in_from_text(
561    log: &str,
562    signature: Signature,
563    slot: u64,
564    tx_index: u64,
565    block_time_us: Option<i64>,
566    grpc_recv_us: i64,
567) -> Option<DexEvent> {
568    use super::utils::text_parser::*;
569
570    let metadata = create_metadata_simple(
571        signature,
572        slot,
573        tx_index,
574        block_time_us,
575        Pubkey::default(),
576        grpc_recv_us,
577    );
578
579    Some(DexEvent::RaydiumCpmmSwap(RaydiumCpmmSwapEvent {
580        metadata,
581
582        // IDL SwapEvent 事件字段
583        pool_id: Pubkey::default(),
584        input_vault_before: 0,
585        output_vault_before: 0,
586        input_amount: extract_number_from_text(log, "amount_in").unwrap_or(1_000_000_000),
587        output_amount: extract_number_from_text(log, "amount_out").unwrap_or(950_000_000),
588        input_transfer_fee: 0,
589        output_transfer_fee: 0,
590        base_input: true,
591        // 暂时注释,以后会用,AI禁止改动
592        // 指令参数字段
593        // amount_in: extract_number_from_text(log, "amount_in").unwrap_or(1_000_000_000),
594        // minimum_amount_out: extract_number_from_text(log, "amount_out").unwrap_or(950_000_000),
595
596        // 指令账户字段
597        // payer: Pubkey::default(),
598        // authority: Pubkey::default(),
599        // amm_config: Pubkey::default(),
600        // pool_state: Pubkey::default(),
601        // input_token_account: Pubkey::default(),
602        // output_token_account: Pubkey::default(),
603        // input_vault: Pubkey::default(),
604        // output_vault: Pubkey::default(),
605        // input_token_mint: Pubkey::default(),
606        // output_token_mint: Pubkey::default(),
607    }))
608}
609
610/// 从文本解析 Base Out 交换事件
611fn parse_swap_base_out_from_text(
612    log: &str,
613    signature: Signature,
614    slot: u64,
615    tx_index: u64,
616    block_time_us: Option<i64>,
617    grpc_recv_us: i64,
618) -> Option<DexEvent> {
619    use super::utils::text_parser::*;
620
621    let metadata = create_metadata_simple(
622        signature,
623        slot,
624        tx_index,
625        block_time_us,
626        Pubkey::default(),
627        grpc_recv_us,
628    );
629
630    Some(DexEvent::RaydiumCpmmSwap(RaydiumCpmmSwapEvent {
631        metadata,
632
633        // IDL SwapEvent 事件字段
634        pool_id: Pubkey::default(),
635        input_vault_before: 0,
636        output_vault_before: 0,
637        input_amount: extract_number_from_text(log, "amount_in").unwrap_or(1_000_000_000),
638        output_amount: extract_number_from_text(log, "amount_out").unwrap_or(950_000_000),
639        input_transfer_fee: 0,
640        output_transfer_fee: 0,
641        base_input: false,
642        // 暂时注释,以后会用,AI禁止改动
643        // 指令参数字段
644        // amount_in: extract_number_from_text(log, "amount_in").unwrap_or(1_000_000_000),
645        // minimum_amount_out: extract_number_from_text(log, "amount_out").unwrap_or(950_000_000),
646
647        // 指令账户字段
648        // payer: Pubkey::default(),
649        // authority: Pubkey::default(),
650        // amm_config: Pubkey::default(),
651        // pool_state: Pubkey::default(),
652        // input_token_account: Pubkey::default(),
653        // output_token_account: Pubkey::default(),
654        // input_vault: Pubkey::default(),
655        // output_vault: Pubkey::default(),
656        // input_token_mint: Pubkey::default(),
657        // output_token_mint: Pubkey::default(),
658    }))
659}
660
661/// 从文本解析池创建事件
662fn parse_create_pool_from_text(
663    log: &str,
664    signature: Signature,
665    slot: u64,
666    tx_index: u64,
667    block_time_us: Option<i64>,
668    grpc_recv_us: i64,
669) -> Option<DexEvent> {
670    use super::utils::text_parser::*;
671
672    let metadata = create_metadata_simple(
673        signature,
674        slot,
675        tx_index,
676        block_time_us,
677        Pubkey::default(),
678        grpc_recv_us,
679    );
680
681    Some(DexEvent::RaydiumCpmmInitialize(RaydiumCpmmInitializeEvent {
682        metadata,
683        pool: Pubkey::default(),
684        creator: Pubkey::default(),
685        init_amount0: extract_number_from_text(log, "amount_0").unwrap_or(1_000_000_000),
686        init_amount1: extract_number_from_text(log, "amount_1").unwrap_or(1_000_000_000),
687    }))
688}
689
690/// 从文本解析存款事件
691fn parse_deposit_from_text(
692    log: &str,
693    signature: Signature,
694    slot: u64,
695    tx_index: u64,
696    block_time_us: Option<i64>,
697    grpc_recv_us: i64,
698) -> Option<DexEvent> {
699    use super::utils::text_parser::*;
700
701    let metadata = create_metadata_simple(
702        signature,
703        slot,
704        tx_index,
705        block_time_us,
706        Pubkey::default(),
707        grpc_recv_us,
708    );
709
710    Some(DexEvent::RaydiumCpmmDeposit(RaydiumCpmmDepositEvent {
711        metadata,
712        pool: Pubkey::default(),
713        user: Pubkey::default(),
714        lp_token_amount: extract_number_from_text(log, "lp_token").unwrap_or(1_000_000),
715        token0_amount: extract_number_from_text(log, "token_0").unwrap_or(1_000_000_000),
716        token1_amount: extract_number_from_text(log, "token_1").unwrap_or(1_000_000_000),
717    }))
718}
719
720/// 从文本解析提款事件
721fn parse_withdraw_from_text(
722    log: &str,
723    signature: Signature,
724    slot: u64,
725    tx_index: u64,
726    block_time_us: Option<i64>,
727    grpc_recv_us: i64,
728) -> Option<DexEvent> {
729    use super::utils::text_parser::*;
730
731    let metadata = create_metadata_simple(
732        signature,
733        slot,
734        tx_index,
735        block_time_us,
736        Pubkey::default(),
737        grpc_recv_us,
738    );
739
740    Some(DexEvent::RaydiumCpmmWithdraw(RaydiumCpmmWithdrawEvent {
741        metadata,
742        pool: Pubkey::default(),
743        user: Pubkey::default(),
744        lp_token_amount: extract_number_from_text(log, "lp_token").unwrap_or(1_000_000),
745        token0_amount: extract_number_from_text(log, "token_0").unwrap_or(1_000_000_000),
746        token1_amount: extract_number_from_text(log, "token_1").unwrap_or(1_000_000_000),
747    }))
748}