Skip to main content

raydium_sdk/
launchpad.rs

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,         // 0-7: Account update epoch
15    pub auth_bump: u8,      // 8: Bump seed
16    pub status: u8,         // 9: Pool status (0: Fund, 1: Migrate, 2: Trade)
17    pub base_decimals: u8,  // 10: Base token decimals
18    pub quote_decimals: u8, // 11: Quote token decimals
19    pub migrate_type: u8,   // 12: Migrate type (0: AMM, 1: CPSWAP)
20    _padding1: [u8; 3],
21    pub supply: u64,                   // 16-23: Supply of the pool base token
22    pub total_base_sell: u64,          // 24-31: Total sell amount of the base token
23    pub virtual_base: u64,             // 32-39: Virtual base amount
24    pub virtual_quote: u64,            // 40-47: Virtual quote amount
25    pub real_base: u64,                // 48-55: Actual base token amount in the pool
26    pub real_quote: u64,               // 56-63: Actual quote token amount in the pool
27    pub total_quote_fund_raising: u64, // 64-71: Total quote fund raising
28    pub quote_protocol_fee: u64,       // 72-79: Protocol fees in quote tokens
29    pub platform_fee: u64,             // 80-87: Platform fees in quote tokens
30    pub migrate_fee: u64,              // 88-95: Migrate fee
31    // VestingSchedule (40 bytes)
32    pub total_locked_amount: u64,    // 96-103: Total locked amount
33    pub cliff_period: u64,           // 104-111: Cliff period in seconds
34    pub unlock_period: u64,          // 112-119: Unlock period in seconds
35    pub start_time: u64,             // 120-127: Start time
36    pub allocated_share_amount: u64, // 128-135: Allocated share amount
37    pub global_config: [u8; 32],     // 136-167: Global config address
38    pub platform_config: [u8; 32],   // 168-199: Platform config address
39    pub base_mint: [u8; 32],         // 200-231: Base mint address
40    pub quote_mint: [u8; 32],        // 232-263: Quote mint address
41    pub base_vault: [u8; 32],        // 264-295: Base token vault
42    pub quote_vault: [u8; 32],       // 296-327: Quote token vault
43    pub creator: [u8; 32],           // 328-359: Creator address
44    pub token_program_flag: u8,      // 360: Token program flags
45    pub amm_creator_fee_on: u8,      // 361: Creator fee on (0: QuoteToken, 1: BothToken)
46    pub padding: [u8; 59],           // 362-420: Padding
47}
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}