1use bytemuck::{Pod, Zeroable};
2use solana_sdk::pubkey::Pubkey;
3use std::convert::TryInto;
4
5pub const LAUNCHPAD_POOL_STATE_DATA_SIZE: usize = 429;
6const DISCRIMINATOR_LEN: usize = 8;
7
8unsafe impl Pod for LaunchpadPool {}
9unsafe impl Zeroable for LaunchpadPool {}
10
11#[repr(C)]
12#[derive(Debug, Copy, Clone)]
13pub struct LaunchpadPool {
14 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],
21 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], }
48
49#[derive(Debug, Clone)]
50pub struct LaunchpadPoolData {
51 pub epoch: u64,
52 pub auth_bump: u8,
53 pub status: PoolStatus,
54 pub base_decimals: u8,
55 pub quote_decimals: u8,
56 pub migrate_type: MigrateType,
57 pub supply: u64,
58 pub total_base_sell: u64,
59 pub virtual_base: u64,
60 pub virtual_quote: u64,
61 pub real_base: u64,
62 pub real_quote: u64,
63 pub total_quote_fund_raising: u64,
64 pub quote_protocol_fee: u64,
65 pub platform_fee: u64,
66 pub migrate_fee: u64,
67 pub vesting_schedule: VestingSchedule,
68 pub global_config: Pubkey,
69 pub platform_config: Pubkey,
70 pub base_mint: Pubkey,
71 pub quote_mint: Pubkey,
72 pub base_vault: Pubkey,
73 pub quote_vault: Pubkey,
74 pub creator: Pubkey,
75 pub token_program_flag: TokenProgramFlagBits,
76 pub amm_creator_fee_on: AmmCreatorFeeOn,
77}
78
79#[derive(Debug, Clone, PartialEq, Eq)]
80pub enum PoolStatus {
81 Fund,
82 Migrate,
83 Trade,
84}
85
86#[derive(Debug, Clone, PartialEq, Eq)]
87pub enum MigrateType {
88 AMM,
89 CPSWAP,
90}
91
92#[derive(Debug, Clone)]
93pub struct TokenProgramFlagBits {
94 pub base_token_program: TokenProgramFlag,
95 pub quote_token_program: TokenProgramFlag,
96}
97
98#[derive(Debug, Clone, PartialEq, Eq)]
99pub enum TokenProgramFlag {
100 SPLTokenProgram,
101 TokenProgram2022,
102}
103
104#[derive(Debug, Clone, PartialEq, Eq)]
105pub enum AmmCreatorFeeOn {
106 QuoteToken,
107 BothToken,
108}
109
110#[derive(Debug, Clone)]
111pub struct VestingSchedule {
112 pub total_locked_amount: u64,
113 pub cliff_period: u64,
114 pub unlock_period: u64,
115 pub start_time: u64,
116 pub allocated_share_amount: u64,
117}
118
119impl LaunchpadPool {
120 pub fn get_liquidity_pool_info(data: &[u8]) -> Result<LaunchpadPoolData, String> {
121 if data.len() != LAUNCHPAD_POOL_STATE_DATA_SIZE {
122 return Err(format!(
123 "Launchpad pool data size mismatch. Expected {}, got {}",
124 LAUNCHPAD_POOL_STATE_DATA_SIZE,
125 data.len()
126 ));
127 }
128 let mut offset: usize = DISCRIMINATOR_LEN;
129 let read_pubkey = |d: &[u8], o: &mut usize| -> Pubkey {
130 let pk = Pubkey::new_from_array(d[*o..*o + 32].try_into().unwrap());
131 *o += 32;
132 pk
133 };
134 let read_u8 = |d: &[u8], o: &mut usize| -> u8 {
135 let v = d[*o];
136 *o += 1;
137 v
138 };
139 let read_u64 = |d: &[u8], o: &mut usize| -> u64 {
140 let v = u64::from_le_bytes(d[*o..*o + 8].try_into().unwrap());
141 *o += 8;
142 v
143 };
144 let epoch = read_u64(data, &mut offset);
145 let auth_bump = read_u8(data, &mut offset);
146 let status_byte = read_u8(data, &mut offset);
147 let base_decimals = read_u8(data, &mut offset);
148 let quote_decimals = read_u8(data, &mut offset);
149 let migrate_type_byte = read_u8(data, &mut offset);
150 let supply = read_u64(data, &mut offset);
151 let total_base_sell = read_u64(data, &mut offset);
152 let virtual_base = read_u64(data, &mut offset);
153 let virtual_quote = read_u64(data, &mut offset);
154 let real_base = read_u64(data, &mut offset);
155 let real_quote = read_u64(data, &mut offset);
156 let total_quote_fund_raising = read_u64(data, &mut offset);
157 let quote_protocol_fee = read_u64(data, &mut offset);
158 let platform_fee = read_u64(data, &mut offset);
159 let migrate_fee = read_u64(data, &mut offset);
160 let total_locked_amount = read_u64(data, &mut offset);
161 let cliff_period = read_u64(data, &mut offset);
162 let unlock_period = read_u64(data, &mut offset);
163 let start_time = read_u64(data, &mut offset);
164 let allocated_share_amount = read_u64(data, &mut offset);
165 let global_config = read_pubkey(data, &mut offset);
166 let platform_config = read_pubkey(data, &mut offset);
167 let base_mint = read_pubkey(data, &mut offset);
168 let quote_mint = read_pubkey(data, &mut offset);
169 let base_vault = read_pubkey(data, &mut offset);
170 let quote_vault = read_pubkey(data, &mut offset);
171 let creator = read_pubkey(data, &mut offset);
172 let token_program_flag_byte = read_u8(data, &mut offset);
173 let amm_creator_fee_on_byte = read_u8(data, &mut offset);
174 let remaining_padding = LAUNCHPAD_POOL_STATE_DATA_SIZE - offset;
175 if remaining_padding > 0 {
176 offset += remaining_padding;
177 }
178 if offset != data.len() {
179 return Err(format!(
180 "Data parsing incomplete. Expected offset {}, got {}",
181 data.len(),
182 offset
183 ));
184 }
185 let status = match status_byte {
186 0 => PoolStatus::Fund,
187 1 => PoolStatus::Migrate,
188 2 => PoolStatus::Trade,
189 _ => return Err(format!("Invalid pool status: {}", status_byte)),
190 };
191 let migrate_type = match migrate_type_byte {
192 0 => MigrateType::AMM,
193 1 => MigrateType::CPSWAP,
194 _ => return Err(format!("Invalid migrate type: {}", migrate_type_byte)),
195 };
196 let base_token_program = if token_program_flag_byte & 0b1 == 0 {
197 TokenProgramFlag::SPLTokenProgram
198 } else {
199 TokenProgramFlag::TokenProgram2022
200 };
201 let quote_token_program = if token_program_flag_byte & 0b10 == 0 {
202 TokenProgramFlag::SPLTokenProgram
203 } else {
204 TokenProgramFlag::TokenProgram2022
205 };
206 let amm_creator_fee_on = match amm_creator_fee_on_byte {
207 0 => AmmCreatorFeeOn::QuoteToken,
208 1 => AmmCreatorFeeOn::BothToken,
209 _ => {
210 return Err(format!(
211 "Invalid creator fee on value: {}",
212 amm_creator_fee_on_byte
213 ));
214 }
215 };
216 Ok(LaunchpadPoolData {
217 epoch,
218 auth_bump,
219 status,
220 base_decimals,
221 quote_decimals,
222 migrate_type,
223 supply,
224 total_base_sell,
225 virtual_base,
226 virtual_quote,
227 real_base,
228 real_quote,
229 total_quote_fund_raising,
230 quote_protocol_fee,
231 platform_fee,
232 migrate_fee,
233 vesting_schedule: VestingSchedule {
234 total_locked_amount,
235 cliff_period,
236 unlock_period,
237 start_time,
238 allocated_share_amount,
239 },
240 global_config,
241 platform_config,
242 base_mint,
243 quote_mint,
244 base_vault,
245 quote_vault,
246 creator,
247 token_program_flag: TokenProgramFlagBits {
248 base_token_program,
249 quote_token_program,
250 },
251 amm_creator_fee_on,
252 })
253 }
254
255 pub fn global_config_pubkey(&self) -> Pubkey {
256 Pubkey::new_from_array(self.global_config)
257 }
258
259 pub fn platform_config_pubkey(&self) -> Pubkey {
260 Pubkey::new_from_array(self.platform_config)
261 }
262
263 pub fn base_mint_pubkey(&self) -> Pubkey {
264 Pubkey::new_from_array(self.base_mint)
265 }
266
267 pub fn quote_mint_pubkey(&self) -> Pubkey {
268 Pubkey::new_from_array(self.quote_mint)
269 }
270
271 pub fn base_vault_pubkey(&self) -> Pubkey {
272 Pubkey::new_from_array(self.base_vault)
273 }
274
275 pub fn quote_vault_pubkey(&self) -> Pubkey {
276 Pubkey::new_from_array(self.quote_vault)
277 }
278
279 pub fn creator_pubkey(&self) -> Pubkey {
280 Pubkey::new_from_array(self.creator)
281 }
282}
283
284impl LaunchpadPoolData {
285 pub fn get_price(&self) -> f64 {
286 if self.virtual_base == 0 {
287 return 0.0;
288 }
289 self.virtual_quote as f64 / self.virtual_base as f64
290 }
291
292 pub fn get_real_price(&self) -> f64 {
293 if self.real_base == 0 {
294 return 0.0;
295 }
296 self.real_quote as f64 / self.real_base as f64
297 }
298
299 pub fn get_total_value(&self) -> f64 {
300 let base_value = self.real_base as f64 * self.get_real_price();
301 base_value + self.real_quote as f64
302 }
303
304 pub fn is_funding(&self) -> bool {
305 matches!(self.status, PoolStatus::Fund)
306 }
307
308 pub fn is_tradable(&self) -> bool {
309 matches!(self.status, PoolStatus::Trade)
310 }
311
312 pub fn get_funding_progress(&self) -> f64 {
313 if self.total_quote_fund_raising == 0 {
314 return 0.0;
315 }
316 (self.real_quote as f64 / self.total_quote_fund_raising as f64) * 100.0
317 }
318
319 pub fn get_unlocked_amount(&self) -> u64 {
320 if self.vesting_schedule.total_locked_amount == 0 {
321 return 0;
322 }
323 let current_time = std::time::SystemTime::now()
324 .duration_since(std::time::UNIX_EPOCH)
325 .unwrap_or_default()
326 .as_secs();
327 if current_time < self.vesting_schedule.start_time + self.vesting_schedule.cliff_period {
328 0
329 } else if current_time
330 >= self.vesting_schedule.start_time
331 + self.vesting_schedule.cliff_period
332 + self.vesting_schedule.unlock_period
333 {
334 self.vesting_schedule.total_locked_amount
335 } else {
336 let elapsed = current_time
337 - (self.vesting_schedule.start_time + self.vesting_schedule.cliff_period);
338 let unlock_ratio = elapsed as f64 / self.vesting_schedule.unlock_period as f64;
339 (self.vesting_schedule.total_locked_amount as f64 * unlock_ratio.min(1.0)) as u64
340 }
341 }
342}
343
344#[cfg(test)]
345mod tests {
346 use solana_network_client::SolanaClient;
347
348 use crate::Raydium;
349 use std::sync::Arc;
350
351 #[tokio::test]
352 async fn test_clmm_data_parsing() -> Result<(), Box<dyn std::error::Error>> {
353 let solana_client = SolanaClient::new(solana_network_client::Mode::MAIN).unwrap();
354 let raydium = Raydium::new(Arc::new(solana_client));
355 let pool_data = raydium
356 .get_liquidity_pool_launchpad("GSxb28CtEf9vJHEoB9D2NoNwbbkj8SxQN3WN86qvMULZ")
357 .await;
358 println!("Pool Info: {:?}", pool_data);
359 Ok(())
360 }
361}