meteora-sdk 0.7.0

meteora sdk
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
use bytemuck::{Pod, Zeroable};
use solana_sdk::pubkey::Pubkey;

/// DAMMV2 liquidity pool data size (1104 bytes from the official layout)
pub const DAMMV2_LIQUIDITY_POOL_DATA_SIZE: usize = 1104;
const DISCRIMINATOR_LEN: usize = 8;

unsafe impl Pod for DAMMV2LiquidityPool {}
unsafe impl Zeroable for DAMMV2LiquidityPool {}

#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct DAMMV2LiquidityPool {
    // 0-159: Pool fee structure (160 bytes)
    pub pool_fees: [u8; 160],
    // 160-191: Token A mint address
    pub token_a_mint: [u8; 32],
    // 192-223: Token B mint address
    pub token_b_mint: [u8; 32],
    // 224-255: Token A vault address
    pub token_a_vault: [u8; 32],
    // 256-287: Token B vault address
    pub token_b_vault: [u8; 32],
    // 288-319: Whitelisted vault address
    pub whitelisted_vault: [u8; 32],
    // 320-351: Partner address
    pub partner: [u8; 32],
    // 352-367: Liquidity (u128, 16 bytes)
    pub liquidity: [u8; 16],
    // 368-383: Padding/reserve (u128, 16 bytes)
    pub _padding: [u8; 16],
    // 384-391: Protocol A fee (u64, 8 bytes)
    pub protocol_a_fee: [u8; 8],
    // 392-399: Protocol B fee (u64, 8 bytes)
    pub protocol_b_fee: [u8; 8],
    // 400-407: Partner A fee (u64, 8 bytes)
    pub partner_a_fee: [u8; 8],
    // 408-415: Partner B fee (u64, 8 bytes)
    pub partner_b_fee: [u8; 8],
    // 416-431: Min price (sqrt, u128, 16 bytes)
    pub sqrt_min_price: [u8; 16],
    // 432-447: Max price (sqrt, u128, 16 bytes)
    pub sqrt_max_price: [u8; 16],
    // 448-463: Current price (sqrt, u128, 16 bytes)
    pub sqrt_price: [u8; 16],
    // 464-471: Activation point (u64, 8 bytes)
    pub activation_point: [u8; 8],
    // 472: Activation type (u8, 1 byte)
    pub activation_type: u8,
    // 473: Pool status (u8, 1 byte)
    pub pool_status: u8,
    // 474: Token A flag (u8, 1 byte)
    pub token_a_flag: u8,
    // 475: Token B flag (u8, 1 byte)
    pub token_b_flag: u8,
    // 476: Collect fee mode (u8, 1 byte)
    pub collect_fee_mode: u8,
    // 477: Pool type (u8, 1 byte)
    pub pool_type: u8,
    // 478: Pool version (u8, 1 byte)
    pub version: u8,
    // 479: Padding (u8, 1 byte)
    pub _padding_0: u8,
    // 480-511: Fee A per liquidity (U256, 32 bytes)
    pub fee_a_per_liquidity: [u8; 32],
    // 512-543: Fee B per liquidity (U256, 32 bytes)
    pub fee_b_per_liquidity: [u8; 32],
    // 544-559: Permanent lock liquidity (u128, 16 bytes)
    pub permanent_lock_liquidity: [u8; 16],
    // 560-639: Pool metrics (80 bytes)
    pub metrics: [u8; 80],
    // 640-671: Pool creator (32 bytes)
    pub creator: [u8; 32],
    // 672-719: Padding (6 * u64 = 48 bytes)
    pub _padding_1: [u8; 48],
    // 720-1103: Reward infos (3 * 128 = 384 bytes)
    pub reward_infos: [[u8; 128]; 3],
}

