sol_parser_sdk/logs/
raydium_amm.rs

1//! Raydium AMM V4 日志解析器
2//!
3//! 使用 match discriminator 模式解析 Raydium AMM V4 事件
4
5use solana_sdk::{pubkey::Pubkey, signature::Signature};
6use crate::core::events::*;
7use super::utils::*;
8
9/// Raydium AMM V4 日志事件 discriminator 常量
10pub mod discriminators {
11    // 事件鉴别器 - 基于参考代码,Raydium AMM V4 使用的可能的日志事件标识
12    pub const SWAP_BASE_IN_EVENT: [u8; 8] = [0, 0, 0, 0, 0, 0, 0, 9];
13    pub const SWAP_BASE_OUT_EVENT: [u8; 8] = [0, 0, 0, 0, 0, 0, 0, 11];
14    pub const DEPOSIT_EVENT: [u8; 8] = [0, 0, 0, 0, 0, 0, 0, 3];
15    pub const WITHDRAW_EVENT: [u8; 8] = [0, 0, 0, 0, 0, 0, 0, 4];
16    pub const INITIALIZE2_EVENT: [u8; 8] = [0, 0, 0, 0, 0, 0, 0, 1];
17    pub const WITHDRAW_PNL_EVENT: [u8; 8] = [0, 0, 0, 0, 0, 0, 0, 7];
18}
19
20/// Raydium AMM V4 程序 ID
21pub const PROGRAM_ID: &str = "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8";
22
23/// 解析 Raydium AMM V4 日志
24#[inline]
25pub fn parse_log(log: &str, signature: Signature, slot: u64, tx_index: u64, block_time: Option<i64>, grpc_recv_us: i64) -> Option<DexEvent> {
26    parse_structured_log(log, signature, slot, tx_index, block_time, grpc_recv_us)
27}
28
29/// 结构化日志解析(基于 Program data)
30fn parse_structured_log(
31    log: &str,
32    signature: Signature,
33    slot: u64,
34    tx_index: u64,
35    block_time: Option<i64>,
36    grpc_recv_us: i64,
37) -> Option<DexEvent> {
38    let program_data = extract_program_data(log)?;
39    if program_data.len() < 8 {
40        return None;
41    }
42
43    let discriminator: [u8; 8] = program_data[0..8].try_into().ok()?;
44    let data = &program_data[8..];
45
46    match discriminator {
47        discriminators::SWAP_BASE_IN_EVENT => {
48            parse_swap_base_in_event(data, signature, slot, tx_index, block_time, grpc_recv_us)
49        },
50        discriminators::SWAP_BASE_OUT_EVENT => {
51            parse_swap_base_out_event(data, signature, slot, tx_index, block_time, grpc_recv_us)
52        },
53        discriminators::DEPOSIT_EVENT => {
54            parse_deposit_event(data, signature, slot, tx_index, block_time, grpc_recv_us)
55        },
56        discriminators::WITHDRAW_EVENT => {
57            parse_withdraw_event(data, signature, slot, tx_index, block_time, grpc_recv_us)
58        },
59        discriminators::INITIALIZE2_EVENT => {
60            parse_initialize2_event(data, signature, slot, tx_index, block_time, grpc_recv_us)
61        },
62        discriminators::WITHDRAW_PNL_EVENT => {
63            parse_withdraw_pnl_event(data, signature, slot, tx_index, block_time, grpc_recv_us)
64        },
65        _ => None,
66    }
67}
68
69/// 解析 SwapBaseIn 事件
70fn parse_swap_base_in_event(
71    data: &[u8],
72    signature: Signature,
73    slot: u64,
74    tx_index: u64,
75    block_time: Option<i64>,
76    grpc_recv_us: i64,
77) -> Option<DexEvent> {
78    let mut offset = 0;
79
80    let amm = 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
91    let metadata = create_metadata_simple(signature, slot, tx_index, block_time, amm, grpc_recv_us);
92
93    Some(DexEvent::RaydiumAmmV4Swap(RaydiumAmmV4SwapEvent {
94        metadata,
95        amount_in,
96        minimum_amount_out,
97        max_amount_in: 0,
98        amount_out: 0,
99        token_program: Pubkey::default(),
100        amm,
101        amm_authority: Pubkey::default(),
102        amm_open_orders: Pubkey::default(),
103        amm_target_orders: None,
104        pool_coin_token_account: Pubkey::default(),
105        pool_pc_token_account: Pubkey::default(),
106        serum_program: Pubkey::default(),
107        serum_market: Pubkey::default(),
108        serum_bids: Pubkey::default(),
109        serum_asks: Pubkey::default(),
110        serum_event_queue: Pubkey::default(),
111        serum_coin_vault_account: Pubkey::default(),
112        serum_pc_vault_account: Pubkey::default(),
113        serum_vault_signer: Pubkey::default(),
114        user_source_token_account: Pubkey::default(),
115        user_destination_token_account: Pubkey::default(),
116        user_source_owner: user,
117    }))
118}
119
120/// 解析 SwapBaseOut 事件
121fn parse_swap_base_out_event(
122    data: &[u8],
123    signature: Signature,
124    slot: u64,
125    tx_index: u64,
126    block_time: Option<i64>,
127    grpc_recv_us: i64,
128) -> Option<DexEvent> {
129    let mut offset = 0;
130
131    let amm = read_pubkey(data, offset)?;
132    offset += 32;
133
134    let user = read_pubkey(data, offset)?;
135    offset += 32;
136
137    let max_amount_in = read_u64_le(data, offset)?;
138    offset += 8;
139
140    let amount_out = read_u64_le(data, offset)?;
141
142    let metadata = create_metadata_simple(signature, slot, tx_index, block_time, amm, grpc_recv_us);
143
144    Some(DexEvent::RaydiumAmmV4Swap(RaydiumAmmV4SwapEvent {
145        metadata,
146        amount_in: 0,
147        minimum_amount_out: 0,
148        max_amount_in,
149        amount_out,
150        token_program: Pubkey::default(),
151        amm,
152        amm_authority: Pubkey::default(),
153        amm_open_orders: Pubkey::default(),
154        amm_target_orders: None,
155        pool_coin_token_account: Pubkey::default(),
156        pool_pc_token_account: Pubkey::default(),
157        serum_program: Pubkey::default(),
158        serum_market: Pubkey::default(),
159        serum_bids: Pubkey::default(),
160        serum_asks: Pubkey::default(),
161        serum_event_queue: Pubkey::default(),
162        serum_coin_vault_account: Pubkey::default(),
163        serum_pc_vault_account: Pubkey::default(),
164        serum_vault_signer: Pubkey::default(),
165        user_source_token_account: Pubkey::default(),
166        user_destination_token_account: Pubkey::default(),
167        user_source_owner: user,
168    }))
169}
170
171/// 解析存款事件
172fn parse_deposit_event(
173    data: &[u8],
174    signature: Signature,
175    slot: u64,
176    tx_index: u64,
177    block_time: Option<i64>,
178    grpc_recv_us: i64,
179) -> Option<DexEvent> {
180    let mut offset = 0;
181
182    let amm = read_pubkey(data, offset)?;
183    offset += 32;
184
185    let user = read_pubkey(data, offset)?;
186    offset += 32;
187
188    let max_coin_amount = read_u64_le(data, offset)?;
189    offset += 8;
190
191    let max_pc_amount = read_u64_le(data, offset)?;
192    offset += 8;
193
194    let base_side = read_u64_le(data, offset)?;
195
196    let metadata = create_metadata_simple(signature, slot, tx_index, block_time, amm, grpc_recv_us);
197
198    Some(DexEvent::RaydiumAmmV4Deposit(RaydiumAmmV4DepositEvent {
199        metadata,
200        max_coin_amount,
201        max_pc_amount,
202        base_side,
203        token_program: Pubkey::default(),
204        amm,
205        amm_authority: Pubkey::default(),
206        amm_open_orders: Pubkey::default(),
207        amm_target_orders: Pubkey::default(),
208        lp_mint_address: Pubkey::default(),
209        pool_coin_token_account: Pubkey::default(),
210        pool_pc_token_account: Pubkey::default(),
211        serum_market: Pubkey::default(),
212        user_coin_token_account: Pubkey::default(),
213        user_pc_token_account: Pubkey::default(),
214        user_lp_token_account: Pubkey::default(),
215        user_owner: user,
216        serum_event_queue: Pubkey::default(),
217    }))
218}
219
220/// 解析提取事件
221fn parse_withdraw_event(
222    data: &[u8],
223    signature: Signature,
224    slot: u64,
225    tx_index: u64,
226    block_time: Option<i64>,
227    grpc_recv_us: i64,
228) -> Option<DexEvent> {
229    let mut offset = 0;
230
231    let amm = read_pubkey(data, offset)?;
232    offset += 32;
233
234    let user = read_pubkey(data, offset)?;
235    offset += 32;
236
237    let amount = read_u64_le(data, offset)?;
238
239    let metadata = create_metadata_simple(signature, slot, tx_index, block_time, amm, grpc_recv_us);
240
241    Some(DexEvent::RaydiumAmmV4Withdraw(RaydiumAmmV4WithdrawEvent {
242        metadata,
243        amount,
244        token_program: Pubkey::default(),
245        amm,
246        amm_authority: Pubkey::default(),
247        amm_open_orders: Pubkey::default(),
248        amm_target_orders: Pubkey::default(),
249        lp_mint_address: Pubkey::default(),
250        pool_coin_token_account: Pubkey::default(),
251        pool_pc_token_account: Pubkey::default(),
252        pool_withdraw_queue: Pubkey::default(),
253        pool_temp_lp_token_account: Pubkey::default(),
254        serum_program: Pubkey::default(),
255        serum_market: Pubkey::default(),
256        serum_coin_vault_account: Pubkey::default(),
257        serum_pc_vault_account: Pubkey::default(),
258        serum_vault_signer: Pubkey::default(),
259        user_lp_token_account: Pubkey::default(),
260        user_coin_token_account: Pubkey::default(),
261        user_pc_token_account: Pubkey::default(),
262        user_owner: user,
263        serum_event_queue: Pubkey::default(),
264        serum_bids: Pubkey::default(),
265        serum_asks: Pubkey::default(),
266    }))
267}
268
269/// 解析初始化事件
270fn parse_initialize2_event(
271    data: &[u8],
272    signature: Signature,
273    slot: u64,
274    tx_index: u64,
275    block_time: Option<i64>,
276    grpc_recv_us: i64,
277) -> Option<DexEvent> {
278    let mut offset = 0;
279
280    let amm = read_pubkey(data, offset)?;
281    offset += 32;
282
283    let user = read_pubkey(data, offset)?;
284    offset += 32;
285
286    let nonce = data.get(offset)?.clone();
287    offset += 1;
288
289    let open_time = read_u64_le(data, offset)?;
290    offset += 8;
291
292    let init_pc_amount = read_u64_le(data, offset)?;
293    offset += 8;
294
295    let init_coin_amount = read_u64_le(data, offset)?;
296
297    let metadata = create_metadata_simple(signature, slot, tx_index, block_time, amm, grpc_recv_us);
298
299    Some(DexEvent::RaydiumAmmV4Initialize2(RaydiumAmmV4Initialize2Event {
300        metadata,
301        nonce,
302        open_time,
303        init_pc_amount,
304        init_coin_amount,
305        token_program: Pubkey::default(),
306        spl_associated_token_account: Pubkey::default(),
307        system_program: Pubkey::default(),
308        rent: Pubkey::default(),
309        amm,
310        amm_authority: Pubkey::default(),
311        amm_open_orders: Pubkey::default(),
312        lp_mint: Pubkey::default(),
313        coin_mint: Pubkey::default(),
314        pc_mint: Pubkey::default(),
315        pool_coin_token_account: Pubkey::default(),
316        pool_pc_token_account: Pubkey::default(),
317        pool_withdraw_queue: Pubkey::default(),
318        amm_target_orders: Pubkey::default(),
319        pool_temp_lp: Pubkey::default(),
320        serum_program: Pubkey::default(),
321        serum_market: Pubkey::default(),
322        user_wallet: user,
323        user_token_coin: Pubkey::default(),
324        user_token_pc: Pubkey::default(),
325        user_lp_token_account: Pubkey::default(),
326    }))
327}
328
329/// 解析提取 PnL 事件
330fn parse_withdraw_pnl_event(
331    data: &[u8],
332    signature: Signature,
333    slot: u64,
334    tx_index: u64,
335    block_time: Option<i64>,
336    grpc_recv_us: i64,
337) -> Option<DexEvent> {
338    let mut offset = 0;
339
340    let amm = read_pubkey(data, offset)?;
341    offset += 32;
342
343    let pnl_owner = read_pubkey(data, offset)?;
344
345    let metadata = create_metadata_simple(signature, slot, tx_index, block_time, amm, grpc_recv_us);
346
347    Some(DexEvent::RaydiumAmmV4WithdrawPnl(RaydiumAmmV4WithdrawPnlEvent {
348        metadata,
349        token_program: Pubkey::default(),
350        amm,
351        amm_config: Pubkey::default(),
352        amm_authority: Pubkey::default(),
353        amm_open_orders: Pubkey::default(),
354        pool_coin_token_account: Pubkey::default(),
355        pool_pc_token_account: Pubkey::default(),
356        coin_pnl_token_account: Pubkey::default(),
357        pc_pnl_token_account: Pubkey::default(),
358        pnl_owner,
359        amm_target_orders: Pubkey::default(),
360        serum_program: Pubkey::default(),
361        serum_market: Pubkey::default(),
362        serum_event_queue: Pubkey::default(),
363        serum_coin_vault_account: Pubkey::default(),
364        serum_pc_vault_account: Pubkey::default(),
365        serum_vault_signer: Pubkey::default(),
366    }))
367}
368
369/// 文本日志解析(回退方案)
370fn parse_text_log(
371    log: &str,
372    signature: Signature,
373    slot: u64,
374    tx_index: u64,
375    block_time: Option<i64>,
376    grpc_recv_us: i64,
377) -> Option<DexEvent> {
378    // 检查是否是交换相关的日志
379    if log.contains("swap") || log.contains("Swap") {
380        return parse_swap_log_fallback(log, signature, slot, tx_index, block_time, grpc_recv_us);
381    }
382
383    // 检查是否是存款相关的日志
384    if log.contains("deposit") || log.contains("Deposit") {
385        return parse_deposit_log_fallback(log, signature, slot, tx_index, block_time, grpc_recv_us);
386    }
387
388    // 检查是否是提取相关的日志
389    if log.contains("withdraw") || log.contains("Withdraw") {
390        return parse_withdraw_log_fallback(log, signature, slot, tx_index, block_time, grpc_recv_us);
391    }
392
393    None
394}
395
396/// 文本回退解析交换事件
397fn parse_swap_log_fallback(
398    log: &str,
399    signature: Signature,
400    slot: u64,
401    tx_index: u64,
402    block_time: Option<i64>,
403    grpc_recv_us: i64,
404) -> Option<DexEvent> {
405    // 尝试从日志文本中提取基本信息
406    let amount_in = super::utils::text_parser::extract_number_from_text(log, "amount_in")
407        .or_else(|| super::utils::text_parser::extract_number_from_text(log, "amountIn"))
408        .unwrap_or(0);
409
410    let amount_out = super::utils::text_parser::extract_number_from_text(log, "amount_out")
411        .or_else(|| super::utils::text_parser::extract_number_from_text(log, "amountOut"))
412        .unwrap_or(0);
413
414    let minimum_amount_out = super::utils::text_parser::extract_number_from_text(log, "minimum_amount_out")
415        .or_else(|| super::utils::text_parser::extract_number_from_text(log, "minimumAmountOut"))
416        .unwrap_or(0);
417
418    let max_amount_in = super::utils::text_parser::extract_number_from_text(log, "max_amount_in")
419        .or_else(|| super::utils::text_parser::extract_number_from_text(log, "maxAmountIn"))
420        .unwrap_or(0);
421
422    let default_pubkey = Pubkey::default();
423    let metadata = create_metadata_simple(signature, slot, tx_index, block_time, default_pubkey, grpc_recv_us);
424
425    Some(DexEvent::RaydiumAmmV4Swap(RaydiumAmmV4SwapEvent {
426        metadata,
427        amount_in,
428        minimum_amount_out,
429        max_amount_in,
430        amount_out,
431        token_program: default_pubkey,
432        amm: default_pubkey,
433        amm_authority: default_pubkey,
434        amm_open_orders: default_pubkey,
435        amm_target_orders: None,
436        pool_coin_token_account: default_pubkey,
437        pool_pc_token_account: default_pubkey,
438        serum_program: default_pubkey,
439        serum_market: default_pubkey,
440        serum_bids: default_pubkey,
441        serum_asks: default_pubkey,
442        serum_event_queue: default_pubkey,
443        serum_coin_vault_account: default_pubkey,
444        serum_pc_vault_account: default_pubkey,
445        serum_vault_signer: default_pubkey,
446        user_source_token_account: default_pubkey,
447        user_destination_token_account: default_pubkey,
448        user_source_owner: default_pubkey,
449    }))
450}
451
452/// 文本回退解析存款事件
453fn parse_deposit_log_fallback(
454    log: &str,
455    signature: Signature,
456    slot: u64,
457    tx_index: u64,
458    block_time: Option<i64>,
459    grpc_recv_us: i64,
460) -> Option<DexEvent> {
461    let max_coin_amount = super::utils::text_parser::extract_number_from_text(log, "max_coin_amount")
462        .or_else(|| super::utils::text_parser::extract_number_from_text(log, "maxCoinAmount"))
463        .unwrap_or(0);
464
465    let max_pc_amount = super::utils::text_parser::extract_number_from_text(log, "max_pc_amount")
466        .or_else(|| super::utils::text_parser::extract_number_from_text(log, "maxPcAmount"))
467        .unwrap_or(0);
468
469    let base_side = super::utils::text_parser::extract_number_from_text(log, "base_side")
470        .or_else(|| super::utils::text_parser::extract_number_from_text(log, "baseSide"))
471        .unwrap_or(0);
472
473    let default_pubkey = Pubkey::default();
474    let metadata = create_metadata_simple(signature, slot, tx_index, block_time, default_pubkey, grpc_recv_us);
475
476    Some(DexEvent::RaydiumAmmV4Deposit(RaydiumAmmV4DepositEvent {
477        metadata,
478        max_coin_amount,
479        max_pc_amount,
480        base_side,
481        token_program: default_pubkey,
482        amm: default_pubkey,
483        amm_authority: default_pubkey,
484        amm_open_orders: default_pubkey,
485        amm_target_orders: default_pubkey,
486        lp_mint_address: default_pubkey,
487        pool_coin_token_account: default_pubkey,
488        pool_pc_token_account: default_pubkey,
489        serum_market: default_pubkey,
490        user_coin_token_account: default_pubkey,
491        user_pc_token_account: default_pubkey,
492        user_lp_token_account: default_pubkey,
493        user_owner: default_pubkey,
494        serum_event_queue: default_pubkey,
495    }))
496}
497
498/// 文本回退解析提取事件
499fn parse_withdraw_log_fallback(
500    log: &str,
501    signature: Signature,
502    slot: u64,
503    tx_index: u64,
504    block_time: Option<i64>,
505    grpc_recv_us: i64,
506) -> Option<DexEvent> {
507    let amount = super::utils::text_parser::extract_number_from_text(log, "amount")
508        .unwrap_or(0);
509
510    let default_pubkey = Pubkey::default();
511    let metadata = create_metadata_simple(signature, slot, tx_index, block_time, default_pubkey, grpc_recv_us);
512
513    Some(DexEvent::RaydiumAmmV4Withdraw(RaydiumAmmV4WithdrawEvent {
514        metadata,
515        amount,
516        token_program: default_pubkey,
517        amm: default_pubkey,
518        amm_authority: default_pubkey,
519        amm_open_orders: default_pubkey,
520        amm_target_orders: default_pubkey,
521        lp_mint_address: default_pubkey,
522        pool_coin_token_account: default_pubkey,
523        pool_pc_token_account: default_pubkey,
524        pool_withdraw_queue: default_pubkey,
525        pool_temp_lp_token_account: default_pubkey,
526        serum_program: default_pubkey,
527        serum_market: default_pubkey,
528        serum_coin_vault_account: default_pubkey,
529        serum_pc_vault_account: default_pubkey,
530        serum_vault_signer: default_pubkey,
531        user_lp_token_account: default_pubkey,
532        user_coin_token_account: default_pubkey,
533        user_pc_token_account: default_pubkey,
534        user_owner: default_pubkey,
535        serum_event_queue: default_pubkey,
536        serum_bids: default_pubkey,
537        serum_asks: default_pubkey,
538    }))
539}