Skip to main content

sol_parser_sdk/logs/
raydium_cpmm.rs

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