/// DAMMV2 pool parsed data structure
#[derive(Debug, Clone)]
pub struct DAMMV2LiquidityPoolData {
    // Pool fees structure - simplified for now
    pub pool_fees: PoolFeesData,
    // Token addresses
    pub token_a_mint: Pubkey,
    pub token_b_mint: Pubkey,
    pub token_a_vault: Pubkey,
    pub token_b_vault: Pubkey,
    pub whitelisted_vault: Pubkey,
    pub partner: Pubkey,
    pub creator: Pubkey,
    // Liquidity and fees
    pub liquidity: u128,
    pub protocol_a_fee: u64,
    pub protocol_b_fee: u64,
    pub partner_a_fee: u64,
    pub partner_b_fee: u64,
    pub permanent_lock_liquidity: u128,
    // Price information (sqrt prices)
    pub sqrt_min_price: u128,
    pub sqrt_max_price: u128,
    pub sqrt_price: u128,
    // Activation info
    pub activation_point: u64,
    pub activation_type: u8,
    // Status flags
    pub pool_status: u8,
    pub token_a_flag: u8,
    pub token_b_flag: u8,
    pub collect_fee_mode: u8,
    pub pool_type: u8,
    pub version: u8,
    // Cumulative fees per liquidity
    pub fee_a_per_liquidity: [u8; 32], // U256
    pub fee_b_per_liquidity: [u8; 32], // U256
    // Pool metrics
    pub metrics: PoolMetricsData,
    // Reward information
    pub reward_infos: [RewardInfoData; 3],
}

/// Pool fees data structure
#[derive(Debug, Clone, Default)]
pub struct PoolFeesData {
    // Simplified - actual structure is more complex
    pub base_fee_numerator: u32,
    pub base_fee_denominator: u32,
    pub dynamic_fee: DynamicFeeData,
}

/// Dynamic fee data for DAMMV2
#[derive(Debug, Clone, Default)]
pub struct DynamicFeeData {
    pub is_dynamic_fee_enabled: bool,
    pub bin_step: u128,
    pub volatility_reference: u128,
    pub volatility_accumulator: u128,
    pub last_update_timestamp: u64,
    pub activation_point: u64,
}

/// Pool metrics data
#[derive(Debug, Clone)]
pub struct PoolMetricsData {
    pub total_lp_a_fee: u128,
    pub total_lp_b_fee: u128,
    pub total_protocol_a_fee: u64,
    pub total_protocol_b_fee: u64,
    pub total_partner_a_fee: u64,
    pub total_partner_b_fee: u64,
    pub total_position: u64,
}

/// Reward info data
#[derive(Debug, Clone)]
pub struct RewardInfoData {
    pub initialized: u8,
    pub reward_token_flag: u8,
    pub mint: Pubkey,
    pub vault: Pubkey,
    pub funder: Pubkey,
    pub reward_duration: u64,
    pub reward_duration_end: u64,
    pub reward_rate: u128,
    pub reward_per_token_stored: [u8; 32], // U256
    pub last_update_time: u64,
    pub cumulative_seconds_with_empty_liquidity_reward: u64,
}

