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]
30pub fn parse_log(log: &str, signature: Signature, slot: u64, tx_index: u64, block_time: Option<i64>, grpc_recv_us: i64) -> Option<DexEvent> {
31    parse_structured_log(log, signature, slot, tx_index, block_time, 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: 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, grpc_recv_us)
54        },
55        discriminators::SWAP_BASE_OUT => {
56            parse_swap_base_out_event(data, signature, slot, tx_index, block_time, grpc_recv_us)
57        },
58        discriminators::CREATE_POOL => {
59            parse_create_pool_event(data, signature, slot, tx_index, block_time, grpc_recv_us)
60        },
61        discriminators::DEPOSIT => {
62            parse_deposit_event(data, signature, slot, tx_index, block_time, grpc_recv_us)
63        },
64        discriminators::WITHDRAW => {
65            parse_withdraw_event(data, signature, slot, tx_index, block_time, grpc_recv_us)
66        },
67        _ => None,
68    }
69}
70
71/// 解析 Base In 交换事件
72fn parse_swap_base_in_event(
73    data: &[u8],
74    signature: Signature,
75    slot: u64,
76    tx_index: u64,
77    block_time: Option<i64>,
78    grpc_recv_us: i64,
79) -> Option<DexEvent> {
80    let mut offset = 0;
81
82    let pool_state = read_pubkey(data, offset)?;
83    offset += 32;
84
85    let user = read_pubkey(data, offset)?;
86    offset += 32;
87
88    let amount_in = read_u64_le(data, offset)?;
89    offset += 8;
90
91    let minimum_amount_out = read_u64_le(data, offset)?;
92    offset += 8;
93
94    let amount_out = read_u64_le(data, offset)?;
95    offset += 8;
96
97    let is_base_input = read_bool(data, offset)?;
98
99    let metadata = create_metadata_simple(signature, slot, tx_index, block_time, pool_state, grpc_recv_us);
100
101    Some(DexEvent::RaydiumCpmmSwap(RaydiumCpmmSwapEvent {
102        metadata,
103
104        // IDL SwapEvent 事件字段
105        pool_id: pool_state,
106        input_vault_before: 0, // 需要从日志数据解析
107        output_vault_before: 0, // 需要从日志数据解析
108        input_amount: amount_in,
109        output_amount: amount_out,
110        input_transfer_fee: 0, // 需要从日志数据解析
111        output_transfer_fee: 0, // 需要从日志数据解析
112        base_input: is_base_input,
113
114        // 暂时注释,以后会用,AI禁止改动
115        // 指令参数字段
116        // amount_in,
117        // minimum_amount_out,
118
119        // 指令账户字段 - 从account_filler填充
120        // payer: user,
121        // authority: Pubkey::default(),
122        // amm_config: Pubkey::default(),
123        // pool_state,
124        // input_token_account: Pubkey::default(),
125        // output_token_account: Pubkey::default(),
126        // input_vault: Pubkey::default(),
127        // output_vault: Pubkey::default(),
128        // input_token_mint: Pubkey::default(),
129        // output_token_mint: Pubkey::default(),
130    }))
131}
132
133/// 解析 Base Out 交换事件
134fn parse_swap_base_out_event(
135    data: &[u8],
136    signature: Signature,
137    slot: u64,
138    tx_index: u64,
139    block_time: Option<i64>,
140    grpc_recv_us: i64,
141) -> Option<DexEvent> {
142    let mut offset = 0;
143
144    let pool_state = read_pubkey(data, offset)?;
145    offset += 32;
146
147    let user = read_pubkey(data, offset)?;
148    offset += 32;
149
150    let maximum_amount_in = read_u64_le(data, offset)?;
151    offset += 8;
152
153    let amount_out = read_u64_le(data, offset)?;
154    offset += 8;
155
156    let amount_in = read_u64_le(data, offset)?;
157    offset += 8;
158
159    let is_base_output = read_bool(data, offset)?;
160
161    let metadata = create_metadata_simple(signature, slot, tx_index, block_time, pool_state, grpc_recv_us);
162
163    Some(DexEvent::RaydiumCpmmSwap(RaydiumCpmmSwapEvent {
164        metadata,
165
166        // IDL SwapEvent 事件字段
167        pool_id: pool_state,
168        input_vault_before: 0, // 需要从日志数据解析
169        output_vault_before: 0, // 需要从日志数据解析
170        input_amount: amount_in,
171        output_amount: amount_out,
172        input_transfer_fee: 0, // 需要从日志数据解析
173        output_transfer_fee: 0, // 需要从日志数据解析
174        base_input: !is_base_output,
175
176        // 暂时注释,以后会用,AI禁止改动
177        // 指令参数字段
178        // amount_in: maximum_amount_in,
179        // minimum_amount_out: amount_out,
180
181        // 指令账户字段 - 从account_filler填充
182        // payer: user,
183        // authority: Pubkey::default(),
184        // amm_config: Pubkey::default(),
185        // pool_state,
186        // input_token_account: Pubkey::default(),
187        // output_token_account: Pubkey::default(),
188        // input_vault: Pubkey::default(),
189        // output_vault: Pubkey::default(),
190        // input_token_mint: Pubkey::default(),
191        // output_token_mint: Pubkey::default(),
192    }))
193}
194
195/// 解析池创建事件
196fn parse_create_pool_event(
197    data: &[u8],
198    signature: Signature,
199    slot: u64,
200    tx_index: u64,
201    block_time: Option<i64>,
202    grpc_recv_us: i64,
203) -> Option<DexEvent> {
204    let mut offset = 0;
205
206    let pool_state = read_pubkey(data, offset)?;
207    offset += 32;
208
209    let token_0_mint = read_pubkey(data, offset)?;
210    offset += 32;
211
212    let token_1_mint = read_pubkey(data, offset)?;
213    offset += 32;
214
215    let creator = read_pubkey(data, offset)?;
216    offset += 32;
217
218    let initial_amount_0 = read_u64_le(data, offset)?;
219    offset += 8;
220
221    let initial_amount_1 = read_u64_le(data, offset)?;
222
223    let metadata = create_metadata_simple(signature, slot, tx_index, block_time, pool_state, grpc_recv_us);
224
225    Some(DexEvent::RaydiumCpmmInitialize(RaydiumCpmmInitializeEvent {
226        metadata,
227        pool: pool_state,
228        creator,
229        init_amount0: initial_amount_0,
230        init_amount1: initial_amount_1,
231    }))
232}
233
234/// 解析存款事件
235fn parse_deposit_event(
236    data: &[u8],
237    signature: Signature,
238    slot: u64,
239    tx_index: u64,
240    block_time: 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 lp_token_amount = read_u64_le(data, offset)?;
252    offset += 8;
253
254    let token_0_amount = read_u64_le(data, offset)?;
255    offset += 8;
256
257    let token_1_amount = read_u64_le(data, offset)?;
258
259    let metadata = create_metadata_simple(signature, slot, tx_index, block_time, pool_state, grpc_recv_us);
260
261    Some(DexEvent::RaydiumCpmmDeposit(RaydiumCpmmDepositEvent {
262        metadata,
263        pool: pool_state,
264        user,
265        lp_token_amount,
266        token0_amount: token_0_amount,
267        token1_amount: token_1_amount,
268    }))
269}
270
271/// 解析提款事件
272fn parse_withdraw_event(
273    data: &[u8],
274    signature: Signature,
275    slot: u64,
276    tx_index: u64,
277    block_time: Option<i64>,
278    grpc_recv_us: i64,
279) -> Option<DexEvent> {
280    let mut offset = 0;
281
282    let pool_state = read_pubkey(data, offset)?;
283    offset += 32;
284
285    let user = read_pubkey(data, offset)?;
286    offset += 32;
287
288    let lp_token_amount = read_u64_le(data, offset)?;
289    offset += 8;
290
291    let token_0_amount = read_u64_le(data, offset)?;
292    offset += 8;
293
294    let token_1_amount = read_u64_le(data, offset)?;
295
296    let metadata = create_metadata_simple(signature, slot, tx_index, block_time, pool_state, grpc_recv_us);
297
298    Some(DexEvent::RaydiumCpmmWithdraw(RaydiumCpmmWithdrawEvent {
299        metadata,
300        pool: pool_state,
301        user,
302        lp_token_amount,
303        token0_amount: token_0_amount,
304        token1_amount: token_1_amount,
305    }))
306}
307
308/// 文本回退解析
309fn parse_text_log(
310    log: &str,
311    signature: Signature,
312    slot: u64,
313    tx_index: u64,
314    block_time: Option<i64>,
315    grpc_recv_us: i64,
316) -> Option<DexEvent> {
317    use super::utils::text_parser::*;
318
319    if log.contains("swap") || log.contains("Swap") {
320        if log.contains("base_in") {
321            return parse_swap_base_in_from_text(log, signature, slot, tx_index, block_time, grpc_recv_us);
322        } else if log.contains("base_out") {
323            return parse_swap_base_out_from_text(log, signature, slot, tx_index, block_time, grpc_recv_us);
324        } else {
325            return parse_swap_base_in_from_text(log, signature, slot, tx_index, block_time, grpc_recv_us);
326        }
327    }
328
329    if log.contains("deposit") || log.contains("Deposit") {
330        return parse_deposit_from_text(log, signature, slot, tx_index, block_time, grpc_recv_us);
331    }
332
333    if log.contains("withdraw") || log.contains("Withdraw") {
334        return parse_withdraw_from_text(log, signature, slot, tx_index, block_time, grpc_recv_us);
335    }
336
337    if log.contains("create") && log.contains("pool") {
338        return parse_create_pool_from_text(log, signature, slot, tx_index, block_time, grpc_recv_us);
339    }
340
341    None
342}
343
344/// 从文本解析 Base In 交换事件
345fn parse_swap_base_in_from_text(
346    log: &str,
347    signature: Signature,
348    slot: u64,
349    tx_index: u64,
350    block_time: Option<i64>,
351    grpc_recv_us: i64,
352) -> Option<DexEvent> {
353    use super::utils::text_parser::*;
354
355    let metadata = create_metadata_simple(signature, slot, tx_index, block_time, Pubkey::default(), grpc_recv_us);
356
357    Some(DexEvent::RaydiumCpmmSwap(RaydiumCpmmSwapEvent {
358        metadata,
359
360        // IDL SwapEvent 事件字段
361        pool_id: Pubkey::default(),
362        input_vault_before: 0,
363        output_vault_before: 0,
364        input_amount: extract_number_from_text(log, "amount_in").unwrap_or(1_000_000_000),
365        output_amount: extract_number_from_text(log, "amount_out").unwrap_or(950_000_000),
366        input_transfer_fee: 0,
367        output_transfer_fee: 0,
368        base_input: true,
369
370        // 暂时注释,以后会用,AI禁止改动
371        // 指令参数字段
372        // amount_in: extract_number_from_text(log, "amount_in").unwrap_or(1_000_000_000),
373        // minimum_amount_out: extract_number_from_text(log, "amount_out").unwrap_or(950_000_000),
374
375        // 指令账户字段
376        // payer: Pubkey::default(),
377        // authority: Pubkey::default(),
378        // amm_config: Pubkey::default(),
379        // pool_state: Pubkey::default(),
380        // input_token_account: Pubkey::default(),
381        // output_token_account: Pubkey::default(),
382        // input_vault: Pubkey::default(),
383        // output_vault: Pubkey::default(),
384        // input_token_mint: Pubkey::default(),
385        // output_token_mint: Pubkey::default(),
386    }))
387}
388
389/// 从文本解析 Base Out 交换事件
390fn parse_swap_base_out_from_text(
391    log: &str,
392    signature: Signature,
393    slot: u64,
394    tx_index: u64,
395    block_time: Option<i64>,
396    grpc_recv_us: i64,
397) -> Option<DexEvent> {
398    use super::utils::text_parser::*;
399
400    let metadata = create_metadata_simple(signature, slot, tx_index, block_time, Pubkey::default(), grpc_recv_us);
401
402    Some(DexEvent::RaydiumCpmmSwap(RaydiumCpmmSwapEvent {
403        metadata,
404
405        // IDL SwapEvent 事件字段
406        pool_id: Pubkey::default(),
407        input_vault_before: 0,
408        output_vault_before: 0,
409        input_amount: extract_number_from_text(log, "amount_in").unwrap_or(1_000_000_000),
410        output_amount: extract_number_from_text(log, "amount_out").unwrap_or(950_000_000),
411        input_transfer_fee: 0,
412        output_transfer_fee: 0,
413        base_input: false,
414
415        // 暂时注释,以后会用,AI禁止改动
416        // 指令参数字段
417        // amount_in: extract_number_from_text(log, "amount_in").unwrap_or(1_000_000_000),
418        // minimum_amount_out: extract_number_from_text(log, "amount_out").unwrap_or(950_000_000),
419
420        // 指令账户字段
421        // payer: Pubkey::default(),
422        // authority: Pubkey::default(),
423        // amm_config: Pubkey::default(),
424        // pool_state: Pubkey::default(),
425        // input_token_account: Pubkey::default(),
426        // output_token_account: Pubkey::default(),
427        // input_vault: Pubkey::default(),
428        // output_vault: Pubkey::default(),
429        // input_token_mint: Pubkey::default(),
430        // output_token_mint: Pubkey::default(),
431    }))
432}
433
434/// 从文本解析池创建事件
435fn parse_create_pool_from_text(
436    log: &str,
437    signature: Signature,
438    slot: u64,
439    tx_index: u64,
440    block_time: Option<i64>,
441    grpc_recv_us: i64,
442) -> Option<DexEvent> {
443    use super::utils::text_parser::*;
444
445    let metadata = create_metadata_simple(signature, slot, tx_index, block_time, Pubkey::default(), grpc_recv_us);
446
447    Some(DexEvent::RaydiumCpmmInitialize(RaydiumCpmmInitializeEvent {
448        metadata,
449        pool: Pubkey::default(),
450        creator: Pubkey::default(),
451        init_amount0: extract_number_from_text(log, "amount_0").unwrap_or(1_000_000_000),
452        init_amount1: extract_number_from_text(log, "amount_1").unwrap_or(1_000_000_000),
453    }))
454}
455
456/// 从文本解析存款事件
457fn parse_deposit_from_text(
458    log: &str,
459    signature: Signature,
460    slot: u64,
461    tx_index: u64,
462    block_time: Option<i64>,
463    grpc_recv_us: i64,
464) -> Option<DexEvent> {
465    use super::utils::text_parser::*;
466
467    let metadata = create_metadata_simple(signature, slot, tx_index, block_time, Pubkey::default(), grpc_recv_us);
468
469    Some(DexEvent::RaydiumCpmmDeposit(RaydiumCpmmDepositEvent {
470        metadata,
471        pool: Pubkey::default(),
472        user: Pubkey::default(),
473        lp_token_amount: extract_number_from_text(log, "lp_token").unwrap_or(1_000_000),
474        token0_amount: extract_number_from_text(log, "token_0").unwrap_or(1_000_000_000),
475        token1_amount: extract_number_from_text(log, "token_1").unwrap_or(1_000_000_000),
476    }))
477}
478
479/// 从文本解析提款事件
480fn parse_withdraw_from_text(
481    log: &str,
482    signature: Signature,
483    slot: u64,
484    tx_index: u64,
485    block_time: Option<i64>,
486    grpc_recv_us: i64,
487) -> Option<DexEvent> {
488    use super::utils::text_parser::*;
489
490    let metadata = create_metadata_simple(signature, slot, tx_index, block_time, Pubkey::default(), grpc_recv_us);
491
492    Some(DexEvent::RaydiumCpmmWithdraw(RaydiumCpmmWithdrawEvent {
493        metadata,
494        pool: Pubkey::default(),
495        user: Pubkey::default(),
496        lp_token_amount: extract_number_from_text(log, "lp_token").unwrap_or(1_000_000),
497        token0_amount: extract_number_from_text(log, "token_0").unwrap_or(1_000_000_000),
498        token1_amount: extract_number_from_text(log, "token_1").unwrap_or(1_000_000_000),
499    }))
500}