use bytemuck::{Pod, Zeroable};
use solana_sdk::pubkey::Pubkey;
pub const DAMM_LIQUIDITY_POOL_DATA_SIZE: usize = 944; const DISCRIMINATOR_LEN: usize = 8;
unsafe impl Pod for DAMMLiquidityPool {}
unsafe impl Zeroable for DAMMLiquidityPool {}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct DAMMLiquidityPool {
pub lp_mint: [u8; 32],
pub token_a_mint: [u8; 32],
pub token_b_mint: [u8; 32],
pub a_vault: [u8; 32],
pub b_vault: [u8; 32],
pub a_vault_lp: [u8; 32],
pub b_vault_lp: [u8; 32],
pub a_vault_lp_bump: u8,
pub enabled: u8,
pub protocol_token_a_fee: [u8; 32],
pub protocol_token_b_fee: [u8; 32],
pub fee_last_updated_at: [u8; 8],
pub _padding0: [u8; 24],
pub fees: [u8; 48],
pub pool_type: u8,
pub stake: [u8; 32],
pub total_locked_lp: [u8; 8],
pub bootstrapping: [u8; 48],
pub partner_info: [u8; 56],
pub padding: [u8; 429],
pub curve_type: [u8; 8],
}
#[derive(Debug, Clone)]
pub struct DAMMLiquidityPoolData {
pub lp_mint: Pubkey,
pub token_a_mint: Pubkey,
pub token_b_mint: Pubkey,
pub a_vault: Pubkey,
pub b_vault: Pubkey,
pub a_vault_lp: Pubkey,
pub b_vault_lp: Pubkey,
pub protocol_token_a_fee: Pubkey,
pub protocol_token_b_fee: Pubkey,
pub stake: Pubkey,
pub a_vault_lp_bump: u8,
pub enabled: bool,
pub fee_last_updated_at: u64,
pub pool_type: PoolType,
pub total_locked_lp: u64,
pub fees: DynPoolFees,
pub bootstrapping: BootstrappingInfo,
pub partner_info: PartnerInfo,
pub curve_type: CurveTypeInfo,
}
#[derive(Debug, Clone, PartialEq)]
pub enum PoolType {
Permissioned,
Permissionless,
Unknown(u8),
}
#[derive(Debug, Clone)]
pub struct DynPoolFees {
pub trade_fee_numerator: u64,
pub trade_fee_denominator: u64,
pub protocol_trade_fee_numerator: u64,
pub protocol_trade_fee_denominator: u64,
}
#[derive(Debug, Clone)]
pub struct BootstrappingInfo {
pub activation_point: u64,
pub whitelisted_vault: Pubkey,
pub pool_creator: Pubkey,
pub activation_type: ActivationType,
}
#[derive(Debug, Clone, PartialEq)]
pub enum ActivationType {
Slot,
Timestamp,
Unknown(u8),
}
#[derive(Debug, Clone)]
pub struct PartnerInfo {
pub fee_numerator: u64,
pub partner_authority: Pubkey,
pub pending_fee_a: u64,
pub pending_fee_b: u64,
}
#[derive(Debug, Clone)]
pub enum CurveTypeInfo {
ConstantProduct,
Stable {
amp: u64,
token_multiplier: TokenMultiplier,
depeg: DepegInfo,
last_amp_updated_timestamp: u64,
},
Unknown,
}
#[derive(Debug, Clone)]
pub struct TokenMultiplier {
pub token_a_multiplier: u64,
pub token_b_multiplier: u64,
pub precision_factor: u8,
}
#[derive(Debug, Clone)]
pub struct DepegInfo {
pub base_virtual_price: u64,
pub base_cache_updated: u64,
pub depeg_type: DepegType,
}
#[derive(Debug, Clone, PartialEq)]
pub enum DepegType {
None,
Marinade,
Lido,
SplStake,
Unknown,
}
impl DAMMLiquidityPool {
pub fn get_liquidity_pool_info(data: &[u8]) -> Result<DAMMLiquidityPoolData, String> {
let total_expected_size = DISCRIMINATOR_LEN + DAMM_LIQUIDITY_POOL_DATA_SIZE;
if data.len() != total_expected_size {
return Err(format!(
"V1 pool data size mismatch. Expected {} (944 data + 8 discriminator), got {}",
total_expected_size,
data.len()
));
}
let mut offset: usize = DISCRIMINATOR_LEN;
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_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 lp_mint = read_pubkey(data, &mut offset)?;
let token_a_mint = read_pubkey(data, &mut offset)?;
let token_b_mint = read_pubkey(data, &mut offset)?;
let a_vault = read_pubkey(data, &mut offset)?;
let b_vault = read_pubkey(data, &mut offset)?;
let a_vault_lp = read_pubkey(data, &mut offset)?;
let b_vault_lp = read_pubkey(data, &mut offset)?;
let a_vault_lp_bump = read_u8(data, &mut offset)?;
let enabled_byte = read_u8(data, &mut offset)?;
let enabled = enabled_byte != 0;
let protocol_token_a_fee = read_pubkey(data, &mut offset)?;
let protocol_token_b_fee = read_pubkey(data, &mut offset)?;
let fee_last_updated_at = read_u64(data, &mut offset)?;
offset += 24;
let trade_fee_numerator = read_u64(data, &mut offset)?;
let trade_fee_denominator = read_u64(data, &mut offset)?;
let protocol_trade_fee_numerator = read_u64(data, &mut offset)?;
let protocol_trade_fee_denominator = read_u64(data, &mut offset)?;
let fees = DynPoolFees {
trade_fee_numerator,
trade_fee_denominator,
protocol_trade_fee_numerator,
protocol_trade_fee_denominator,
};
let pool_type_byte = read_u8(data, &mut offset)?;
let pool_type = match pool_type_byte {
0 => PoolType::Permissioned,
1 => PoolType::Permissionless,
_ => PoolType::Unknown(pool_type_byte),
};
let stake = read_pubkey(data, &mut offset)?;
let total_locked_lp = read_u64(data, &mut offset)?;
let activation_point = read_u64(data, &mut offset)?;
let whitelisted_vault = read_pubkey(data, &mut offset)?;
let pool_creator = read_pubkey(data, &mut offset)?;
let activation_type_byte = read_u8(data, &mut offset)?;
let activation_type = match activation_type_byte {
0 => ActivationType::Slot,
1 => ActivationType::Timestamp,
_ => ActivationType::Unknown(activation_type_byte),
};
let bootstrapping = BootstrappingInfo {
activation_point,
whitelisted_vault,
pool_creator,
activation_type,
};
let fee_numerator = read_u64(data, &mut offset)?;
let partner_authority = read_pubkey(data, &mut offset)?;
let pending_fee_a = read_u64(data, &mut offset)?;
let pending_fee_b = read_u64(data, &mut offset)?;
let partner_info = PartnerInfo {
fee_numerator,
partner_authority,
pending_fee_a,
pending_fee_b,
};
offset += 429;
let curve_type = Self::parse_curve_type_from_stream(data, &mut offset)?;
Ok(DAMMLiquidityPoolData {
lp_mint,
token_a_mint,
token_b_mint,
a_vault,
b_vault,
a_vault_lp,
b_vault_lp,
protocol_token_a_fee,
protocol_token_b_fee,
stake,
a_vault_lp_bump,
enabled,
fee_last_updated_at,
pool_type,
total_locked_lp,
fees,
bootstrapping,
partner_info,
curve_type,
})
}
fn parse_curve_type_from_stream(
data: &[u8],
offset: &mut usize,
) -> Result<CurveTypeInfo, String> {
if *offset >= data.len() {
return Ok(CurveTypeInfo::Unknown);
}
let curve_type_byte = data[*offset];
*offset += 1;
match curve_type_byte {
0 => Ok(CurveTypeInfo::ConstantProduct),
1 => {
if *offset + 49 > data.len() {
return Ok(CurveTypeInfo::Unknown);
}
let amp = u64::from_le_bytes(data[*offset..*offset + 8].try_into().unwrap());
*offset += 8;
let token_a_multiplier =
u64::from_le_bytes(data[*offset..*offset + 8].try_into().unwrap());
*offset += 8;
let token_b_multiplier =
u64::from_le_bytes(data[*offset..*offset + 8].try_into().unwrap());
*offset += 8;
let precision_factor = data[*offset];
*offset += 1;
let base_virtual_price =
u64::from_le_bytes(data[*offset..*offset + 8].try_into().unwrap());
*offset += 8;
let base_cache_updated =
u64::from_le_bytes(data[*offset..*offset + 8].try_into().unwrap());
*offset += 8;
let depeg_type_byte = data[*offset];
*offset += 1;
let depeg_type = match depeg_type_byte {
0 => DepegType::None,
1 => DepegType::Marinade,
2 => DepegType::Lido,
3 => DepegType::SplStake,
_ => DepegType::Unknown,
};
let last_amp_updated_timestamp =
u64::from_le_bytes(data[*offset..*offset + 8].try_into().unwrap());
*offset += 8;
Ok(CurveTypeInfo::Stable {
amp,
token_multiplier: TokenMultiplier {
token_a_multiplier,
token_b_multiplier,
precision_factor,
},
depeg: DepegInfo {
base_virtual_price,
base_cache_updated,
depeg_type,
},
last_amp_updated_timestamp,
})
}
_ => Ok(CurveTypeInfo::Unknown),
}
}
pub fn lp_mint_pubkey(&self) -> Pubkey {
Pubkey::new_from_array(self.lp_mint)
}
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 a_vault_pubkey(&self) -> Pubkey {
Pubkey::new_from_array(self.a_vault)
}
pub fn b_vault_pubkey(&self) -> Pubkey {
Pubkey::new_from_array(self.b_vault)
}
pub fn a_vault_lp_pubkey(&self) -> Pubkey {
Pubkey::new_from_array(self.a_vault_lp)
}
pub fn b_vault_lp_pubkey(&self) -> Pubkey {
Pubkey::new_from_array(self.b_vault_lp)
}
pub fn protocol_token_a_fee_pubkey(&self) -> Pubkey {
Pubkey::new_from_array(self.protocol_token_a_fee)
}
pub fn protocol_token_b_fee_pubkey(&self) -> Pubkey {
Pubkey::new_from_array(self.protocol_token_b_fee)
}
pub fn stake_pubkey(&self) -> Pubkey {
Pubkey::new_from_array(self.stake)
}
}
impl DAMMLiquidityPoolData {
pub fn is_enabled(&self) -> bool {
self.enabled
}
pub fn get_pool_type_str(&self) -> &'static str {
match self.pool_type {
PoolType::Permissioned => "Permissioned",
PoolType::Permissionless => "Permissionless",
PoolType::Unknown(_) => "Unknown",
}
}
pub fn get_activation_type_str(&self) -> &'static str {
match self.bootstrapping.activation_type {
ActivationType::Slot => "Slot",
ActivationType::Timestamp => "Timestamp",
ActivationType::Unknown(_) => "Unknown",
}
}
pub fn get_depeg_type_str(&self) -> &'static str {
if let CurveTypeInfo::Stable { depeg, .. } = &self.curve_type {
match depeg.depeg_type {
DepegType::None => "None",
DepegType::Marinade => "Marinade",
DepegType::Lido => "Lido",
DepegType::SplStake => "SplStake",
DepegType::Unknown => "Unknown",
}
} else {
"Not Applicable"
}
}
pub fn get_curve_type_str(&self) -> &'static str {
match self.curve_type {
CurveTypeInfo::ConstantProduct => "ConstantProduct",
CurveTypeInfo::Stable { .. } => "Stable",
CurveTypeInfo::Unknown => "Unknown",
}
}
pub fn calculate_trading_fee(&self, trading_tokens: u128) -> Option<u128> {
self.fees.trading_fee(trading_tokens)
}
pub fn calculate_protocol_trading_fee(&self, trading_tokens: u128) -> Option<u128> {
self.fees.protocol_trading_fee(trading_tokens)
}
}
impl DynPoolFees {
pub fn trading_fee(&self, trading_tokens: u128) -> Option<u128> {
if self.trade_fee_numerator == 0 || trading_tokens == 0 {
Some(0)
} else {
let fee = trading_tokens
.checked_mul(self.trade_fee_numerator as u128)?
.checked_div(self.trade_fee_denominator as u128)?;
if fee == 0 {
Some(1) } else {
Some(fee)
}
}
}
pub fn protocol_trading_fee(&self, trading_tokens: u128) -> Option<u128> {
if self.protocol_trade_fee_numerator == 0 || trading_tokens == 0 {
Some(0)
} else {
let fee = trading_tokens
.checked_mul(self.protocol_trade_fee_numerator as u128)?
.checked_div(self.protocol_trade_fee_denominator as u128)?;
if fee == 0 {
Some(1) } else {
Some(fee)
}
}
}
}
#[cfg(test)]
mod tests {
use crate::Meteora;
use solana_network_client::{Mode, SolanaClient};
use std::sync::Arc;
#[tokio::test]
async fn test_damm_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_dyn("DqAfrGV2GBxpGRsq6Xk1z9ojRncqgLeeVPaKg5bCc24Z")
.await
.unwrap();
println!("Pool Data: {:?}", pool_data);
}
}