impl DAMMV2LiquidityPool {
    /// Parse DAMMV2 pool data from byte array
    pub fn get_liquidity_pool_info(data: &[u8]) -> Result<DAMMV2LiquidityPoolData, String> {
        let total_expected_size = DISCRIMINATOR_LEN + DAMMV2_LIQUIDITY_POOL_DATA_SIZE;
        if data.len() != total_expected_size {
            return Err(format!(
                "DAMMV2 pool data size mismatch. Expected {} (1104 data + 8 discriminator), got {}",
                total_expected_size,
                data.len()
            ));
        }
        let mut offset: usize = DISCRIMINATOR_LEN;
        let read_pubkey = |d: &[u8], o: &mut usize| -> Result<Pubkey, String> {
            if *o + 32 > d.len() {
                return Err(format!(
                    "Reading pubkey out of bounds: offset={}, len={}",
                    *o,
                    d.len()
                ));
            }
            let pk = Pubkey::new_from_array(d[*o..*o + 32].try_into().unwrap());
            *o += 32;
            Ok(pk)
        };
        let read_u8 = |d: &[u8], o: &mut usize| -> Result<u8, String> {
            if *o >= d.len() {
                return Err(format!(
                    "Reading u8 out of bounds: offset={}, len={}",
                    *o,
                    d.len()
                ));
            }
            let v = d[*o];
            *o += 1;
            Ok(v)
        };
        let read_u64 = |d: &[u8], o: &mut usize| -> Result<u64, String> {
            if *o + 8 > d.len() {
                return Err(format!(
                    "Reading u64 out of bounds: offset={}, len={}",
                    *o,
                    d.len()
                ));
            }
            let v = u64::from_le_bytes(d[*o..*o + 8].try_into().unwrap());
            *o += 8;
            Ok(v)
        };
        let read_u128 = |d: &[u8], o: &mut usize| -> Result<u128, String> {
            if *o + 16 > d.len() {
                return Err(format!(
                    "Reading u128 out of bounds: offset={}, len={}",
                    *o,
                    d.len()
                ));
            }
            let v = u128::from_le_bytes(d[*o..*o + 16].try_into().unwrap());
            *o += 16;
            Ok(v)
        };
        let read_bytes = |d: &[u8], o: &mut usize, len: usize| -> Result<Vec<u8>, String> {
            if *o + len > d.len() {
                return Err(format!(
                    "Reading {} bytes out of bounds: offset={}, len={}",
                    len,
                    *o,
                    d.len()
                ));
            }
            let bytes = d[*o..*o + len].to_vec();
            *o += len;
            Ok(bytes)
        };
        let pool_fees_bytes = read_bytes(data, &mut offset, 160)?;
        let pool_fees = parse_pool_fees(&pool_fees_bytes);
        let token_a_mint = read_pubkey(data, &mut offset)?;
        let token_b_mint = read_pubkey(data, &mut offset)?;
        let token_a_vault = read_pubkey(data, &mut offset)?;
        let token_b_vault = read_pubkey(data, &mut offset)?;
        let whitelisted_vault = read_pubkey(data, &mut offset)?;
        let partner = read_pubkey(data, &mut offset)?;
        let liquidity = read_u128(data, &mut offset)?;
        let _padding_reserve = read_u128(data, &mut offset)?;
        let protocol_a_fee = read_u64(data, &mut offset)?;
        let protocol_b_fee = read_u64(data, &mut offset)?;
        let partner_a_fee = read_u64(data, &mut offset)?;
        let partner_b_fee = read_u64(data, &mut offset)?;
        let sqrt_min_price = read_u128(data, &mut offset)?;
        let sqrt_max_price = read_u128(data, &mut offset)?;
        let sqrt_price = read_u128(data, &mut offset)?;
        let activation_point = read_u64(data, &mut offset)?;
        let activation_type = read_u8(data, &mut offset)?;
        let pool_status = read_u8(data, &mut offset)?;
        let token_a_flag = read_u8(data, &mut offset)?;
        let token_b_flag = read_u8(data, &mut offset)?;
        let collect_fee_mode = read_u8(data, &mut offset)?;
        let pool_type = read_u8(data, &mut offset)?;
        let version = read_u8(data, &mut offset)?;
        let _padding_0 = read_u8(data, &mut offset)?;
        let fee_a_per_liquidity_bytes = read_bytes(data, &mut offset, 32)?;
        let fee_b_per_liquidity_bytes = read_bytes(data, &mut offset, 32)?;
        let fee_a_per_liquidity: [u8; 32] = fee_a_per_liquidity_bytes
            .try_into()
            .map_err(|_| "Failed to convert fee_a_per_liquidity bytes".to_string())?;
        let fee_b_per_liquidity: [u8; 32] = fee_b_per_liquidity_bytes
            .try_into()
            .map_err(|_| "Failed to convert fee_b_per_liquidity bytes".to_string())?;
        let permanent_lock_liquidity = read_u128(data, &mut offset)?;
        let metrics_bytes = read_bytes(data, &mut offset, 80)?;
        let metrics = parse_pool_metrics(&metrics_bytes);
        let creator = read_pubkey(data, &mut offset)?;
        offset += 48;
        let mut reward_infos: [RewardInfoData; 3] = std::array::from_fn(|_| RewardInfoData {
            initialized: 0,
            reward_token_flag: 0,
            mint: Pubkey::default(),
            vault: Pubkey::default(),
            funder: Pubkey::default(),
            reward_duration: 0,
            reward_duration_end: 0,
            reward_rate: 0,
            reward_per_token_stored: [0; 32],
            last_update_time: 0,
            cumulative_seconds_with_empty_liquidity_reward: 0,
        });
        for i in 0..3 {
            let start_offset = offset;
            if offset >= data.len() {
                break;
            }
            reward_infos[i].initialized = read_u8(data, &mut offset)?;
            if reward_infos[i].initialized == 0 {
                if i == 0 {
                    offset = 600 + 184;
                } else if i == 1 {
                    offset = 600 + 184 * 2;
                } else if i == 2 {
                    offset = data.len();
                }
                continue;
            }
            reward_infos[i].reward_token_flag = read_u8(data, &mut offset)?;
            offset += 6;
            reward_infos[i].mint = read_pubkey(data, &mut offset)?;
            reward_infos[i].vault = read_pubkey(data, &mut offset)?;
            reward_infos[i].funder = read_pubkey(data, &mut offset)?;
            reward_infos[i].reward_duration = read_u64(data, &mut offset)?;
            reward_infos[i].reward_duration_end = read_u64(data, &mut offset)?;
            reward_infos[i].reward_rate = read_u128(data, &mut offset)?;
            let reward_per_token_bytes = read_bytes(data, &mut offset, 32)?;
            reward_infos[i].reward_per_token_stored = reward_per_token_bytes
                .try_into()
                .map_err(|_| "Failed to convert reward_per_token_stored bytes".to_string())?;
            reward_infos[i].last_update_time = read_u64(data, &mut offset)?;
            reward_infos[i].cumulative_seconds_with_empty_liquidity_reward =
                read_u64(data, &mut offset)?;
        }
        if offset != data.len() {
            let remaining = data.len() - offset;
            if remaining > 0 {
                offset = data.len();
            }
        }
        Ok(DAMMV2LiquidityPoolData {
            pool_fees,
            token_a_mint,
            token_b_mint,
            token_a_vault,
            token_b_vault,
            whitelisted_vault,
            partner,
            creator,
            liquidity,
            protocol_a_fee,
            protocol_b_fee,
            partner_a_fee,
            partner_b_fee,
            permanent_lock_liquidity,
            sqrt_min_price,
            sqrt_max_price,
            sqrt_price,
            activation_point,
            activation_type,
            pool_status,
            token_a_flag,
            token_b_flag,
            collect_fee_mode,
            pool_type,
            version,
            fee_a_per_liquidity,
            fee_b_per_liquidity,
            metrics,
            reward_infos,
        })
    }

