meteora_sdk/
dbc.rs

1use std::mem;
2
3use bytemuck::{Pod, Zeroable};
4use solana_sdk::pubkey::Pubkey;
5
6pub const VIRTUAL_POOL_DATA_SIZE: usize = 416;
7const DISCRIMINATOR_LEN: usize = 8;
8
9unsafe impl Pod for DynamicBondingCurvePool {}
10unsafe impl Zeroable for DynamicBondingCurvePool {}
11
12#[repr(C)]
13#[derive(Debug, Copy, Clone)]
14pub struct DynamicBondingCurvePool {
15    /// volatility tracker
16    pub volatility_tracker: [u8; 64],
17    /// config key (32 bytes)
18    pub config: [u8; 32],
19    /// creator (32 bytes)
20    pub creator: [u8; 32],
21    /// base mint (32 bytes)
22    pub base_mint: [u8; 32],
23    /// base vault (32 bytes)
24    pub base_vault: [u8; 32],
25    /// quote vault (32 bytes)
26    pub quote_vault: [u8; 32],
27    /// base reserve (8 bytes)
28    pub base_reserve: [u8; 8],
29    /// quote reserve (8 bytes)
30    pub quote_reserve: [u8; 8],
31    /// protocol base fee (8 bytes)
32    pub protocol_base_fee: [u8; 8],
33    /// protocol quote fee (8 bytes)
34    pub protocol_quote_fee: [u8; 8],
35    /// partner base fee (8 bytes)
36    pub partner_base_fee: [u8; 8],
37    /// partner quote fee (8 bytes)
38    pub partner_quote_fee: [u8; 8],
39    /// current price (16 bytes for u128)
40    pub sqrt_price: [u8; 16],
41    /// Activation point (8 bytes)
42    pub activation_point: [u8; 8],
43    /// pool type (1 byte)
44    pub pool_type: u8,
45    /// is migrated (1 byte)
46    pub is_migrated: u8,
47    /// is partner withdraw surplus (1 byte)
48    pub is_partner_withdraw_surplus: u8,
49    /// is protocol withdraw surplus (1 byte)
50    pub is_protocol_withdraw_surplus: u8,
51    /// migration progress (1 byte)
52    pub migration_progress: u8,
53    /// is withdraw leftover (1 byte)
54    pub is_withdraw_leftover: u8,
55    /// is creator withdraw surplus (1 byte)
56    pub is_creator_withdraw_surplus: u8,
57    /// migration fee withdraw status (1 byte)
58    pub migration_fee_withdraw_status: u8,
59    /// pool metrics (32 bytes for PoolMetrics)
60    pub metrics: [u8; 32],
61    /// The time curve is finished (8 bytes)
62    pub finish_curve_timestamp: [u8; 8],
63    /// creator base fee (8 bytes)
64    pub creator_base_fee: [u8; 8],
65    /// creator quote fee (8 bytes)
66    pub creator_quote_fee: [u8; 8],
67    /// creation fee bits (1 byte)
68    pub creation_fee_bits: u8,
69    /// padding (7 bytes)
70    pub _padding_0: [u8; 7],
71    /// Padding for further use (48 bytes = [u64; 6])
72    pub _padding_1: [u8; 48],
73}
74
75#[derive(Debug, Clone)]
76pub struct DynamicBondingCurvePoolData {
77    // Token addresses
78    pub config: Pubkey,
79    pub creator: Pubkey,
80    pub base_mint: Pubkey,
81    pub base_vault: Pubkey,
82    pub quote_vault: Pubkey,
83    pub base_reserve: u64,
84    pub quote_reserve: u64,
85    pub protocol_base_fee: u64,
86    pub protocol_quote_fee: u64,
87    pub partner_base_fee: u64,
88    pub partner_quote_fee: u64,
89    pub creator_base_fee: u64,
90    pub creator_quote_fee: u64,
91    pub sqrt_price: u128,
92    pub activation_point: u64,
93    pub finish_curve_timestamp: u64,
94    pub pool_type: u8,
95    pub is_migrated: bool,
96    pub is_partner_withdraw_surplus: bool,
97    pub is_protocol_withdraw_surplus: bool,
98    pub migration_progress: u8,
99    pub is_withdraw_leftover: bool,
100    pub is_creator_withdraw_surplus: bool,
101    pub migration_fee_withdraw_status: u8,
102    pub total_protocol_base_fee: u64,
103    pub total_protocol_quote_fee: u64,
104    pub total_trading_base_fee: u64,
105    pub total_trading_quote_fee: u64,
106    pub creation_fee_bits: u8,
107    pub has_creation_fee: bool,
108    pub creation_fee_claimed: bool,
109    pub volatility_tracker: VolatilityTrackerData,
110}
111
112/// Volatility tracker
113#[derive(Debug, Clone)]
114pub struct VolatilityTrackerData {
115    pub last_update_timestamp: u64,
116    pub padding: [u8; 8],
117    pub sqrt_price_reference: u128,
118    pub volatility_accumulator: u128,
119    pub volatility_reference: u128,
120}
121
122/// Pool metrics structure
123#[derive(Debug, Clone)]
124pub struct PoolMetricsData {
125    pub total_protocol_base_fee: u64,
126    pub total_protocol_quote_fee: u64,
127    pub total_trading_base_fee: u64,
128    pub total_trading_quote_fee: u64,
129}
130
131impl DynamicBondingCurvePool {
132    /// Parse Virtual Pool data from byte array
133    pub fn get_dbc_pool_info(data: &[u8]) -> Result<DynamicBondingCurvePoolData, String> {
134        let total_expected_size = DISCRIMINATOR_LEN + VIRTUAL_POOL_DATA_SIZE;
135        if data.len() != total_expected_size {
136            return Err(format!(
137                "Virtual pool data size mismatch. Expected {} ({} data + {} discriminator), got {}",
138                total_expected_size,
139                VIRTUAL_POOL_DATA_SIZE,
140                DISCRIMINATOR_LEN,
141                data.len()
142            ));
143        }
144        let pool_data = &data[DISCRIMINATOR_LEN..];
145        let struct_size = mem::size_of::<DynamicBondingCurvePool>();
146        if struct_size != VIRTUAL_POOL_DATA_SIZE {
147            return Err(format!(
148                "Struct size mismatch. DynamicBondingCurvePool  is {} bytes, but VIRTUAL_POOL_DATA_SIZE is {}",
149                struct_size, VIRTUAL_POOL_DATA_SIZE
150            ));
151        }
152        let pool: &DynamicBondingCurvePool = bytemuck::try_from_bytes(pool_data)
153            .map_err(|e| format!("Failed to cast bytes to DynamicBondingCurvePool : {}", e))?;
154        Self::parse_from_struct(pool)
155    }
156
157    fn parse_from_struct(
158        pool: &DynamicBondingCurvePool,
159    ) -> Result<DynamicBondingCurvePoolData, String> {
160        // volatility tracker (64 bytes)
161        let volatility_tracker = VolatilityTrackerData {
162            last_update_timestamp: u64::from_le_bytes(
163                pool.volatility_tracker[0..8].try_into().unwrap(),
164            ),
165            padding: pool.volatility_tracker[8..16].try_into().unwrap(),
166            sqrt_price_reference: u128::from_le_bytes(
167                pool.volatility_tracker[16..32].try_into().unwrap(),
168            ),
169            volatility_accumulator: u128::from_le_bytes(
170                pool.volatility_tracker[32..48].try_into().unwrap(),
171            ),
172            volatility_reference: u128::from_le_bytes(
173                pool.volatility_tracker[48..64].try_into().unwrap(),
174            ),
175        };
176        let total_protocol_base_fee = u64::from_le_bytes(pool.metrics[0..8].try_into().unwrap());
177        let total_protocol_quote_fee = u64::from_le_bytes(pool.metrics[8..16].try_into().unwrap());
178        let total_trading_base_fee = u64::from_le_bytes(pool.metrics[16..24].try_into().unwrap());
179        let total_trading_quote_fee = u64::from_le_bytes(pool.metrics[24..32].try_into().unwrap());
180        let creation_fee_bits = pool.creation_fee_bits;
181        let has_creation_fee = (creation_fee_bits & 0b01) != 0;
182        let creation_fee_claimed = (creation_fee_bits & 0b10) != 0;
183        Ok(DynamicBondingCurvePoolData {
184            config: Pubkey::new_from_array(pool.config),
185            creator: Pubkey::new_from_array(pool.creator),
186            base_mint: Pubkey::new_from_array(pool.base_mint),
187            base_vault: Pubkey::new_from_array(pool.base_vault),
188            quote_vault: Pubkey::new_from_array(pool.quote_vault),
189            base_reserve: u64::from_le_bytes(pool.base_reserve),
190            quote_reserve: u64::from_le_bytes(pool.quote_reserve),
191            protocol_base_fee: u64::from_le_bytes(pool.protocol_base_fee),
192            protocol_quote_fee: u64::from_le_bytes(pool.protocol_quote_fee),
193            partner_base_fee: u64::from_le_bytes(pool.partner_base_fee),
194            partner_quote_fee: u64::from_le_bytes(pool.partner_quote_fee),
195            creator_base_fee: u64::from_le_bytes(pool.creator_base_fee),
196            creator_quote_fee: u64::from_le_bytes(pool.creator_quote_fee),
197            sqrt_price: u128::from_le_bytes(pool.sqrt_price),
198            activation_point: u64::from_le_bytes(pool.activation_point),
199            finish_curve_timestamp: u64::from_le_bytes(pool.finish_curve_timestamp),
200            pool_type: pool.pool_type,
201            is_migrated: pool.is_migrated != 0,
202            is_partner_withdraw_surplus: pool.is_partner_withdraw_surplus != 0,
203            is_protocol_withdraw_surplus: pool.is_protocol_withdraw_surplus != 0,
204            migration_progress: pool.migration_progress,
205            is_withdraw_leftover: pool.is_withdraw_leftover != 0,
206            is_creator_withdraw_surplus: pool.is_creator_withdraw_surplus != 0,
207            migration_fee_withdraw_status: pool.migration_fee_withdraw_status,
208            total_protocol_base_fee,
209            total_protocol_quote_fee,
210            total_trading_base_fee,
211            total_trading_quote_fee,
212            creation_fee_bits,
213            has_creation_fee,
214            creation_fee_claimed,
215            volatility_tracker,
216        })
217    }
218
219    // Getter methods for public keys
220    pub fn config_pubkey(&self) -> Pubkey {
221        Pubkey::new_from_array(self.config)
222    }
223
224    pub fn creator_pubkey(&self) -> Pubkey {
225        Pubkey::new_from_array(self.creator)
226    }
227
228    pub fn base_mint_pubkey(&self) -> Pubkey {
229        Pubkey::new_from_array(self.base_mint)
230    }
231
232    pub fn base_vault_pubkey(&self) -> Pubkey {
233        Pubkey::new_from_array(self.base_vault)
234    }
235
236    pub fn quote_vault_pubkey(&self) -> Pubkey {
237        Pubkey::new_from_array(self.quote_vault)
238    }
239}
240
241impl DynamicBondingCurvePoolData {
242    /// Get pool type as string
243    pub fn get_pool_type_str(&self) -> &'static str {
244        match self.pool_type {
245            0 => "SplToken",
246            1 => "Token2022",
247            _ => "Unknown",
248        }
249    }
250
251    /// Get migration progress as string
252    pub fn get_migration_progress_str(&self) -> &'static str {
253        match self.migration_progress {
254            0 => "PreBondingCurve",
255            1 => "PostBondingCurve",
256            2 => "LockedVesting",
257            3 => "CreatedPool",
258            _ => "Unknown",
259        }
260    }
261
262    /// Get creation fee status
263    pub fn has_creation_fee(&self) -> bool {
264        self.has_creation_fee
265    }
266
267    /// Get creation fee claimed status
268    pub fn creation_fee_claimed(&self) -> bool {
269        self.creation_fee_claimed
270    }
271
272    /// Get migration fee withdraw eligibility
273    pub fn eligible_to_withdraw_migration_fee(&self, mask: u8) -> bool {
274        (self.migration_fee_withdraw_status & mask) == 0
275    }
276
277    /// Check if curve is complete
278    pub fn is_curve_complete(&self, migration_threshold: u64) -> bool {
279        self.quote_reserve >= migration_threshold
280    }
281
282    /// Get protocol and trading base fee total
283    pub fn get_protocol_and_trading_base_fee(&self) -> u64 {
284        self.partner_base_fee
285            .saturating_add(self.protocol_base_fee)
286            .saturating_add(self.creator_base_fee)
287    }
288
289    /// Get total surplus
290    pub fn get_total_surplus(&self, migration_threshold: u64) -> Option<u64> {
291        self.quote_reserve.checked_sub(migration_threshold)
292    }
293
294    /// Get price from sqrt price
295    pub fn get_price(&self) -> f64 {
296        let sqrt_price = self.sqrt_price as f64;
297        let price = (sqrt_price * sqrt_price) / (2.0f64.powi(64));
298        price
299    }
300
301    /// Get volatility accumulator as percentage
302    pub fn get_volatility_accumulator_percentage(&self) -> f64 {
303        (self.volatility_tracker.volatility_accumulator as f64) / 100.0
304    }
305
306    /// Get protocol fees
307    pub fn get_protocol_fees(&self) -> (u64, u64) {
308        (self.protocol_base_fee, self.protocol_quote_fee)
309    }
310
311    /// Get partner fees
312    pub fn get_partner_fees(&self) -> (u64, u64) {
313        (self.partner_base_fee, self.partner_quote_fee)
314    }
315
316    /// Get creator fees
317    pub fn get_creator_fees(&self) -> (u64, u64) {
318        (self.creator_base_fee, self.creator_quote_fee)
319    }
320
321    /// Get all reserves
322    pub fn get_reserves(&self) -> (u64, u64) {
323        (self.base_reserve, self.quote_reserve)
324    }
325
326    /// Check if pool is migrated
327    pub fn is_migrated(&self) -> bool {
328        self.is_migrated
329    }
330
331    /// Check if partner can withdraw surplus
332    pub fn can_partner_withdraw_surplus(&self) -> bool {
333        self.is_partner_withdraw_surplus
334    }
335
336    /// Check if protocol can withdraw surplus
337    pub fn can_protocol_withdraw_surplus(&self) -> bool {
338        self.is_protocol_withdraw_surplus
339    }
340
341    /// Check if creator can withdraw surplus
342    pub fn can_creator_withdraw_surplus(&self) -> bool {
343        self.is_creator_withdraw_surplus
344    }
345
346    /// Check if leftover can be withdrawn
347    pub fn can_withdraw_leftover(&self) -> bool {
348        self.is_withdraw_leftover
349    }
350
351    /// Get pool metrics
352    pub fn get_metrics(&self) -> PoolMetricsData {
353        PoolMetricsData {
354            total_protocol_base_fee: self.total_protocol_base_fee,
355            total_protocol_quote_fee: self.total_protocol_quote_fee,
356            total_trading_base_fee: self.total_trading_base_fee,
357            total_trading_quote_fee: self.total_trading_quote_fee,
358        }
359    }
360}
361
362/// Dynamic Bonding Curve Info structure
363#[derive(Debug, Clone)]
364pub struct DynamicBondingCurvePoolInfo {
365    pub virtual_pool_data: DynamicBondingCurvePoolData,
366    pub additional_info: Option<String>,
367}
368
369impl DynamicBondingCurvePoolInfo {
370    /// Create new Dynamic Bonding Curve Info
371    pub fn new(virtual_pool_data: DynamicBondingCurvePoolData) -> Self {
372        Self {
373            virtual_pool_data,
374            additional_info: None,
375        }
376    }
377
378    /// Set additional info
379    pub fn set_additional_info(&mut self, info: String) {
380        self.additional_info = Some(info);
381    }
382
383    /// Get summary of the bonding curve
384    pub fn get_summary(&self) -> String {
385        format!(
386            "Virtual Pool: {} -> {}, Reserves: {} base / {} quote, Price: {:.6}, Status: {}",
387            self.virtual_pool_data.base_mint,
388            self.virtual_pool_data.quote_vault,
389            self.virtual_pool_data.base_reserve,
390            self.virtual_pool_data.quote_reserve,
391            self.virtual_pool_data.get_price(),
392            self.virtual_pool_data.get_migration_progress_str()
393        )
394    }
395}
396
397#[cfg(test)]
398mod tests {
399    use crate::Meteora;
400    use solana_network_client::{Mode, SolanaClient};
401    use std::sync::Arc;
402
403    #[tokio::test]
404    async fn test_dbc_pool() {
405        let solana_client = SolanaClient::new(Mode::MAIN).unwrap();
406        let meteora = Meteora::new(Arc::new(solana_client));
407        let pool_data = meteora
408            .get_liquidity_pool_dbc("FDT74DRFm6d2zig9ZT1ABT3NJMPooWXs1tCMvNzvd2jV")
409            .await
410            .unwrap();
411        println!("Pool Data: {:?}", pool_data);
412    }
413}