1use super::utils::*;
7use crate::core::events::*;
8use solana_sdk::{pubkey::Pubkey, signature::Signature};
9
10pub mod discriminators {
12 pub const CLAIM_VESTED: [u8; 8] = [21, 194, 114, 87, 120, 211, 226, 32];
13 pub const CREATE_VESTING: [u8; 8] = [150, 152, 11, 179, 52, 210, 191, 125];
14 pub const POOL_CREATE: [u8; 8] = [151, 215, 226, 9, 118, 161, 115, 174];
15 pub const TRADE: [u8; 8] = [189, 219, 127, 211, 78, 230, 97, 238];
16}
17
18pub const PROGRAM_ID: &str = "LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj";
20
21pub fn is_raydium_launchlab_log(log: &str) -> bool {
23 log.contains(&format!("Program {} invoke", PROGRAM_ID))
24 || log.contains(&format!("Program {} success", PROGRAM_ID))
25}
26
27pub fn parse_log(
29 log: &str,
30 signature: Signature,
31 slot: u64,
32 tx_index: u64,
33 block_time_us: Option<i64>,
34 grpc_recv_us: i64,
35) -> Option<DexEvent> {
36 let program_data = extract_program_data(log)?;
37 if program_data.len() < 8 {
38 return None;
39 }
40
41 let discriminator: [u8; 8] = program_data[0..8].try_into().ok()?;
42 let data = &program_data[8..];
43 let metadata = EventMetadata {
44 signature,
45 slot,
46 tx_index,
47 block_time_us: block_time_us.unwrap_or(0),
48 grpc_recv_us,
49 recent_blockhash: None,
50 };
51
52 match discriminator {
53 discriminators::TRADE => parse_trade_from_data(data, metadata),
54 discriminators::POOL_CREATE => parse_pool_create_from_data(data, metadata),
55 _ => None,
56 }
57}
58
59#[inline]
61pub fn parse_trade_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
62 const TRADE_EVENT_LEN: usize = 32 + 13 * 8 + 3;
63 if data.len() != TRADE_EVENT_LEN {
64 return None;
65 }
66
67 let pool_state = read_pubkey(data, 0)?;
68 let total_base_sell = read_u64_le(data, 32)?;
69 let virtual_base = read_u64_le(data, 40)?;
70 let virtual_quote = read_u64_le(data, 48)?;
71 let real_base_before = read_u64_le(data, 56)?;
72 let real_quote_before = read_u64_le(data, 64)?;
73 let real_base_after = read_u64_le(data, 72)?;
74 let real_quote_after = read_u64_le(data, 80)?;
75 let amount_in = read_u64_le(data, 88)?;
76 let amount_out = read_u64_le(data, 96)?;
77 let protocol_fee = read_u64_le(data, 104)?;
78 let platform_fee = read_u64_le(data, 112)?;
79 let creator_fee = read_u64_le(data, 120)?;
80 let share_fee = read_u64_le(data, 128)?;
81 let trade_direction = *data.get(136)?;
82 if trade_direction > 1 {
83 return None;
84 }
85 let pool_status = *data.get(137)?;
86 if pool_status > 2 {
87 return None;
88 }
89 let exact_in_raw = *data.get(138)?;
90 if exact_in_raw > 1 {
91 return None;
92 }
93 let exact_in = exact_in_raw == 1;
94 let is_buy = trade_direction == 0;
95 let pool_status = match pool_status {
96 0 => RaydiumLaunchlabPoolStatus::Fund,
97 1 => RaydiumLaunchlabPoolStatus::Migrate,
98 2 => RaydiumLaunchlabPoolStatus::Trade,
99 _ => return None,
100 };
101
102 Some(DexEvent::RaydiumLaunchlabTrade(RaydiumLaunchlabTradeEvent {
103 third_program: None,
104 parse_status: Default::default(),
105 metadata,
106 pool_state,
107 user: Pubkey::default(),
108 amount_in,
109 amount_out,
110 is_buy,
111 total_base_sell,
112 virtual_base,
113 virtual_quote,
114 real_base_before,
115 real_quote_before,
116 real_base_after,
117 real_quote_after,
118 protocol_fee,
119 platform_fee,
120 creator_fee,
121 share_fee,
122 trade_direction: if is_buy { TradeDirection::Buy } else { TradeDirection::Sell },
123 pool_status,
124 exact_in,
125 global_config: Pubkey::default(),
126 platform_config: Pubkey::default(),
127 user_base_token: Pubkey::default(),
128 user_quote_token: Pubkey::default(),
129 base_vault: Pubkey::default(),
130 quote_vault: Pubkey::default(),
131 base_mint: Pubkey::default(),
132 quote_mint: Pubkey::default(),
133 base_token_program: Pubkey::default(),
134 quote_token_program: Pubkey::default(),
135 system_program: Pubkey::default(),
136 platform_associated_account: Pubkey::default(),
137 creator_associated_account: Pubkey::default(),
138 }))
139}
140
141#[inline]
143pub fn parse_pool_create_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
144 let mut offset = 0usize;
145 let pool_state = read_pubkey(data, offset)?;
146 offset += 32;
147 let creator = read_pubkey(data, offset)?;
148 offset += 32;
149 let _config = read_pubkey(data, offset)?;
150 offset += 32;
151 let base_mint_param = parse_mint_params(data, &mut offset)?;
152
153 Some(DexEvent::RaydiumLaunchlabPoolCreate(RaydiumLaunchlabPoolCreateEvent {
154 metadata,
155 base_mint_param,
156 pool_state,
157 payer: Pubkey::default(),
158 creator,
159 global_config: Pubkey::default(),
160 platform_config: Pubkey::default(),
161 base_mint: Pubkey::default(),
162 quote_mint: Pubkey::default(),
163 base_vault: Pubkey::default(),
164 quote_vault: Pubkey::default(),
165 base_token_program: Pubkey::default(),
166 quote_token_program: Pubkey::default(),
167 }))
168}
169
170fn parse_mint_params(data: &[u8], offset: &mut usize) -> Option<BaseMintParam> {
171 let decimals = *data.get(*offset)?;
172 *offset += 1;
173 let name = read_borsh_string(data, offset)?;
174 let symbol = read_borsh_string(data, offset)?;
175 let uri = read_borsh_string(data, offset)?;
176 Some(BaseMintParam { symbol, name, uri, decimals })
177}
178
179fn read_borsh_string(data: &[u8], offset: &mut usize) -> Option<String> {
180 let len = read_u32_le(data, *offset)? as usize;
181 *offset += 4;
182 let end = (*offset).checked_add(len)?;
183 let bytes = data.get(*offset..end)?;
184 *offset = end;
185 std::str::from_utf8(bytes).ok().map(str::to_owned)
186}
187
188#[inline]
189fn read_u32_le(data: &[u8], offset: usize) -> Option<u32> {
190 let bytes = data.get(offset..offset + 4)?;
191 Some(u32::from_le_bytes(bytes.try_into().ok()?))
192}
193
194#[cfg(test)]
195mod tests {
196 use super::*;
197
198 #[test]
199 fn current_trade_layout_preserves_reserves_and_all_fee_legs() {
200 let mut data = Vec::with_capacity(139);
201 data.extend_from_slice(Pubkey::new_unique().as_ref());
202 for value in [
203 793_100_000_000_000_u64,
204 1_073_025_605_597_286,
205 21_191_598_554,
206 284_268_264_059_232,
207 7_637_455_325,
208 284_320_641_177_089,
209 7_639_369_834,
210 1_938_744,
211 52_377_117_857,
212 4_847,
213 19_388,
214 0,
215 0,
216 ] {
217 data.extend_from_slice(&value.to_le_bytes());
218 }
219 data.extend_from_slice(&[0, 0, 1]);
220
221 let DexEvent::RaydiumLaunchlabTrade(event) =
222 parse_trade_from_data(&data, EventMetadata::default()).expect("trade")
223 else {
224 panic!("LaunchLab trade")
225 };
226
227 assert_eq!(event.total_base_sell, 793_100_000_000_000);
228 assert_eq!(event.amount_in, 1_938_744);
229 assert_eq!(event.amount_out, 52_377_117_857);
230 assert_eq!(event.protocol_fee, 4_847);
231 assert_eq!(event.platform_fee, 19_388);
232 assert_eq!(event.creator_fee, 0);
233 assert_eq!(event.share_fee, 0);
234 assert_eq!(event.pool_status, RaydiumLaunchlabPoolStatus::Fund);
235 assert!(event.is_buy);
236 assert!(event.exact_in);
237 }
238}