    // Getter methods for public keys
    pub fn token_a_mint_pubkey(&self) -> Pubkey {
        Pubkey::new_from_array(self.token_a_mint)
    }

    pub fn token_b_mint_pubkey(&self) -> Pubkey {
        Pubkey::new_from_array(self.token_b_mint)
    }

    pub fn token_a_vault_pubkey(&self) -> Pubkey {
        Pubkey::new_from_array(self.token_a_vault)
    }

    pub fn token_b_vault_pubkey(&self) -> Pubkey {
        Pubkey::new_from_array(self.token_b_vault)
    }

    pub fn whitelisted_vault_pubkey(&self) -> Pubkey {
        Pubkey::new_from_array(self.whitelisted_vault)
    }

    pub fn partner_pubkey(&self) -> Pubkey {
        Pubkey::new_from_array(self.partner)
    }

    pub fn creator_pubkey(&self) -> Pubkey {
        Pubkey::new_from_array(self.creator)
    }
}

impl DAMMV2LiquidityPoolData {
    /// Get current price from sqrt price
    pub fn get_price(&self) -> f64 {
        let sqrt_price_f64 = self.sqrt_price as f64;
        let sqrt_price = sqrt_price_f64 / (1u128 << 64) as f64;
        let price = sqrt_price * sqrt_price;
        price
    }
    /// Get tick-based price (for compatibility with CLMM)
    pub fn get_tick_price(&self) -> f64 {
        let price = self.get_price();
        price
    }
    /// Get token A amount in the pool (approximation)
    pub fn get_token_a_amount(&self) -> f64 {
        if self.liquidity == 0 {
            return 0.0;
        }
        let sqrt_price = (self.sqrt_price as f64) / (1u128 << 64) as f64;
        let liquidity = self.liquidity as f64;
        liquidity / sqrt_price
    }

