use bytemuck::{Pod, Zeroable};
use solana_sdk::pubkey::Pubkey;
use std::convert::TryInto;
pub const LAUNCHPAD_POOL_STATE_DATA_SIZE: usize = 429;
const DISCRIMINATOR_LEN: usize = 8;
unsafe impl Pod for LaunchpadPool {}
unsafe impl Zeroable for LaunchpadPool {}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct LaunchpadPool {
pub epoch: u64, pub auth_bump: u8, pub status: u8, pub base_decimals: u8, pub quote_decimals: u8, pub migrate_type: u8, _padding1: [u8; 3],
pub supply: u64, pub total_base_sell: u64, pub virtual_base: u64, pub virtual_quote: u64, pub real_base: u64, pub real_quote: u64, pub total_quote_fund_raising: u64, pub quote_protocol_fee: u64, pub platform_fee: u64, pub migrate_fee: u64, pub total_locked_amount: u64, pub cliff_period: u64, pub unlock_period: u64, pub start_time: u64, pub allocated_share_amount: u64, pub global_config: [u8; 32], pub platform_config: [u8; 32], pub base_mint: [u8; 32], pub quote_mint: [u8; 32], pub base_vault: [u8; 32], pub quote_vault: [u8; 32], pub creator: [u8; 32], pub token_program_flag: u8, pub amm_creator_fee_on: u8, pub padding: [u8; 59], }
#[derive(Debug, Clone)]
pub struct LaunchpadPoolData {
pub epoch: u64,
pub auth_bump: u8,
pub status: PoolStatus,
pub base_decimals: u8,
pub quote_decimals: u8,
pub migrate_type: MigrateType,
pub supply: u64,
pub total_base_sell: u64,
pub virtual_base: u64,
pub virtual_quote: u64,
pub real_base: u64,
pub real_quote: u64,
pub total_quote_fund_raising: u64,
pub quote_protocol_fee: u64,
pub platform_fee: u64,
pub migrate_fee: u64,
pub vesting_schedule: VestingSchedule,
pub global_config: Pubkey,
pub platform_config: Pubkey,
pub base_mint: Pubkey,
pub quote_mint: Pubkey,
pub base_vault: Pubkey,
pub quote_vault: Pubkey,
pub creator: Pubkey,
pub token_program_flag: TokenProgramFlagBits,
pub amm_creator_fee_on: AmmCreatorFeeOn,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PoolStatus {
Fund,
Migrate,
Trade,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MigrateType {
AMM,
CPSWAP,
}
#[derive(Debug, Clone)]
pub struct TokenProgramFlagBits {
pub base_token_program: TokenProgramFlag,
pub quote_token_program: TokenProgramFlag,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TokenProgramFlag {
SPLTokenProgram,
TokenProgram2022,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AmmCreatorFeeOn {
QuoteToken,
BothToken,
}
#[derive(Debug, Clone)]
pub struct VestingSchedule {
pub total_locked_amount: u64,
pub cliff_period: u64,
pub unlock_period: u64,
pub start_time: u64,
pub allocated_share_amount: u64,
}
impl LaunchpadPool {
pub fn get_liquidity_pool_info(data: &[u8]) -> Result<LaunchpadPoolData, String> {
if data.len() != LAUNCHPAD_POOL_STATE_DATA_SIZE {
return Err(format!(
"Launchpad pool data size mismatch. Expected {}, got {}",
LAUNCHPAD_POOL_STATE_DATA_SIZE,
data.len()
));
}
let mut offset: usize = DISCRIMINATOR_LEN;
let read_pubkey = |d: &[u8], o: &mut usize| -> Pubkey {
let pk = Pubkey::new_from_array(d[*o..*o + 32].try_into().unwrap());
*o += 32;
pk
};
let read_u8 = |d: &[u8], o: &mut usize| -> u8 {
let v = d[*o];
*o += 1;
v
};
let read_u64 = |d: &[u8], o: &mut usize| -> u64 {
let v = u64::from_le_bytes(d[*o..*o + 8].try_into().unwrap());
*o += 8;
v
};
let epoch = read_u64(data, &mut offset);
let auth_bump = read_u8(data, &mut offset);
let status_byte = read_u8(data, &mut offset);
let base_decimals = read_u8(data, &mut offset);
let quote_decimals = read_u8(data, &mut offset);
let migrate_type_byte = read_u8(data, &mut offset);
let supply = read_u64(data, &mut offset);
let total_base_sell = read_u64(data, &mut offset);
let virtual_base = read_u64(data, &mut offset);
let virtual_quote = read_u64(data, &mut offset);
let real_base = read_u64(data, &mut offset);
let real_quote = read_u64(data, &mut offset);
let total_quote_fund_raising = read_u64(data, &mut offset);
let quote_protocol_fee = read_u64(data, &mut offset);
let platform_fee = read_u64(data, &mut offset);
let migrate_fee = read_u64(data, &mut offset);
let total_locked_amount = read_u64(data, &mut offset);
let cliff_period = read_u64(data, &mut offset);
let unlock_period = read_u64(data, &mut offset);
let start_time = read_u64(data, &mut offset);
let allocated_share_amount = read_u64(data, &mut offset);
let global_config = read_pubkey(data, &mut offset);
let platform_config = read_pubkey(data, &mut offset);
let base_mint = read_pubkey(data, &mut offset);
let quote_mint = read_pubkey(data, &mut offset);
let base_vault = read_pubkey(data, &mut offset);
let quote_vault = read_pubkey(data, &mut offset);
let creator = read_pubkey(data, &mut offset);
let token_program_flag_byte = read_u8(data, &mut offset);
let amm_creator_fee_on_byte = read_u8(data, &mut offset);
let remaining_padding = LAUNCHPAD_POOL_STATE_DATA_SIZE - offset;
if remaining_padding > 0 {
offset += remaining_padding;
}
if offset != data.len() {
return Err(format!(
"Data parsing incomplete. Expected offset {}, got {}",
data.len(),
offset
));
}
let status = match status_byte {
0 => PoolStatus::Fund,
1 => PoolStatus::Migrate,
2 => PoolStatus::Trade,
_ => return Err(format!("Invalid pool status: {}", status_byte)),
};
let migrate_type = match migrate_type_byte {
0 => MigrateType::AMM,
1 => MigrateType::CPSWAP,
_ => return Err(format!("Invalid migrate type: {}", migrate_type_byte)),
};
let base_token_program = if token_program_flag_byte & 0b1 == 0 {
TokenProgramFlag::SPLTokenProgram
} else {
TokenProgramFlag::TokenProgram2022
};
let quote_token_program = if token_program_flag_byte & 0b10 == 0 {
TokenProgramFlag::SPLTokenProgram
} else {
TokenProgramFlag::TokenProgram2022
};
let amm_creator_fee_on = match amm_creator_fee_on_byte {
0 => AmmCreatorFeeOn::QuoteToken,
1 => AmmCreatorFeeOn::BothToken,
_ => {
return Err(format!(
"Invalid creator fee on value: {}",
amm_creator_fee_on_byte
));
}
};
Ok(LaunchpadPoolData {
epoch,
auth_bump,
status,
base_decimals,
quote_decimals,
migrate_type,
supply,
total_base_sell,
virtual_base,
virtual_quote,
real_base,
real_quote,
total_quote_fund_raising,
quote_protocol_fee,
platform_fee,
migrate_fee,
vesting_schedule: VestingSchedule {
total_locked_amount,
cliff_period,
unlock_period,
start_time,
allocated_share_amount,
},
global_config,
platform_config,
base_mint,
quote_mint,
base_vault,
quote_vault,
creator,
token_program_flag: TokenProgramFlagBits {
base_token_program,
quote_token_program,
},
amm_creator_fee_on,
})
}
pub fn global_config_pubkey(&self) -> Pubkey {
Pubkey::new_from_array(self.global_config)
}
pub fn platform_config_pubkey(&self) -> Pubkey {
Pubkey::new_from_array(self.platform_config)
}
pub fn base_mint_pubkey(&self) -> Pubkey {
Pubkey::new_from_array(self.base_mint)
}
pub fn quote_mint_pubkey(&self) -> Pubkey {
Pubkey::new_from_array(self.quote_mint)
}
pub fn base_vault_pubkey(&self) -> Pubkey {
Pubkey::new_from_array(self.base_vault)
}
pub fn quote_vault_pubkey(&self) -> Pubkey {
Pubkey::new_from_array(self.quote_vault)
}
pub fn creator_pubkey(&self) -> Pubkey {
Pubkey::new_from_array(self.creator)
}
}
impl LaunchpadPoolData {
pub fn get_price(&self) -> f64 {
if self.virtual_base == 0 {
return 0.0;
}
self.virtual_quote as f64 / self.virtual_base as f64
}
pub fn get_real_price(&self) -> f64 {
if self.real_base == 0 {
return 0.0;
}
self.real_quote as f64 / self.real_base as f64
}
pub fn get_total_value(&self) -> f64 {
let base_value = self.real_base as f64 * self.get_real_price();
base_value + self.real_quote as f64
}
pub fn is_funding(&self) -> bool {
matches!(self.status, PoolStatus::Fund)
}
pub fn is_tradable(&self) -> bool {
matches!(self.status, PoolStatus::Trade)
}
pub fn get_funding_progress(&self) -> f64 {
if self.total_quote_fund_raising == 0 {
return 0.0;
}
(self.real_quote as f64 / self.total_quote_fund_raising as f64) * 100.0
}
pub fn get_unlocked_amount(&self) -> u64 {
if self.vesting_schedule.total_locked_amount == 0 {
return 0;
}
let current_time = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
if current_time < self.vesting_schedule.start_time + self.vesting_schedule.cliff_period {
0
} else if current_time
>= self.vesting_schedule.start_time
+ self.vesting_schedule.cliff_period
+ self.vesting_schedule.unlock_period
{
self.vesting_schedule.total_locked_amount
} else {
let elapsed = current_time
- (self.vesting_schedule.start_time + self.vesting_schedule.cliff_period);
let unlock_ratio = elapsed as f64 / self.vesting_schedule.unlock_period as f64;
(self.vesting_schedule.total_locked_amount as f64 * unlock_ratio.min(1.0)) as u64
}
}
}
#[cfg(test)]
mod tests {
use crate::Raydium;
use solana_network_sdk::Solana;
use solana_network_sdk::types::Mode::MAIN;
use std::sync::Arc;
#[tokio::test]
async fn test_clmm_data_parsing() -> Result<(), Box<dyn std::error::Error>> {
let solana = Solana::new(MAIN).unwrap();
let raydium = Raydium::new(Arc::new(solana));
let pool_data = raydium
.get_liquidity_pool_launchpad("GSxb28CtEf9vJHEoB9D2NoNwbbkj8SxQN3WN86qvMULZ")
.await;
println!("Pool Info: {:?}", pool_data);
Ok(())
}
}