Skip to main content

sol_parser_sdk/logs/
meteora_damm.rs

1//! Meteora DAMM V2 日志解析器
2//!
3//! 解析 Meteora DAMM V2 程序的日志事件
4
5use super::utils::*;
6use crate::core::events::*;
7use solana_sdk::signature::Signature;
8
9/// Meteora DAMM V2 事件 discriminator 常量
10pub mod discriminators {
11    pub const SWAP_EVENT: [u8; 8] = [27, 60, 21, 213, 138, 170, 187, 147];
12    pub const SWAP2_EVENT: [u8; 8] = [189, 66, 51, 168, 38, 80, 117, 153];
13    pub const ADD_LIQUIDITY_EVENT: [u8; 8] = [175, 242, 8, 157, 30, 247, 185, 169];
14    pub const REMOVE_LIQUIDITY_EVENT: [u8; 8] = [87, 46, 88, 98, 175, 96, 34, 91];
15    pub const INITIALIZE_POOL_EVENT: [u8; 8] = [228, 50, 246, 85, 203, 66, 134, 37];
16    pub const CREATE_POSITION_EVENT: [u8; 8] = [156, 15, 119, 198, 29, 181, 221, 55];
17    pub const CLOSE_POSITION_EVENT: [u8; 8] = [20, 145, 144, 68, 143, 142, 214, 178];
18    pub const CLAIM_POSITION_FEE_EVENT: [u8; 8] = [198, 182, 183, 52, 97, 12, 49, 56];
19    pub const INITIALIZE_REWARD_EVENT: [u8; 8] = [129, 91, 188, 3, 246, 52, 185, 249];
20    pub const FUND_REWARD_EVENT: [u8; 8] = [104, 233, 237, 122, 199, 191, 121, 85];
21    pub const CLAIM_REWARD_EVENT: [u8; 8] = [218, 86, 147, 200, 235, 188, 215, 231];
22}
23
24/// 主要的 Meteora DAMM V2 日志解析函数
25pub fn parse_log(
26    log: &str,
27    signature: Signature,
28    slot: u64,
29    tx_index: u64,
30    block_time_us: Option<i64>,
31    grpc_recv_us: i64,
32) -> Option<DexEvent> {
33    parse_structured_log(log, signature, slot, tx_index, block_time_us, grpc_recv_us)
34}
35
36/// 解析结构化日志(基于 discriminator)
37fn parse_structured_log(
38    log: &str,
39    signature: Signature,
40    slot: u64,
41    tx_index: u64,
42    block_time_us: Option<i64>,
43    grpc_recv_us: i64,
44) -> Option<DexEvent> {
45    let program_data = extract_program_data(log)?;
46
47    if program_data.len() < 8 {
48        return None;
49    }
50
51    let discriminator: [u8; 8] = program_data[0..8].try_into().ok()?;
52    let data = &program_data[8..];
53
54    match discriminator {
55        discriminators::SWAP_EVENT => {
56            parse_swap_event(data, signature, slot, tx_index, block_time_us, grpc_recv_us)
57        }
58        discriminators::SWAP2_EVENT => {
59            parse_swap2_event(data, signature, slot, tx_index, block_time_us, grpc_recv_us)
60        }
61        discriminators::ADD_LIQUIDITY_EVENT => {
62            parse_add_liquidity_event(data, signature, slot, tx_index, block_time_us, grpc_recv_us)
63        }
64        discriminators::REMOVE_LIQUIDITY_EVENT => parse_remove_liquidity_event(
65            data,
66            signature,
67            slot,
68            tx_index,
69            block_time_us,
70            grpc_recv_us,
71        ),
72        discriminators::INITIALIZE_POOL_EVENT => parse_initialize_pool_event(
73            data,
74            signature,
75            slot,
76            tx_index,
77            block_time_us,
78            grpc_recv_us,
79        ),
80        discriminators::CREATE_POSITION_EVENT => parse_create_position_event(
81            data,
82            signature,
83            slot,
84            tx_index,
85            block_time_us,
86            grpc_recv_us,
87        ),
88        discriminators::CLOSE_POSITION_EVENT => {
89            parse_close_position_event(data, signature, slot, tx_index, block_time_us, grpc_recv_us)
90        }
91        discriminators::CLAIM_POSITION_FEE_EVENT => parse_claim_position_fee_event(
92            data,
93            signature,
94            slot,
95            tx_index,
96            block_time_us,
97            grpc_recv_us,
98        ),
99        discriminators::INITIALIZE_REWARD_EVENT => parse_initialize_reward_event(
100            data,
101            signature,
102            slot,
103            tx_index,
104            block_time_us,
105            grpc_recv_us,
106        ),
107        discriminators::FUND_REWARD_EVENT => {
108            parse_fund_reward_event(data, signature, slot, tx_index, block_time_us, grpc_recv_us)
109        }
110        discriminators::CLAIM_REWARD_EVENT => {
111            parse_claim_reward_event(data, signature, slot, tx_index, block_time_us, grpc_recv_us)
112        }
113        _ => None,
114    }
115}
116
117/// 解析 Swap 事件
118#[inline(always)]
119pub fn parse_swap_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
120    let mut offset = 0;
121
122    let pool = read_pubkey(data, offset)?;
123    offset += 32;
124
125    let _config = read_pubkey(data, offset)?;
126    offset += 32;
127
128    let trade_direction = read_u8(data, offset)?;
129    offset += 1;
130
131    let has_referral = read_bool(data, offset)?;
132    offset += 1;
133
134    let amount_in = read_u64_le(data, offset)?;
135    offset += 8;
136
137    let minimum_amount_out = read_u64_le(data, offset)?;
138    offset += 8;
139
140    let actual_input_amount = read_u64_le(data, offset)?;
141    offset += 8;
142
143    let output_amount = read_u64_le(data, offset)?;
144    offset += 8;
145
146    let next_sqrt_price = read_u128_le(data, offset)?;
147    offset += 16;
148
149    let lp_fee = read_u64_le(data, offset)?;
150    offset += 8;
151
152    let protocol_fee = read_u64_le(data, offset)?;
153    offset += 8;
154
155    let referral_fee = read_u64_le(data, offset)?;
156    offset += 8;
157
158    let _amount_in_dup = read_u64_le(data, offset)?;
159    offset += 8;
160
161    let current_timestamp = read_u64_le(data, offset)?;
162
163    Some(DexEvent::MeteoraDammV2Swap(MeteoraDammV2SwapEvent {
164        metadata,
165        pool,
166        trade_direction,
167        has_referral,
168        amount_in,
169        minimum_amount_out,
170        output_amount,
171        next_sqrt_price,
172        lp_fee,
173        protocol_fee,
174        partner_fee: 0,
175        referral_fee,
176        actual_amount_in: actual_input_amount,
177        current_timestamp,
178        ..Default::default()
179    }))
180}
181
182fn parse_swap_event(
183    data: &[u8],
184    signature: Signature,
185    slot: u64,
186    tx_index: u64,
187    block_time_us: Option<i64>,
188    grpc_recv_us: i64,
189) -> Option<DexEvent> {
190    let pool = read_pubkey(data, 0)?;
191    let metadata =
192        create_metadata_simple(signature, slot, tx_index, block_time_us, pool, grpc_recv_us);
193    parse_swap_from_data(data, metadata)
194}
195
196/// 解析 Swap2 事件 (EvtSwap2 格式)
197#[inline(always)]
198pub fn parse_swap2_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
199    let mut offset = 0;
200
201    let pool = read_pubkey(data, offset)?;
202    offset += 32;
203
204    let trade_direction = read_u8(data, offset)?;
205    offset += 1;
206
207    let _collect_fee_mode = read_u8(data, offset)?;
208    offset += 1;
209
210    let has_referral = read_bool(data, offset)?;
211    offset += 1;
212
213    let amount_0 = read_u64_le(data, offset)?;
214    offset += 8;
215
216    let amount_1 = read_u64_le(data, offset)?;
217    offset += 8;
218
219    let swap_mode = read_u8(data, offset)?;
220    offset += 1;
221
222    let included_fee_input_amount = read_u64_le(data, offset)?;
223    offset += 8;
224
225    let _excluded_fee_input_amount = read_u64_le(data, offset)?;
226    offset += 8;
227
228    let _amount_left = read_u64_le(data, offset)?;
229    offset += 8;
230
231    let output_amount = read_u64_le(data, offset)?;
232    offset += 8;
233
234    let next_sqrt_price = read_u128_le(data, offset)?;
235    offset += 16;
236
237    let lp_fee = read_u64_le(data, offset)?;
238    offset += 8;
239
240    let protocol_fee = read_u64_le(data, offset)?;
241    offset += 8;
242
243    let referral_fee = read_u64_le(data, offset)?;
244    offset += 8;
245
246    let _quote_reserve_amount = read_u64_le(data, offset)?;
247    offset += 8;
248
249    let _migration_threshold = read_u64_le(data, offset)?;
250    offset += 8;
251
252    let current_timestamp = read_u64_le(data, offset)?;
253
254    let (amount_in, minimum_amount_out) =
255        if swap_mode == 0 { (amount_0, amount_1) } else { (amount_1, amount_0) };
256
257    Some(DexEvent::MeteoraDammV2Swap(MeteoraDammV2SwapEvent {
258        metadata,
259        pool,
260        trade_direction,
261        has_referral,
262        amount_in,
263        minimum_amount_out,
264        output_amount,
265        next_sqrt_price,
266        lp_fee,
267        protocol_fee,
268        partner_fee: 0,
269        referral_fee,
270        actual_amount_in: included_fee_input_amount,
271        current_timestamp,
272        ..Default::default()
273    }))
274}
275
276fn parse_swap2_event(
277    data: &[u8],
278    signature: Signature,
279    slot: u64,
280    tx_index: u64,
281    block_time_us: Option<i64>,
282    grpc_recv_us: i64,
283) -> Option<DexEvent> {
284    let pool = read_pubkey(data, 0)?;
285    let metadata =
286        create_metadata_simple(signature, slot, tx_index, block_time_us, pool, grpc_recv_us);
287    parse_swap2_from_data(data, metadata)
288}
289
290/// 解析 Add Liquidity 事件
291#[inline(always)]
292pub fn parse_add_liquidity_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
293    let mut offset = 0;
294
295    let pool = read_pubkey(data, offset)?;
296    offset += 32;
297
298    let position = read_pubkey(data, offset)?;
299    offset += 32;
300
301    let owner = read_pubkey(data, offset)?;
302    offset += 32;
303
304    let liquidity_delta = read_u128_le(data, offset)?;
305    offset += 16;
306
307    let token_a_amount_threshold = read_u64_le(data, offset)?;
308    offset += 8;
309
310    let token_b_amount_threshold = read_u64_le(data, offset)?;
311    offset += 8;
312
313    let token_a_amount = read_u64_le(data, offset)?;
314    offset += 8;
315
316    let token_b_amount = read_u64_le(data, offset)?;
317    offset += 8;
318
319    let total_amount_a = read_u64_le(data, offset)?;
320    offset += 8;
321
322    let total_amount_b = read_u64_le(data, offset)?;
323
324    Some(DexEvent::MeteoraDammV2AddLiquidity(MeteoraDammV2AddLiquidityEvent {
325        metadata,
326        pool,
327        position,
328        owner,
329        liquidity_delta,
330        token_a_amount_threshold,
331        token_b_amount_threshold,
332        token_a_amount,
333        token_b_amount,
334        total_amount_a,
335        total_amount_b,
336    }))
337}
338
339fn parse_add_liquidity_event(
340    data: &[u8],
341    signature: Signature,
342    slot: u64,
343    tx_index: u64,
344    block_time_us: Option<i64>,
345    grpc_recv_us: i64,
346) -> Option<DexEvent> {
347    let pool = read_pubkey(data, 0)?;
348    let metadata =
349        create_metadata_simple(signature, slot, tx_index, block_time_us, pool, grpc_recv_us);
350    parse_add_liquidity_from_data(data, metadata)
351}
352
353/// 解析 Remove Liquidity 事件
354#[inline(always)]
355pub fn parse_remove_liquidity_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
356    let mut offset = 0;
357
358    let pool = read_pubkey(data, offset)?;
359    offset += 32;
360
361    let position = read_pubkey(data, offset)?;
362    offset += 32;
363
364    let owner = read_pubkey(data, offset)?;
365    offset += 32;
366
367    let liquidity_delta = read_u128_le(data, offset)?;
368    offset += 16;
369
370    let token_a_amount_threshold = read_u64_le(data, offset)?;
371    offset += 8;
372
373    let token_b_amount_threshold = read_u64_le(data, offset)?;
374    offset += 8;
375
376    let token_a_amount = read_u64_le(data, offset)?;
377    offset += 8;
378
379    let token_b_amount = read_u64_le(data, offset)?;
380
381    Some(DexEvent::MeteoraDammV2RemoveLiquidity(MeteoraDammV2RemoveLiquidityEvent {
382        metadata,
383        pool,
384        position,
385        owner,
386        liquidity_delta,
387        token_a_amount_threshold,
388        token_b_amount_threshold,
389        token_a_amount,
390        token_b_amount,
391    }))
392}
393
394fn parse_remove_liquidity_event(
395    data: &[u8],
396    signature: Signature,
397    slot: u64,
398    tx_index: u64,
399    block_time_us: Option<i64>,
400    grpc_recv_us: i64,
401) -> Option<DexEvent> {
402    let pool = read_pubkey(data, 0)?;
403    let metadata =
404        create_metadata_simple(signature, slot, tx_index, block_time_us, pool, grpc_recv_us);
405    parse_remove_liquidity_from_data(data, metadata)
406}
407
408/// 解析 Initialize Pool 事件
409fn parse_initialize_pool_event(
410    data: &[u8],
411    signature: Signature,
412    slot: u64,
413    tx_index: u64,
414    block_time_us: Option<i64>,
415    grpc_recv_us: i64,
416) -> Option<DexEvent> {
417    let pool = read_pubkey(data, 0)?;
418    let metadata =
419        create_metadata_simple(signature, slot, tx_index, block_time_us, pool, grpc_recv_us);
420    parse_initialize_pool_from_data(data, metadata)
421}
422
423/// 解析 Initialize Pool 事件载荷
424#[inline(always)]
425pub fn parse_initialize_pool_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
426    let mut offset = 0usize;
427
428    let pool = read_pubkey(data, offset)?;
429    offset += 32;
430    let token_a_mint = read_pubkey(data, offset)?;
431    offset += 32;
432    let token_b_mint = read_pubkey(data, offset)?;
433    offset += 32;
434    let creator = read_pubkey(data, offset)?;
435    offset += 32;
436    let payer = read_pubkey(data, offset)?;
437    offset += 32;
438    let alpha_vault = read_pubkey(data, offset)?;
439    offset += 32;
440
441    offset = skip_pool_fee_parameters(data, offset)?;
442    if data.len() < offset + 109 {
443        return None;
444    }
445
446    let sqrt_min_price = read_u128_le(data, offset)?;
447    offset += 16;
448    let sqrt_max_price = read_u128_le(data, offset)?;
449    offset += 16;
450    let activation_type = read_u8(data, offset)?;
451    offset += 1;
452    let collect_fee_mode = read_u8(data, offset)?;
453    offset += 1;
454    let liquidity = read_u128_le(data, offset)?;
455    offset += 16;
456    let sqrt_price = read_u128_le(data, offset)?;
457    offset += 16;
458    let activation_point = Some(read_u64_le(data, offset)?);
459    offset += 8;
460    let token_a_flag = read_u8(data, offset)?;
461    offset += 1;
462    let token_b_flag = read_u8(data, offset)?;
463    offset += 1;
464    let token_a_amount = read_u64_le(data, offset)?;
465    offset += 8;
466    let token_b_amount = read_u64_le(data, offset)?;
467    offset += 8;
468    let total_amount_a = read_u64_le(data, offset)?;
469    offset += 8;
470    let total_amount_b = read_u64_le(data, offset)?;
471    offset += 8;
472    let pool_type = read_u8(data, offset)?;
473
474    Some(DexEvent::MeteoraDammV2InitializePool(MeteoraDammV2InitializePoolEvent {
475        metadata,
476        pool,
477        token_a_mint,
478        token_b_mint,
479        creator,
480        payer,
481        alpha_vault,
482        sqrt_min_price,
483        sqrt_max_price,
484        activation_type,
485        collect_fee_mode,
486        liquidity,
487        sqrt_price,
488        activation_point,
489        token_a_flag,
490        token_b_flag,
491        token_a_amount,
492        token_b_amount,
493        total_amount_a,
494        total_amount_b,
495        pool_type,
496        ..Default::default()
497    }))
498}
499
500#[inline(always)]
501fn skip_pool_fee_parameters(data: &[u8], offset: usize) -> Option<usize> {
502    let tag_offset = offset + 30;
503    let tag = *data.get(tag_offset)?;
504    match tag {
505        0 => Some(tag_offset + 1),
506        1 => data.get(tag_offset + 1..tag_offset + 33).map(|_| tag_offset + 33),
507        _ => None,
508    }
509}
510
511/// 解析 Create Position 事件
512#[inline(always)]
513pub fn parse_create_position_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
514    let mut offset = 0;
515
516    let pool = read_pubkey(data, offset)?;
517    offset += 32;
518
519    let owner = read_pubkey(data, offset)?;
520    offset += 32;
521
522    let position = read_pubkey(data, offset)?;
523    offset += 32;
524
525    let position_nft_mint = read_pubkey(data, offset)?;
526
527    Some(DexEvent::MeteoraDammV2CreatePosition(MeteoraDammV2CreatePositionEvent {
528        metadata,
529        pool,
530        owner,
531        position,
532        position_nft_mint,
533    }))
534}
535
536fn parse_create_position_event(
537    data: &[u8],
538    signature: Signature,
539    slot: u64,
540    tx_index: u64,
541    block_time_us: Option<i64>,
542    grpc_recv_us: i64,
543) -> Option<DexEvent> {
544    let pool = read_pubkey(data, 0)?;
545    let metadata =
546        create_metadata_simple(signature, slot, tx_index, block_time_us, pool, grpc_recv_us);
547    parse_create_position_from_data(data, metadata)
548}
549
550/// 解析 Close Position 事件
551#[inline(always)]
552pub fn parse_close_position_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
553    let mut offset = 0;
554
555    let pool = read_pubkey(data, offset)?;
556    offset += 32;
557
558    let owner = read_pubkey(data, offset)?;
559    offset += 32;
560
561    let position = read_pubkey(data, offset)?;
562    offset += 32;
563
564    let position_nft_mint = read_pubkey(data, offset)?;
565
566    Some(DexEvent::MeteoraDammV2ClosePosition(MeteoraDammV2ClosePositionEvent {
567        metadata,
568        pool,
569        owner,
570        position,
571        position_nft_mint,
572    }))
573}
574
575fn parse_close_position_event(
576    data: &[u8],
577    signature: Signature,
578    slot: u64,
579    tx_index: u64,
580    block_time_us: Option<i64>,
581    grpc_recv_us: i64,
582) -> Option<DexEvent> {
583    let pool = read_pubkey(data, 0)?;
584    let metadata =
585        create_metadata_simple(signature, slot, tx_index, block_time_us, pool, grpc_recv_us);
586    parse_close_position_from_data(data, metadata)
587}
588
589/// 解析 Claim Position Fee 事件
590fn parse_claim_position_fee_event(
591    data: &[u8],
592    signature: Signature,
593    slot: u64,
594    tx_index: u64,
595    block_time_us: Option<i64>,
596    grpc_recv_us: i64,
597) -> Option<DexEvent> {
598    // let mut offset = 0;
599
600    // let lb_pair = read_pubkey(data, offset)?;
601    // offset += 32;
602
603    // let position = read_pubkey(data, offset)?;
604    // offset += 32;
605
606    // let owner = read_pubkey(data, offset)?;
607    // offset += 32;
608
609    // let fee_x = read_u64_le(data, offset)?;
610    // offset += 8;
611
612    // let fee_y = read_u64_le(data, offset)?;
613
614    // let metadata =
615    //     create_metadata_simple(signature, slot, tx_index, block_time_us, lb_pair, grpc_recv_us);
616
617    // Some(DexEvent::MeteoraDammV2ClaimPositionFee(MeteoraDammV2ClaimPositionFeeEvent {
618    //     metadata,
619    //     lb_pair,
620    //     position,
621    //     owner,
622    //     fee_x,
623    //     fee_y,
624    // }))
625    None
626}
627
628/// 解析 Initialize Reward 事件
629fn parse_initialize_reward_event(
630    data: &[u8],
631    signature: Signature,
632    slot: u64,
633    tx_index: u64,
634    block_time_us: Option<i64>,
635    grpc_recv_us: i64,
636) -> Option<DexEvent> {
637    // let mut offset = 0;
638
639    // let lb_pair = read_pubkey(data, offset)?;
640    // offset += 32;
641
642    // let reward_mint = read_pubkey(data, offset)?;
643    // offset += 32;
644
645    // let funder = read_pubkey(data, offset)?;
646    // offset += 32;
647
648    // let reward_index = read_u64_le(data, offset)?;
649    // offset += 8;
650
651    // let reward_duration = read_u64_le(data, offset)?;
652
653    // let metadata =
654    //     create_metadata_simple(signature, slot, tx_index, block_time_us, lb_pair, grpc_recv_us);
655
656    // Some(DexEvent::MeteoraDammV2InitializeReward(MeteoraDammV2InitializeRewardEvent {
657    //     metadata,
658    //     lb_pair,
659    //     reward_mint,
660    //     funder,
661    //     reward_index,
662    //     reward_duration,
663    // }))
664    None
665}
666
667/// 解析 Fund Reward 事件
668fn parse_fund_reward_event(
669    data: &[u8],
670    signature: Signature,
671    slot: u64,
672    tx_index: u64,
673    block_time_us: Option<i64>,
674    grpc_recv_us: i64,
675) -> Option<DexEvent> {
676    // let mut offset = 0;
677
678    // let lb_pair = read_pubkey(data, offset)?;
679    // offset += 32;
680
681    // let funder = read_pubkey(data, offset)?;
682    // offset += 32;
683
684    // let reward_index = read_u64_le(data, offset)?;
685    // offset += 8;
686
687    // let amount = read_u64_le(data, offset)?;
688
689    // let metadata =
690    //     create_metadata_simple(signature, slot, tx_index, block_time_us, lb_pair, grpc_recv_us);
691
692    // Some(DexEvent::MeteoraDammV2FundReward(MeteoraDammV2FundRewardEvent {
693    //     metadata,
694    //     lb_pair,
695    //     funder,
696    //     reward_index,
697    //     amount,
698    // }))
699    None
700}
701
702/// 解析 Claim Reward 事件
703fn parse_claim_reward_event(
704    data: &[u8],
705    signature: Signature,
706    slot: u64,
707    tx_index: u64,
708    block_time_us: Option<i64>,
709    grpc_recv_us: i64,
710) -> Option<DexEvent> {
711    // let mut offset = 0;
712
713    // let lb_pair = read_pubkey(data, offset)?;
714    // offset += 32;
715
716    // let position = read_pubkey(data, offset)?;
717    // offset += 32;
718
719    // let owner = read_pubkey(data, offset)?;
720    // offset += 32;
721
722    // let reward_index = read_u64_le(data, offset)?;
723    // offset += 8;
724
725    // let total_reward = read_u64_le(data, offset)?;
726
727    // let metadata =
728    //     create_metadata_simple(signature, slot, tx_index, block_time_us, lb_pair, grpc_recv_us);
729
730    // Some(DexEvent::MeteoraDammV2ClaimReward(MeteoraDammV2ClaimRewardEvent {
731    //     metadata,
732    //     lb_pair,
733    //     position,
734    //     owner,
735    //     reward_index,
736    //     total_reward,
737    // }))
738    None
739}
740
741/// 解析文本格式日志
742fn parse_text_log(
743    _log: &str,
744    _signature: Signature,
745    _slot: u64,
746    tx_index: u64,
747    _block_time_us: Option<i64>,
748) -> Option<DexEvent> {
749    // 目前暂不实现文本解析,主要依赖结构化解析
750    None
751}