    /// Get token B amount in the pool (approximation)
    pub fn get_token_b_amount(&self) -> f64 {
        if self.liquidity == 0 {
            return 0.0;
        }
        // DAMMV2 uses dynamic weights, so this is an approximation
        let sqrt_price = (self.sqrt_price as f64) / (1u128 << 64) as f64;
        let liquidity = self.liquidity as f64;
        // Simplified calculation
        liquidity * sqrt_price
    }
    /// Check if dynamic fee is enabled
    pub fn is_dynamic_fee_enabled(&self) -> bool {
        self.pool_fees.dynamic_fee.is_dynamic_fee_enabled
    }
    /// Get pool status as string
    pub fn get_pool_status_str(&self) -> &'static str {
        match self.pool_status {
            0 => "Enable",
            1 => "Disable",
            _ => "Unknown",
        }
    }
    /// Get collect fee mode as string
    pub fn get_collect_fee_mode_str(&self) -> &'static str {
        match self.collect_fee_mode {
            0 => "BothToken",
            1 => "OnlyA",
            2 => "OnlyB",
            _ => "Unknown",
        }
    }
    /// Get pool type as string
    pub fn get_pool_type_str(&self) -> &'static str {
        match self.pool_type {
            0 => "Permissionless",
            1 => "Customizable",
            _ => "Unknown",
        }
    }
    /// Get activation type as string
    pub fn get_activation_type_str(&self) -> &'static str {
        match self.activation_type {
            0 => "BySlot",
            1 => "ByTimestamp",
            _ => "Unknown",
        }
    }
}

fn parse_pool_fees(bytes: &[u8]) -> PoolFeesData {
    if bytes.len() < 160 {
        println!(
            "Warning: PoolFeesStruct data too short: {} < 160",
            bytes.len()
        );
        return PoolFeesData::default();
    }
    let mut offset = 0;
    let cliff_fee_numerator =
        u64::from_le_bytes(bytes[offset..offset + 8].try_into().unwrap_or([0; 8]));
    offset += 8;
    let base_fee_mode = bytes[offset];
    offset += 1;
    offset += 5;
    let first_factor = u16::from_le_bytes(bytes[offset..offset + 2].try_into().unwrap_or([0; 2]));
    offset += 2;
    let second_factor = bytes[offset..offset + 8].try_into().unwrap_or([0; 8]);
    offset += 8;
    let third_factor = u64::from_le_bytes(bytes[offset..offset + 8].try_into().unwrap_or([0; 8]));
    offset += 8;
    offset += 8;
    let protocol_fee_percent = bytes[offset];
    offset += 1;
    let partner_fee_percent = bytes[offset];
    offset += 1;
    let referral_fee_percent = bytes[offset];
    offset += 1;
    offset += 5;
    let dynamic_initialized = bytes[offset];
    offset += 1;
    offset += 7;
    let max_volatility_accumulator =
        u32::from_le_bytes(bytes[offset..offset + 4].try_into().unwrap_or([0; 4]));
    offset += 4;
    let variable_fee_control =
        u32::from_le_bytes(bytes[offset..offset + 4].try_into().unwrap_or([0; 4]));
    offset += 4;
    let bin_step = u16::from_le_bytes(bytes[offset..offset + 2].try_into().unwrap_or([0; 2]));
    offset += 2;
    let filter_period = u16::from_le_bytes(bytes[offset..offset + 2].try_into().unwrap_or([0; 2]));
    offset += 2;
    let decay_period = u16::from_le_bytes(bytes[offset..offset + 2].try_into().unwrap_or([0; 2]));
    offset += 2;
    let reduction_factor =
        u16::from_le_bytes(bytes[offset..offset + 2].try_into().unwrap_or([0; 2]));
    offset += 2;
    let last_update_timestamp =
        u64::from_le_bytes(bytes[offset..offset + 8].try_into().unwrap_or([0; 8]));
    offset += 8;
    let bin_step_u128 =
        u128::from_le_bytes(bytes[offset..offset + 16].try_into().unwrap_or([0; 16]));
    offset += 16;
    let sqrt_price_reference =
        u128::from_le_bytes(bytes[offset..offset + 16].try_into().unwrap_or([0; 16]));
    offset += 16;
    let volatility_accumulator =
        u128::from_le_bytes(bytes[offset..offset + 16].try_into().unwrap_or([0; 16]));
    offset += 16;
    let volatility_reference =
        u128::from_le_bytes(bytes[offset..offset + 16].try_into().unwrap_or([0; 16]));
    offset += 16;
    offset += 16;
    let base_fee_numerator = cliff_fee_numerator;
    let base_fee_denominator = 10000; // FEE_DENOMINATOR
    let is_dynamic_fee_enabled = dynamic_initialized != 0;
    PoolFeesData {
        base_fee_numerator: base_fee_numerator as u32,
        base_fee_denominator: base_fee_denominator as u32,
        dynamic_fee: DynamicFeeData {
            is_dynamic_fee_enabled,
            bin_step: bin_step_u128,
            volatility_reference,
            volatility_accumulator,
            last_update_timestamp,
            activation_point: 0,
        },
    }
}

