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