Skip to main content

sol_parser_sdk/instr/
raydium_launchlab.rs

1//! Raydium LaunchLab 指令解析器
2//!
3//! 底层按 `idls/raydium_launchpad.json` 的真实 instruction discriminator
4//! 和账户布局解析,对外事件名统一为 `RaydiumLaunchlab*`。
5
6use super::program_ids;
7use super::utils::*;
8use crate::core::events::*;
9use solana_sdk::{pubkey::Pubkey, signature::Signature};
10
11/// Raydium LaunchLab instruction discriminators from `idls/raydium_launchpad.json`.
12pub mod discriminators {
13    pub const BUY_EXACT_IN: [u8; 8] = [250, 234, 13, 123, 213, 156, 19, 236];
14    pub const BUY_EXACT_OUT: [u8; 8] = [24, 211, 116, 40, 105, 3, 153, 56];
15    pub const INITIALIZE: [u8; 8] = [175, 175, 109, 31, 13, 152, 155, 237];
16    pub const INITIALIZE_V2: [u8; 8] = [67, 153, 175, 39, 218, 16, 38, 32];
17    pub const INITIALIZE_WITH_TOKEN_2022: [u8; 8] = [37, 190, 126, 222, 44, 154, 171, 17];
18    pub const MIGRATE_TO_AMM: [u8; 8] = [207, 82, 192, 145, 254, 207, 145, 223];
19    pub const MIGRATE_TO_CPSWAP: [u8; 8] = [136, 92, 200, 103, 28, 218, 144, 140];
20    pub const SELL_EXACT_IN: [u8; 8] = [149, 39, 222, 155, 211, 124, 152, 26];
21    pub const SELL_EXACT_OUT: [u8; 8] = [95, 200, 71, 34, 8, 9, 11, 166];
22}
23
24/// Raydium LaunchLab 程序 ID
25pub const PROGRAM_ID_PUBKEY: Pubkey = program_ids::RAYDIUM_LAUNCHLAB_PROGRAM_ID;
26
27/// 主要的 Raydium LaunchLab 指令解析函数
28pub fn parse_instruction(
29    instruction_data: &[u8],
30    accounts: &[Pubkey],
31    signature: Signature,
32    slot: u64,
33    tx_index: u64,
34    block_time_us: Option<i64>,
35) -> Option<DexEvent> {
36    if instruction_data.len() < 8 {
37        return None;
38    }
39
40    let discriminator: [u8; 8] = instruction_data[0..8].try_into().ok()?;
41    let data = &instruction_data[8..];
42
43    match discriminator {
44        discriminators::BUY_EXACT_IN => parse_trade_instruction(
45            data,
46            accounts,
47            signature,
48            slot,
49            tx_index,
50            block_time_us,
51            true,
52            true,
53        ),
54        discriminators::BUY_EXACT_OUT => parse_trade_instruction(
55            data,
56            accounts,
57            signature,
58            slot,
59            tx_index,
60            block_time_us,
61            true,
62            false,
63        ),
64        discriminators::SELL_EXACT_IN => parse_trade_instruction(
65            data,
66            accounts,
67            signature,
68            slot,
69            tx_index,
70            block_time_us,
71            false,
72            true,
73        ),
74        discriminators::SELL_EXACT_OUT => parse_trade_instruction(
75            data,
76            accounts,
77            signature,
78            slot,
79            tx_index,
80            block_time_us,
81            false,
82            false,
83        ),
84        discriminators::INITIALIZE
85        | discriminators::INITIALIZE_V2
86        | discriminators::INITIALIZE_WITH_TOKEN_2022 => {
87            parse_pool_create_instruction(data, accounts, signature, slot, tx_index, block_time_us)
88        }
89        // The LaunchLab IDL does not expose enough fields to synthesize a
90        // migration event with the SDK's migrate layout.
91        discriminators::MIGRATE_TO_AMM | discriminators::MIGRATE_TO_CPSWAP => None,
92        _ => None,
93    }
94}
95
96/// 解析 buy/sell 指令。
97///
98/// 外层指令只携带用户输入的 amount / min-max amount;真实成交量由 log 事件覆盖。
99fn parse_trade_instruction(
100    data: &[u8],
101    accounts: &[Pubkey],
102    signature: Signature,
103    slot: u64,
104    tx_index: u64,
105    block_time_us: Option<i64>,
106    is_buy: bool,
107    exact_in: bool,
108) -> Option<DexEvent> {
109    let first_amount = read_u64_le(data, 0)?;
110    let second_amount = read_u64_le(data, 8)?;
111
112    let (amount_in, amount_out) =
113        if exact_in { (first_amount, second_amount) } else { (second_amount, first_amount) };
114
115    let pool_state = get_account(accounts, 4)?;
116    let metadata = create_metadata_simple(signature, slot, tx_index, block_time_us, pool_state);
117
118    Some(DexEvent::RaydiumLaunchlabTrade(RaydiumLaunchlabTradeEvent {
119        metadata,
120        pool_state,
121        user: get_account(accounts, 0).unwrap_or_default(),
122        amount_in,
123        amount_out,
124        is_buy,
125        trade_direction: if is_buy { TradeDirection::Buy } else { TradeDirection::Sell },
126        exact_in,
127    }))
128}
129
130/// 解析 initialize / initialize_v2 / initialize_with_token_2022 指令。
131fn parse_pool_create_instruction(
132    data: &[u8],
133    accounts: &[Pubkey],
134    signature: Signature,
135    slot: u64,
136    tx_index: u64,
137    block_time_us: Option<i64>,
138) -> Option<DexEvent> {
139    let base_mint_param = parse_mint_params(data)?;
140
141    let pool_state = get_account(accounts, 5)?;
142    let metadata = create_metadata_simple(signature, slot, tx_index, block_time_us, pool_state);
143
144    Some(DexEvent::RaydiumLaunchlabPoolCreate(RaydiumLaunchlabPoolCreateEvent {
145        metadata,
146        base_mint_param,
147        pool_state,
148        creator: get_account(accounts, 1).unwrap_or_default(),
149    }))
150}
151
152fn parse_mint_params(data: &[u8]) -> Option<BaseMintParam> {
153    let mut offset = 0usize;
154    let decimals = *data.get(offset)?;
155    offset += 1;
156    let name = read_borsh_string(data, &mut offset)?;
157    let symbol = read_borsh_string(data, &mut offset)?;
158    let uri = read_borsh_string(data, &mut offset)?;
159    Some(BaseMintParam { symbol, name, uri, decimals })
160}
161
162fn read_borsh_string(data: &[u8], offset: &mut usize) -> Option<String> {
163    let len = read_u32_le(data, *offset)? as usize;
164    *offset += 4;
165    let end = (*offset).checked_add(len)?;
166    let bytes = data.get(*offset..end)?;
167    *offset = end;
168    std::str::from_utf8(bytes).ok().map(str::to_owned)
169}
170
171#[inline]
172fn read_u32_le(data: &[u8], offset: usize) -> Option<u32> {
173    let bytes = data.get(offset..offset + 4)?;
174    Some(u32::from_le_bytes(bytes.try_into().ok()?))
175}