Skip to main content

sol_parser_sdk/logs/
raydium_amm.rs

1//! Raydium AMM V4 日志解析器
2//!
3//! 使用 match discriminator 模式解析 Raydium AMM V4 事件
4
5use super::utils::*;
6use crate::core::events::*;
7use solana_sdk::{pubkey::Pubkey, signature::Signature};
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/// Decode the official bincode `ray_log` swap records emitted by AMM V4.
24#[inline]
25pub fn parse_ray_log_swap(log: &str, metadata: EventMetadata) -> Option<DexEvent> {
26    const PREFIX: &str = "ray_log: ";
27    let start = log.find(PREFIX)? + PREFIX.len();
28    let data = base64_simd::STANDARD.decode_to_vec(log[start..].trim().as_bytes()).ok()?;
29    if data.len() != 57 || !matches!(data[0], 3 | 4) {
30        return None;
31    }
32    let first = read_u64_le(&data, 1)?;
33    let second = read_u64_le(&data, 9)?;
34    let actual = read_u64_le(&data, 49)?;
35    let (amount_in, minimum_amount_out, max_amount_in, amount_out) =
36        if data[0] == 3 { (first, second, 0, actual) } else { (actual, 0, first, second) };
37
38    Some(DexEvent::RaydiumAmmV4Swap(RaydiumAmmV4SwapEvent {
39        metadata,
40        amount_in,
41        minimum_amount_out,
42        max_amount_in,
43        amount_out,
44        token_program: Pubkey::default(),
45        amm: Pubkey::default(),
46        amm_authority: Pubkey::default(),
47        amm_open_orders: Pubkey::default(),
48        amm_target_orders: None,
49        pool_coin_token_account: Pubkey::default(),
50        pool_pc_token_account: Pubkey::default(),
51        serum_program: Pubkey::default(),
52        serum_market: Pubkey::default(),
53        serum_bids: Pubkey::default(),
54        serum_asks: Pubkey::default(),
55        serum_event_queue: Pubkey::default(),
56        serum_coin_vault_account: Pubkey::default(),
57        serum_pc_vault_account: Pubkey::default(),
58        serum_vault_signer: Pubkey::default(),
59        user_source_token_account: Pubkey::default(),
60        user_destination_token_account: Pubkey::default(),
61        user_source_owner: Pubkey::default(),
62
63        ..Default::default()
64    }))
65}
66
67/// 解析 Raydium AMM V4 日志
68#[inline]
69pub fn parse_log(
70    log: &str,
71    signature: Signature,
72    slot: u64,
73    tx_index: u64,
74    block_time_us: Option<i64>,
75    grpc_recv_us: i64,
76) -> Option<DexEvent> {
77    parse_structured_log(log, signature, slot, tx_index, block_time_us, grpc_recv_us)
78}
79
80/// 结构化日志解析(基于 Program data)
81fn parse_structured_log(
82    log: &str,
83    signature: Signature,
84    slot: u64,
85    tx_index: u64,
86    block_time_us: Option<i64>,
87    grpc_recv_us: i64,
88) -> Option<DexEvent> {
89    let program_data = extract_program_data(log)?;
90    if program_data.len() < 8 {
91        return None;
92    }
93
94    let discriminator: [u8; 8] = program_data[0..8].try_into().ok()?;
95    let data = &program_data[8..];
96
97    match discriminator {
98        discriminators::SWAP_BASE_IN_EVENT => {
99            parse_swap_base_in_event(data, signature, slot, tx_index, block_time_us, grpc_recv_us)
100        }
101        discriminators::SWAP_BASE_OUT_EVENT => {
102            parse_swap_base_out_event(data, signature, slot, tx_index, block_time_us, grpc_recv_us)
103        }
104        discriminators::DEPOSIT_EVENT => {
105            parse_deposit_event(data, signature, slot, tx_index, block_time_us, grpc_recv_us)
106        }
107        discriminators::WITHDRAW_EVENT => {
108            parse_withdraw_event(data, signature, slot, tx_index, block_time_us, grpc_recv_us)
109        }
110        discriminators::INITIALIZE2_EVENT => {
111            parse_initialize2_event(data, signature, slot, tx_index, block_time_us, grpc_recv_us)
112        }
113        discriminators::WITHDRAW_PNL_EVENT => {
114            parse_withdraw_pnl_event(data, signature, slot, tx_index, block_time_us, grpc_recv_us)
115        }
116        _ => None,
117    }
118}
119
120// =============================================================================
121// Public from_data parsers - Accept pre-decoded data, eliminate double decode
122// =============================================================================
123
124/// Parse Raydium AMM V4 SwapBaseIn event from pre-decoded data
125#[inline(always)]
126pub fn parse_swap_base_in_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
127    let mut offset = 0;
128
129    let amm = read_pubkey(data, offset)?;
130    offset += 32;
131
132    let user = read_pubkey(data, offset)?;
133    offset += 32;
134
135    let amount_in = read_u64_le(data, offset)?;
136    offset += 8;
137
138    let minimum_amount_out = read_u64_le(data, offset)?;
139
140    Some(DexEvent::RaydiumAmmV4Swap(RaydiumAmmV4SwapEvent {
141        metadata,
142        amount_in,
143        minimum_amount_out,
144        max_amount_in: 0,
145        amount_out: 0,
146        token_program: Pubkey::default(),
147        amm,
148        amm_authority: Pubkey::default(),
149        amm_open_orders: Pubkey::default(),
150        amm_target_orders: None,
151        pool_coin_token_account: Pubkey::default(),
152        pool_pc_token_account: Pubkey::default(),
153        serum_program: Pubkey::default(),
154        serum_market: Pubkey::default(),
155        serum_bids: Pubkey::default(),
156        serum_asks: Pubkey::default(),
157        serum_event_queue: Pubkey::default(),
158        serum_coin_vault_account: Pubkey::default(),
159        serum_pc_vault_account: Pubkey::default(),
160        serum_vault_signer: Pubkey::default(),
161        user_source_token_account: Pubkey::default(),
162        user_destination_token_account: Pubkey::default(),
163        user_source_owner: user,
164
165        ..Default::default()
166    }))
167}
168
169/// Parse Raydium AMM V4 SwapBaseOut event from pre-decoded data
170#[inline(always)]
171pub fn parse_swap_base_out_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
172    let mut offset = 0;
173
174    let amm = read_pubkey(data, offset)?;
175    offset += 32;
176
177    let user = read_pubkey(data, offset)?;
178    offset += 32;
179
180    let max_amount_in = read_u64_le(data, offset)?;
181    offset += 8;
182
183    let amount_out = read_u64_le(data, offset)?;
184
185    Some(DexEvent::RaydiumAmmV4Swap(RaydiumAmmV4SwapEvent {
186        metadata,
187        amount_in: 0,
188        minimum_amount_out: 0,
189        max_amount_in,
190        amount_out,
191        token_program: Pubkey::default(),
192        amm,
193        amm_authority: Pubkey::default(),
194        amm_open_orders: Pubkey::default(),
195        amm_target_orders: None,
196        pool_coin_token_account: Pubkey::default(),
197        pool_pc_token_account: Pubkey::default(),
198        serum_program: Pubkey::default(),
199        serum_market: Pubkey::default(),
200        serum_bids: Pubkey::default(),
201        serum_asks: Pubkey::default(),
202        serum_event_queue: Pubkey::default(),
203        serum_coin_vault_account: Pubkey::default(),
204        serum_pc_vault_account: Pubkey::default(),
205        serum_vault_signer: Pubkey::default(),
206        user_source_token_account: Pubkey::default(),
207        user_destination_token_account: Pubkey::default(),
208        user_source_owner: user,
209
210        ..Default::default()
211    }))
212}
213
214/// Parse Raydium AMM V4 Deposit event from pre-decoded data
215#[inline(always)]
216pub fn parse_deposit_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
217    let mut offset = 0;
218
219    let amm = read_pubkey(data, offset)?;
220    offset += 32;
221
222    let user = read_pubkey(data, offset)?;
223    offset += 32;
224
225    let max_coin_amount = read_u64_le(data, offset)?;
226    offset += 8;
227
228    let max_pc_amount = read_u64_le(data, offset)?;
229    offset += 8;
230
231    let base_side = read_u64_le(data, offset)?;
232
233    Some(DexEvent::RaydiumAmmV4Deposit(RaydiumAmmV4DepositEvent {
234        metadata,
235        max_coin_amount,
236        max_pc_amount,
237        base_side,
238        token_program: Pubkey::default(),
239        amm,
240        amm_authority: Pubkey::default(),
241        amm_open_orders: Pubkey::default(),
242        amm_target_orders: Pubkey::default(),
243        lp_mint_address: Pubkey::default(),
244        pool_coin_token_account: Pubkey::default(),
245        pool_pc_token_account: Pubkey::default(),
246        serum_market: Pubkey::default(),
247        user_coin_token_account: Pubkey::default(),
248        user_pc_token_account: Pubkey::default(),
249        user_lp_token_account: Pubkey::default(),
250        user_owner: user,
251        serum_event_queue: Pubkey::default(),
252    }))
253}
254
255/// Parse Raydium AMM V4 Withdraw event from pre-decoded data
256#[inline(always)]
257pub fn parse_withdraw_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
258    let mut offset = 0;
259
260    let amm = read_pubkey(data, offset)?;
261    offset += 32;
262
263    let user = read_pubkey(data, offset)?;
264    offset += 32;
265
266    let amount = read_u64_le(data, offset)?;
267
268    Some(DexEvent::RaydiumAmmV4Withdraw(RaydiumAmmV4WithdrawEvent {
269        metadata,
270        amount,
271        token_program: Pubkey::default(),
272        amm,
273        amm_authority: Pubkey::default(),
274        amm_open_orders: Pubkey::default(),
275        amm_target_orders: Pubkey::default(),
276        lp_mint_address: Pubkey::default(),
277        pool_coin_token_account: Pubkey::default(),
278        pool_pc_token_account: Pubkey::default(),
279        pool_withdraw_queue: Pubkey::default(),
280        pool_temp_lp_token_account: Pubkey::default(),
281        serum_program: Pubkey::default(),
282        serum_market: Pubkey::default(),
283        serum_coin_vault_account: Pubkey::default(),
284        serum_pc_vault_account: Pubkey::default(),
285        serum_vault_signer: Pubkey::default(),
286        user_lp_token_account: Pubkey::default(),
287        user_coin_token_account: Pubkey::default(),
288        user_pc_token_account: Pubkey::default(),
289        user_owner: user,
290        serum_event_queue: Pubkey::default(),
291        serum_bids: Pubkey::default(),
292        serum_asks: Pubkey::default(),
293    }))
294}
295
296/// Parse Raydium AMM V4 Initialize2 event from pre-decoded data
297#[inline(always)]
298pub fn parse_initialize2_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
299    let mut offset = 0;
300
301    let amm = read_pubkey(data, offset)?;
302    offset += 32;
303
304    let user = read_pubkey(data, offset)?;
305    offset += 32;
306
307    let nonce = *data.get(offset)?;
308    offset += 1;
309
310    let open_time = read_u64_le(data, offset)?;
311    offset += 8;
312
313    let init_pc_amount = read_u64_le(data, offset)?;
314    offset += 8;
315
316    let init_coin_amount = read_u64_le(data, offset)?;
317
318    Some(DexEvent::RaydiumAmmV4Initialize2(RaydiumAmmV4Initialize2Event {
319        metadata,
320        nonce,
321        open_time,
322        init_pc_amount,
323        init_coin_amount,
324        token_program: Pubkey::default(),
325        spl_associated_token_account: Pubkey::default(),
326        system_program: Pubkey::default(),
327        rent: Pubkey::default(),
328        amm,
329        amm_authority: Pubkey::default(),
330        amm_open_orders: Pubkey::default(),
331        lp_mint: Pubkey::default(),
332        coin_mint: Pubkey::default(),
333        pc_mint: Pubkey::default(),
334        pool_coin_token_account: Pubkey::default(),
335        pool_pc_token_account: Pubkey::default(),
336        pool_withdraw_queue: Pubkey::default(),
337        amm_target_orders: Pubkey::default(),
338        pool_temp_lp: Pubkey::default(),
339        serum_program: Pubkey::default(),
340        serum_market: Pubkey::default(),
341        user_wallet: user,
342        user_token_coin: Pubkey::default(),
343        user_token_pc: Pubkey::default(),
344        user_lp_token_account: Pubkey::default(),
345    }))
346}
347
348/// Parse Raydium AMM V4 WithdrawPnl event from pre-decoded data
349#[inline(always)]
350pub fn parse_withdraw_pnl_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
351    let mut offset = 0;
352
353    let amm = read_pubkey(data, offset)?;
354    offset += 32;
355
356    let pnl_owner = read_pubkey(data, offset)?;
357
358    Some(DexEvent::RaydiumAmmV4WithdrawPnl(RaydiumAmmV4WithdrawPnlEvent {
359        metadata,
360        token_program: Pubkey::default(),
361        amm,
362        amm_config: Pubkey::default(),
363        amm_authority: Pubkey::default(),
364        amm_open_orders: Pubkey::default(),
365        pool_coin_token_account: Pubkey::default(),
366        pool_pc_token_account: Pubkey::default(),
367        coin_pnl_token_account: Pubkey::default(),
368        pc_pnl_token_account: Pubkey::default(),
369        pnl_owner,
370        amm_target_orders: Pubkey::default(),
371        serum_program: Pubkey::default(),
372        serum_market: Pubkey::default(),
373        serum_event_queue: Pubkey::default(),
374        serum_coin_vault_account: Pubkey::default(),
375        serum_pc_vault_account: Pubkey::default(),
376        serum_vault_signer: Pubkey::default(),
377    }))
378}
379
380/// 解析 SwapBaseIn 事件
381fn parse_swap_base_in_event(
382    data: &[u8],
383    signature: Signature,
384    slot: u64,
385    tx_index: u64,
386    block_time_us: Option<i64>,
387    grpc_recv_us: i64,
388) -> Option<DexEvent> {
389    let mut offset = 0;
390
391    let amm = read_pubkey(data, offset)?;
392    offset += 32;
393
394    let user = read_pubkey(data, offset)?;
395    offset += 32;
396
397    let amount_in = read_u64_le(data, offset)?;
398    offset += 8;
399
400    let minimum_amount_out = read_u64_le(data, offset)?;
401
402    let metadata =
403        create_metadata_simple(signature, slot, tx_index, block_time_us, amm, grpc_recv_us);
404
405    Some(DexEvent::RaydiumAmmV4Swap(RaydiumAmmV4SwapEvent {
406        metadata,
407        amount_in,
408        minimum_amount_out,
409        max_amount_in: 0,
410        amount_out: 0,
411        token_program: Pubkey::default(),
412        amm,
413        amm_authority: Pubkey::default(),
414        amm_open_orders: Pubkey::default(),
415        amm_target_orders: None,
416        pool_coin_token_account: Pubkey::default(),
417        pool_pc_token_account: Pubkey::default(),
418        serum_program: Pubkey::default(),
419        serum_market: Pubkey::default(),
420        serum_bids: Pubkey::default(),
421        serum_asks: Pubkey::default(),
422        serum_event_queue: Pubkey::default(),
423        serum_coin_vault_account: Pubkey::default(),
424        serum_pc_vault_account: Pubkey::default(),
425        serum_vault_signer: Pubkey::default(),
426        user_source_token_account: Pubkey::default(),
427        user_destination_token_account: Pubkey::default(),
428        user_source_owner: user,
429
430        ..Default::default()
431    }))
432}
433
434/// 解析 SwapBaseOut 事件
435fn parse_swap_base_out_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 amm = read_pubkey(data, offset)?;
446    offset += 32;
447
448    let user = read_pubkey(data, offset)?;
449    offset += 32;
450
451    let max_amount_in = read_u64_le(data, offset)?;
452    offset += 8;
453
454    let amount_out = read_u64_le(data, offset)?;
455
456    let metadata =
457        create_metadata_simple(signature, slot, tx_index, block_time_us, amm, grpc_recv_us);
458
459    Some(DexEvent::RaydiumAmmV4Swap(RaydiumAmmV4SwapEvent {
460        metadata,
461        amount_in: 0,
462        minimum_amount_out: 0,
463        max_amount_in,
464        amount_out,
465        token_program: Pubkey::default(),
466        amm,
467        amm_authority: Pubkey::default(),
468        amm_open_orders: Pubkey::default(),
469        amm_target_orders: None,
470        pool_coin_token_account: Pubkey::default(),
471        pool_pc_token_account: Pubkey::default(),
472        serum_program: Pubkey::default(),
473        serum_market: Pubkey::default(),
474        serum_bids: Pubkey::default(),
475        serum_asks: Pubkey::default(),
476        serum_event_queue: Pubkey::default(),
477        serum_coin_vault_account: Pubkey::default(),
478        serum_pc_vault_account: Pubkey::default(),
479        serum_vault_signer: Pubkey::default(),
480        user_source_token_account: Pubkey::default(),
481        user_destination_token_account: Pubkey::default(),
482        user_source_owner: user,
483
484        ..Default::default()
485    }))
486}
487
488/// 解析存款事件
489fn parse_deposit_event(
490    data: &[u8],
491    signature: Signature,
492    slot: u64,
493    tx_index: u64,
494    block_time_us: Option<i64>,
495    grpc_recv_us: i64,
496) -> Option<DexEvent> {
497    let mut offset = 0;
498
499    let amm = read_pubkey(data, offset)?;
500    offset += 32;
501
502    let user = read_pubkey(data, offset)?;
503    offset += 32;
504
505    let max_coin_amount = read_u64_le(data, offset)?;
506    offset += 8;
507
508    let max_pc_amount = read_u64_le(data, offset)?;
509    offset += 8;
510
511    let base_side = read_u64_le(data, offset)?;
512
513    let metadata =
514        create_metadata_simple(signature, slot, tx_index, block_time_us, amm, grpc_recv_us);
515
516    Some(DexEvent::RaydiumAmmV4Deposit(RaydiumAmmV4DepositEvent {
517        metadata,
518        max_coin_amount,
519        max_pc_amount,
520        base_side,
521        token_program: Pubkey::default(),
522        amm,
523        amm_authority: Pubkey::default(),
524        amm_open_orders: Pubkey::default(),
525        amm_target_orders: Pubkey::default(),
526        lp_mint_address: Pubkey::default(),
527        pool_coin_token_account: Pubkey::default(),
528        pool_pc_token_account: Pubkey::default(),
529        serum_market: Pubkey::default(),
530        user_coin_token_account: Pubkey::default(),
531        user_pc_token_account: Pubkey::default(),
532        user_lp_token_account: Pubkey::default(),
533        user_owner: user,
534        serum_event_queue: Pubkey::default(),
535    }))
536}
537
538/// 解析提取事件
539fn parse_withdraw_event(
540    data: &[u8],
541    signature: Signature,
542    slot: u64,
543    tx_index: u64,
544    block_time_us: Option<i64>,
545    grpc_recv_us: i64,
546) -> Option<DexEvent> {
547    let mut offset = 0;
548
549    let amm = read_pubkey(data, offset)?;
550    offset += 32;
551
552    let user = read_pubkey(data, offset)?;
553    offset += 32;
554
555    let amount = read_u64_le(data, offset)?;
556
557    let metadata =
558        create_metadata_simple(signature, slot, tx_index, block_time_us, amm, grpc_recv_us);
559
560    Some(DexEvent::RaydiumAmmV4Withdraw(RaydiumAmmV4WithdrawEvent {
561        metadata,
562        amount,
563        token_program: Pubkey::default(),
564        amm,
565        amm_authority: Pubkey::default(),
566        amm_open_orders: Pubkey::default(),
567        amm_target_orders: Pubkey::default(),
568        lp_mint_address: Pubkey::default(),
569        pool_coin_token_account: Pubkey::default(),
570        pool_pc_token_account: Pubkey::default(),
571        pool_withdraw_queue: Pubkey::default(),
572        pool_temp_lp_token_account: Pubkey::default(),
573        serum_program: Pubkey::default(),
574        serum_market: Pubkey::default(),
575        serum_coin_vault_account: Pubkey::default(),
576        serum_pc_vault_account: Pubkey::default(),
577        serum_vault_signer: Pubkey::default(),
578        user_lp_token_account: Pubkey::default(),
579        user_coin_token_account: Pubkey::default(),
580        user_pc_token_account: Pubkey::default(),
581        user_owner: user,
582        serum_event_queue: Pubkey::default(),
583        serum_bids: Pubkey::default(),
584        serum_asks: Pubkey::default(),
585    }))
586}
587
588/// 解析初始化事件
589fn parse_initialize2_event(
590    data: &[u8],
591    signature: Signature,
592    slot: u64,
593    tx_index: u64,
594    block_time_us: Option<i64>,
595    grpc_recv_us: i64,
596) -> Option<DexEvent> {
597    let mut offset = 0;
598
599    let amm = read_pubkey(data, offset)?;
600    offset += 32;
601
602    let user = read_pubkey(data, offset)?;
603    offset += 32;
604
605    let nonce = *data.get(offset)?;
606    offset += 1;
607
608    let open_time = read_u64_le(data, offset)?;
609    offset += 8;
610
611    let init_pc_amount = read_u64_le(data, offset)?;
612    offset += 8;
613
614    let init_coin_amount = read_u64_le(data, offset)?;
615
616    let metadata =
617        create_metadata_simple(signature, slot, tx_index, block_time_us, amm, grpc_recv_us);
618
619    Some(DexEvent::RaydiumAmmV4Initialize2(RaydiumAmmV4Initialize2Event {
620        metadata,
621        nonce,
622        open_time,
623        init_pc_amount,
624        init_coin_amount,
625        token_program: Pubkey::default(),
626        spl_associated_token_account: Pubkey::default(),
627        system_program: Pubkey::default(),
628        rent: Pubkey::default(),
629        amm,
630        amm_authority: Pubkey::default(),
631        amm_open_orders: Pubkey::default(),
632        lp_mint: Pubkey::default(),
633        coin_mint: Pubkey::default(),
634        pc_mint: Pubkey::default(),
635        pool_coin_token_account: Pubkey::default(),
636        pool_pc_token_account: Pubkey::default(),
637        pool_withdraw_queue: Pubkey::default(),
638        amm_target_orders: Pubkey::default(),
639        pool_temp_lp: Pubkey::default(),
640        serum_program: Pubkey::default(),
641        serum_market: Pubkey::default(),
642        user_wallet: user,
643        user_token_coin: Pubkey::default(),
644        user_token_pc: Pubkey::default(),
645        user_lp_token_account: Pubkey::default(),
646    }))
647}
648
649/// 解析提取 PnL 事件
650fn parse_withdraw_pnl_event(
651    data: &[u8],
652    signature: Signature,
653    slot: u64,
654    tx_index: u64,
655    block_time_us: Option<i64>,
656    grpc_recv_us: i64,
657) -> Option<DexEvent> {
658    let mut offset = 0;
659
660    let amm = read_pubkey(data, offset)?;
661    offset += 32;
662
663    let pnl_owner = read_pubkey(data, offset)?;
664
665    let metadata =
666        create_metadata_simple(signature, slot, tx_index, block_time_us, amm, grpc_recv_us);
667
668    Some(DexEvent::RaydiumAmmV4WithdrawPnl(RaydiumAmmV4WithdrawPnlEvent {
669        metadata,
670        token_program: Pubkey::default(),
671        amm,
672        amm_config: Pubkey::default(),
673        amm_authority: Pubkey::default(),
674        amm_open_orders: Pubkey::default(),
675        pool_coin_token_account: Pubkey::default(),
676        pool_pc_token_account: Pubkey::default(),
677        coin_pnl_token_account: Pubkey::default(),
678        pc_pnl_token_account: Pubkey::default(),
679        pnl_owner,
680        amm_target_orders: Pubkey::default(),
681        serum_program: Pubkey::default(),
682        serum_market: Pubkey::default(),
683        serum_event_queue: Pubkey::default(),
684        serum_coin_vault_account: Pubkey::default(),
685        serum_pc_vault_account: Pubkey::default(),
686        serum_vault_signer: Pubkey::default(),
687    }))
688}
689
690/// 文本日志解析(回退方案)
691fn parse_text_log(
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    // 检查是否是交换相关的日志
700    if log.contains("swap") || log.contains("Swap") {
701        return parse_swap_log_fallback(
702            log,
703            signature,
704            slot,
705            tx_index,
706            block_time_us,
707            grpc_recv_us,
708        );
709    }
710
711    // 检查是否是存款相关的日志
712    if log.contains("deposit") || log.contains("Deposit") {
713        return parse_deposit_log_fallback(
714            log,
715            signature,
716            slot,
717            tx_index,
718            block_time_us,
719            grpc_recv_us,
720        );
721    }
722
723    // 检查是否是提取相关的日志
724    if log.contains("withdraw") || log.contains("Withdraw") {
725        return parse_withdraw_log_fallback(
726            log,
727            signature,
728            slot,
729            tx_index,
730            block_time_us,
731            grpc_recv_us,
732        );
733    }
734
735    None
736}
737
738/// 文本回退解析交换事件
739fn parse_swap_log_fallback(
740    log: &str,
741    signature: Signature,
742    slot: u64,
743    tx_index: u64,
744    block_time_us: Option<i64>,
745    grpc_recv_us: i64,
746) -> Option<DexEvent> {
747    // 尝试从日志文本中提取基本信息
748    let amount_in = super::utils::text_parser::extract_number_from_text(log, "amount_in")
749        .or_else(|| super::utils::text_parser::extract_number_from_text(log, "amountIn"))
750        .unwrap_or(0);
751
752    let amount_out = super::utils::text_parser::extract_number_from_text(log, "amount_out")
753        .or_else(|| super::utils::text_parser::extract_number_from_text(log, "amountOut"))
754        .unwrap_or(0);
755
756    let minimum_amount_out =
757        super::utils::text_parser::extract_number_from_text(log, "minimum_amount_out")
758            .or_else(|| {
759                super::utils::text_parser::extract_number_from_text(log, "minimumAmountOut")
760            })
761            .unwrap_or(0);
762
763    let max_amount_in = super::utils::text_parser::extract_number_from_text(log, "max_amount_in")
764        .or_else(|| super::utils::text_parser::extract_number_from_text(log, "maxAmountIn"))
765        .unwrap_or(0);
766
767    let default_pubkey = Pubkey::default();
768    let metadata = create_metadata_simple(
769        signature,
770        slot,
771        tx_index,
772        block_time_us,
773        default_pubkey,
774        grpc_recv_us,
775    );
776
777    Some(DexEvent::RaydiumAmmV4Swap(RaydiumAmmV4SwapEvent {
778        metadata,
779        amount_in,
780        minimum_amount_out,
781        max_amount_in,
782        amount_out,
783        token_program: default_pubkey,
784        amm: default_pubkey,
785        amm_authority: default_pubkey,
786        amm_open_orders: default_pubkey,
787        amm_target_orders: None,
788        pool_coin_token_account: default_pubkey,
789        pool_pc_token_account: default_pubkey,
790        serum_program: default_pubkey,
791        serum_market: default_pubkey,
792        serum_bids: default_pubkey,
793        serum_asks: default_pubkey,
794        serum_event_queue: default_pubkey,
795        serum_coin_vault_account: default_pubkey,
796        serum_pc_vault_account: default_pubkey,
797        serum_vault_signer: default_pubkey,
798        user_source_token_account: default_pubkey,
799        user_destination_token_account: default_pubkey,
800        user_source_owner: default_pubkey,
801
802        ..Default::default()
803    }))
804}
805
806/// 文本回退解析存款事件
807fn parse_deposit_log_fallback(
808    log: &str,
809    signature: Signature,
810    slot: u64,
811    tx_index: u64,
812    block_time_us: Option<i64>,
813    grpc_recv_us: i64,
814) -> Option<DexEvent> {
815    let max_coin_amount =
816        super::utils::text_parser::extract_number_from_text(log, "max_coin_amount")
817            .or_else(|| super::utils::text_parser::extract_number_from_text(log, "maxCoinAmount"))
818            .unwrap_or(0);
819
820    let max_pc_amount = super::utils::text_parser::extract_number_from_text(log, "max_pc_amount")
821        .or_else(|| super::utils::text_parser::extract_number_from_text(log, "maxPcAmount"))
822        .unwrap_or(0);
823
824    let base_side = super::utils::text_parser::extract_number_from_text(log, "base_side")
825        .or_else(|| super::utils::text_parser::extract_number_from_text(log, "baseSide"))
826        .unwrap_or(0);
827
828    let default_pubkey = Pubkey::default();
829    let metadata = create_metadata_simple(
830        signature,
831        slot,
832        tx_index,
833        block_time_us,
834        default_pubkey,
835        grpc_recv_us,
836    );
837
838    Some(DexEvent::RaydiumAmmV4Deposit(RaydiumAmmV4DepositEvent {
839        metadata,
840        max_coin_amount,
841        max_pc_amount,
842        base_side,
843        token_program: default_pubkey,
844        amm: default_pubkey,
845        amm_authority: default_pubkey,
846        amm_open_orders: default_pubkey,
847        amm_target_orders: default_pubkey,
848        lp_mint_address: default_pubkey,
849        pool_coin_token_account: default_pubkey,
850        pool_pc_token_account: default_pubkey,
851        serum_market: default_pubkey,
852        user_coin_token_account: default_pubkey,
853        user_pc_token_account: default_pubkey,
854        user_lp_token_account: default_pubkey,
855        user_owner: default_pubkey,
856        serum_event_queue: default_pubkey,
857    }))
858}
859
860/// 文本回退解析提取事件
861fn parse_withdraw_log_fallback(
862    log: &str,
863    signature: Signature,
864    slot: u64,
865    tx_index: u64,
866    block_time_us: Option<i64>,
867    grpc_recv_us: i64,
868) -> Option<DexEvent> {
869    let amount = super::utils::text_parser::extract_number_from_text(log, "amount").unwrap_or(0);
870
871    let default_pubkey = Pubkey::default();
872    let metadata = create_metadata_simple(
873        signature,
874        slot,
875        tx_index,
876        block_time_us,
877        default_pubkey,
878        grpc_recv_us,
879    );
880
881    Some(DexEvent::RaydiumAmmV4Withdraw(RaydiumAmmV4WithdrawEvent {
882        metadata,
883        amount,
884        token_program: default_pubkey,
885        amm: default_pubkey,
886        amm_authority: default_pubkey,
887        amm_open_orders: default_pubkey,
888        amm_target_orders: default_pubkey,
889        lp_mint_address: default_pubkey,
890        pool_coin_token_account: default_pubkey,
891        pool_pc_token_account: default_pubkey,
892        pool_withdraw_queue: default_pubkey,
893        pool_temp_lp_token_account: default_pubkey,
894        serum_program: default_pubkey,
895        serum_market: default_pubkey,
896        serum_coin_vault_account: default_pubkey,
897        serum_pc_vault_account: default_pubkey,
898        serum_vault_signer: default_pubkey,
899        user_lp_token_account: default_pubkey,
900        user_coin_token_account: default_pubkey,
901        user_pc_token_account: default_pubkey,
902        user_owner: default_pubkey,
903        serum_event_queue: default_pubkey,
904        serum_bids: default_pubkey,
905        serum_asks: default_pubkey,
906    }))
907}