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 metadata,
104 pool_state,
105 user: Pubkey::default(),
106 amount_in,
107 amount_out,
108 is_buy,
109 total_base_sell,
110 virtual_base,
111 virtual_quote,
112 real_base_before,
113 real_quote_before,
114 real_base_after,
115 real_quote_after,
116 protocol_fee,
117 platform_fee,
118 creator_fee,
119 share_fee,
120 trade_direction: if is_buy { TradeDirection::Buy } else { TradeDirection::Sell },
121 pool_status,
122 exact_in,
123 global_config: Pubkey::default(),
124 platform_config: Pubkey::default(),
125 user_base_token: Pubkey::default(),
126 user_quote_token: Pubkey::default(),
127 base_vault: Pubkey::default(),
128 quote_vault: Pubkey::default(),
129 base_mint: Pubkey::default(),
130 quote_mint: Pubkey::default(),
131 base_token_program: Pubkey::default(),
132 quote_token_program: Pubkey::default(),
133 system_program: Pubkey::default(),
134 platform_associated_account: Pubkey::default(),
135 creator_associated_account: Pubkey::default(),
136 }))
137}
138
139#[inline]
141pub fn parse_pool_create_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
142 let mut offset = 0usize;
143 let pool_state = read_pubkey(data, offset)?;
144 offset += 32;
145 let creator = read_pubkey(data, offset)?;
146 offset += 32;
147 let _config = read_pubkey(data, offset)?;
148 offset += 32;
149 let base_mint_param = parse_mint_params(data, &mut offset)?;
150
151 Some(DexEvent::RaydiumLaunchlabPoolCreate(RaydiumLaunchlabPoolCreateEvent {
152 metadata,
153 base_mint_param,
154 pool_state,
155 payer: Pubkey::default(),
156 creator,
157 global_config: Pubkey::default(),
158 platform_config: Pubkey::default(),
159 base_mint: Pubkey::default(),
160 quote_mint: Pubkey::default(),
161 base_vault: Pubkey::default(),
162 quote_vault: Pubkey::default(),
163 base_token_program: Pubkey::default(),
164 quote_token_program: Pubkey::default(),
165 }))
166}
167
168fn parse_mint_params(data: &[u8], offset: &mut usize) -> Option<BaseMintParam> {
169 let decimals = *data.get(*offset)?;
170 *offset += 1;
171 let name = read_borsh_string(data, offset)?;
172 let symbol = read_borsh_string(data, offset)?;
173 let uri = read_borsh_string(data, offset)?;
174 Some(BaseMintParam { symbol, name, uri, decimals })
175}
176
177fn read_borsh_string(data: &[u8], offset: &mut usize) -> Option<String> {
178 let len = read_u32_le(data, *offset)? as usize;
179 *offset += 4;
180 let end = (*offset).checked_add(len)?;
181 let bytes = data.get(*offset..end)?;
182 *offset = end;
183 std::str::from_utf8(bytes).ok().map(str::to_owned)
184}
185
186#[inline]
187fn read_u32_le(data: &[u8], offset: usize) -> Option<u32> {
188 let bytes = data.get(offset..offset + 4)?;
189 Some(u32::from_le_bytes(bytes.try_into().ok()?))
190}
191
192#[cfg(test)]
193mod tests {
194 use super::*;
195
196 #[test]
197 fn current_trade_layout_preserves_reserves_and_all_fee_legs() {
198 let mut data = Vec::with_capacity(139);
199 data.extend_from_slice(Pubkey::new_unique().as_ref());
200 for value in [
201 793_100_000_000_000_u64,
202 1_073_025_605_597_286,
203 21_191_598_554,
204 284_268_264_059_232,
205 7_637_455_325,
206 284_320_641_177_089,
207 7_639_369_834,
208 1_938_744,
209 52_377_117_857,
210 4_847,
211 19_388,
212 0,
213 0,
214 ] {
215 data.extend_from_slice(&value.to_le_bytes());
216 }
217 data.extend_from_slice(&[0, 0, 1]);
218
219 let DexEvent::RaydiumLaunchlabTrade(event) =
220 parse_trade_from_data(&data, EventMetadata::default()).expect("trade")
221 else {
222 panic!("LaunchLab trade")
223 };
224
225 assert_eq!(event.total_base_sell, 793_100_000_000_000);
226 assert_eq!(event.amount_in, 1_938_744);
227 assert_eq!(event.amount_out, 52_377_117_857);
228 assert_eq!(event.protocol_fee, 4_847);
229 assert_eq!(event.platform_fee, 19_388);
230 assert_eq!(event.creator_fee, 0);
231 assert_eq!(event.share_fee, 0);
232 assert_eq!(event.pool_status, RaydiumLaunchlabPoolStatus::Fund);
233 assert!(event.is_buy);
234 assert!(event.exact_in);
235 }
236}