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 pub volatility_tracker: [u8; 64],
17 pub config: [u8; 32],
19 pub creator: [u8; 32],
21 pub base_mint: [u8; 32],
23 pub base_vault: [u8; 32],
25 pub quote_vault: [u8; 32],
27 pub base_reserve: [u8; 8],
29 pub quote_reserve: [u8; 8],
31 pub protocol_base_fee: [u8; 8],
33 pub protocol_quote_fee: [u8; 8],
35 pub partner_base_fee: [u8; 8],
37 pub partner_quote_fee: [u8; 8],
39 pub sqrt_price: [u8; 16],
41 pub activation_point: [u8; 8],
43 pub pool_type: u8,
45 pub is_migrated: u8,
47 pub is_partner_withdraw_surplus: u8,
49 pub is_protocol_withdraw_surplus: u8,
51 pub migration_progress: u8,
53 pub is_withdraw_leftover: u8,
55 pub is_creator_withdraw_surplus: u8,
57 pub migration_fee_withdraw_status: u8,
59 pub metrics: [u8; 32],
61 pub finish_curve_timestamp: [u8; 8],
63 pub creator_base_fee: [u8; 8],
65 pub creator_quote_fee: [u8; 8],
67 pub creation_fee_bits: u8,
69 pub _padding_0: [u8; 7],
71 pub _padding_1: [u8; 48],
73}
74
75#[derive(Debug, Clone)]
76pub struct DynamicBondingCurvePoolData {
77 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#[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#[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 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 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 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 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 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 pub fn has_creation_fee(&self) -> bool {
264 self.has_creation_fee
265 }
266
267 pub fn creation_fee_claimed(&self) -> bool {
269 self.creation_fee_claimed
270 }
271
272 pub fn eligible_to_withdraw_migration_fee(&self, mask: u8) -> bool {
274 (self.migration_fee_withdraw_status & mask) == 0
275 }
276
277 pub fn is_curve_complete(&self, migration_threshold: u64) -> bool {
279 self.quote_reserve >= migration_threshold
280 }
281
282 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 pub fn get_total_surplus(&self, migration_threshold: u64) -> Option<u64> {
291 self.quote_reserve.checked_sub(migration_threshold)
292 }
293
294 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 pub fn get_volatility_accumulator_percentage(&self) -> f64 {
303 (self.volatility_tracker.volatility_accumulator as f64) / 100.0
304 }
305
306 pub fn get_protocol_fees(&self) -> (u64, u64) {
308 (self.protocol_base_fee, self.protocol_quote_fee)
309 }
310
311 pub fn get_partner_fees(&self) -> (u64, u64) {
313 (self.partner_base_fee, self.partner_quote_fee)
314 }
315
316 pub fn get_creator_fees(&self) -> (u64, u64) {
318 (self.creator_base_fee, self.creator_quote_fee)
319 }
320
321 pub fn get_reserves(&self) -> (u64, u64) {
323 (self.base_reserve, self.quote_reserve)
324 }
325
326 pub fn is_migrated(&self) -> bool {
328 self.is_migrated
329 }
330
331 pub fn can_partner_withdraw_surplus(&self) -> bool {
333 self.is_partner_withdraw_surplus
334 }
335
336 pub fn can_protocol_withdraw_surplus(&self) -> bool {
338 self.is_protocol_withdraw_surplus
339 }
340
341 pub fn can_creator_withdraw_surplus(&self) -> bool {
343 self.is_creator_withdraw_surplus
344 }
345
346 pub fn can_withdraw_leftover(&self) -> bool {
348 self.is_withdraw_leftover
349 }
350
351 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#[derive(Debug, Clone)]
364pub struct DynamicBondingCurvePoolInfo {
365 pub virtual_pool_data: DynamicBondingCurvePoolData,
366 pub additional_info: Option<String>,
367}
368
369impl DynamicBondingCurvePoolInfo {
370 pub fn new(virtual_pool_data: DynamicBondingCurvePoolData) -> Self {
372 Self {
373 virtual_pool_data,
374 additional_info: None,
375 }
376 }
377
378 pub fn set_additional_info(&mut self, info: String) {
380 self.additional_info = Some(info);
381 }
382
383 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}