/// Parse pool metrics from bytes
fn parse_pool_metrics(bytes: &[u8]) -> PoolMetricsData {
    if bytes.len() < 80 {
        return PoolMetricsData {
            total_lp_a_fee: 0,
            total_lp_b_fee: 0,
            total_protocol_a_fee: 0,
            total_protocol_b_fee: 0,
            total_partner_a_fee: 0,
            total_partner_b_fee: 0,
            total_position: 0,
        };
    }
    let mut offset = 0;
    let total_lp_a_fee =
        u128::from_le_bytes(bytes[offset..offset + 16].try_into().unwrap_or([0; 16]));
    offset += 16;
    let total_lp_b_fee =
        u128::from_le_bytes(bytes[offset..offset + 16].try_into().unwrap_or([0; 16]));
    offset += 16;
    let total_protocol_a_fee =
        u64::from_le_bytes(bytes[offset..offset + 8].try_into().unwrap_or([0; 8]));
    offset += 8;
    let total_protocol_b_fee =
        u64::from_le_bytes(bytes[offset..offset + 8].try_into().unwrap_or([0; 8]));
    offset += 8;
    let total_partner_a_fee =
        u64::from_le_bytes(bytes[offset..offset + 8].try_into().unwrap_or([0; 8]));
    offset += 8;
    let total_partner_b_fee =
        u64::from_le_bytes(bytes[offset..offset + 8].try_into().unwrap_or([0; 8]));
    offset += 8;
    let total_position = u64::from_le_bytes(bytes[offset..offset + 8].try_into().unwrap_or([0; 8]));
    PoolMetricsData {
        total_lp_a_fee,
        total_lp_b_fee,
        total_protocol_a_fee,
        total_protocol_b_fee,
        total_partner_a_fee,
        total_partner_b_fee,
        total_position,
    }
}

#[cfg(test)]
mod tests {
    use crate::Meteora;
    use solana_network_client::{Mode, SolanaClient};
    use std::sync::Arc;

    #[tokio::test]
    async fn test_dammv2_pool_parsing() {
        let solana_client = SolanaClient::new(Mode::MAIN).unwrap();
        let meteora = Meteora::new(Arc::new(solana_client));
        let pool_data = meteora
            .get_liquidity_pool_dynv2("5gB4NPgFB3MHFHSeKN4sbaY6t9MB8ikCe9HyiKYid4Td")
            .await
            .unwrap();
        println!("Pool Data: {:?}", pool_data);
    }
}