sol_parser_sdk/instr/
raydium_amm.rs

1//! Raydium AMM V4 指令解析器
2//!
3//! 使用 match discriminator 模式解析 Raydium AMM V4 指令
4
5use solana_sdk::{pubkey::Pubkey, signature::Signature};
6use crate::core::events::*;
7use super::utils::*;
8use super::program_ids;
9
10/// Raydium AMM V4 指令类型枚举
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum RaydiumAmmV4Instruction {
13    Initialize2 = 1,
14    Deposit = 3,
15    Withdraw = 4,
16    WithdrawPnl = 7,
17    SwapBaseIn = 9,
18    SwapBaseOut = 11,
19}
20
21impl RaydiumAmmV4Instruction {
22    /// 从字节转换为指令类型
23    pub fn from_u8(value: u8) -> Option<Self> {
24        match value {
25            1 => Some(Self::Initialize2),
26            3 => Some(Self::Deposit),
27            4 => Some(Self::Withdraw),
28            7 => Some(Self::WithdrawPnl),
29            9 => Some(Self::SwapBaseIn),
30            11 => Some(Self::SwapBaseOut),
31            _ => None,
32        }
33    }
34}
35
36/// Raydium AMM V4 discriminator 常量
37pub mod discriminators {
38    pub const SWAP_BASE_IN: u8 = 9;
39    pub const SWAP_BASE_OUT: u8 = 11;
40    pub const DEPOSIT: u8 = 3;
41    pub const WITHDRAW: u8 = 4;
42    pub const INITIALIZE2: u8 = 1;
43    pub const WITHDRAW_PNL: u8 = 7;
44}
45
46/// Raydium AMM 程序 ID
47pub const PROGRAM_ID_PUBKEY: Pubkey = program_ids::RAYDIUM_AMM_V4_PROGRAM_ID;
48
49/// 主要的 Raydium AMM V4 指令解析函数
50pub fn parse_instruction(
51    instruction_data: &[u8],
52    accounts: &[Pubkey],
53    signature: Signature,
54    slot: u64,
55    tx_index: u64,
56    block_time: Option<i64>,
57) -> Option<DexEvent> {
58    if instruction_data.is_empty() {
59        return None;
60    }
61
62    let discriminator_byte = instruction_data[0];
63    let instruction_type = RaydiumAmmV4Instruction::from_u8(discriminator_byte)?;
64    let data = &instruction_data[1..];
65
66    match instruction_type {
67        RaydiumAmmV4Instruction::SwapBaseIn => {
68            parse_swap_base_in_instruction(data, accounts, signature, slot, tx_index, block_time)
69        },
70        RaydiumAmmV4Instruction::SwapBaseOut => {
71            parse_swap_base_out_instruction(data, accounts, signature, slot, tx_index, block_time)
72        },
73        RaydiumAmmV4Instruction::Deposit => {
74            parse_deposit_instruction(data, accounts, signature, slot, tx_index, block_time)
75        },
76        RaydiumAmmV4Instruction::Withdraw => {
77            parse_withdraw_instruction(data, accounts, signature, slot, tx_index, block_time)
78        },
79        RaydiumAmmV4Instruction::Initialize2 => {
80            parse_initialize2_instruction(data, accounts, signature, slot, tx_index, block_time)
81        },
82        RaydiumAmmV4Instruction::WithdrawPnl => {
83            parse_withdraw_pnl_instruction(data, accounts, signature, slot, tx_index, block_time)
84        },
85    }
86}
87
88/// 解析 SwapBaseIn 指令
89fn parse_swap_base_in_instruction(
90    data: &[u8],
91    accounts: &[Pubkey],
92    signature: Signature,
93    slot: u64,
94    tx_index: u64,
95    block_time: Option<i64>,
96) -> Option<DexEvent> {
97    let mut offset = 0;
98
99    let amount_in = read_u64_le(data, offset)?;
100    offset += 8;
101
102    let minimum_amount_out = read_u64_le(data, offset)?;
103
104    let amm = get_account(accounts, 1)?;
105    let metadata = create_metadata_simple(signature, slot, tx_index, block_time, amm);
106
107    Some(DexEvent::RaydiumAmmV4Swap(RaydiumAmmV4SwapEvent {
108        metadata,
109        amount_in,
110        minimum_amount_out,
111        max_amount_in: 0,
112        amount_out: 0,
113        token_program: get_account(accounts, 0).unwrap_or_default(),
114        amm,
115        amm_authority: get_account(accounts, 2).unwrap_or_default(),
116        amm_open_orders: get_account(accounts, 3).unwrap_or_default(),
117        amm_target_orders: get_account(accounts, 4),
118        pool_coin_token_account: get_account(accounts, 5).unwrap_or_default(),
119        pool_pc_token_account: get_account(accounts, 6).unwrap_or_default(),
120        serum_program: get_account(accounts, 7).unwrap_or_default(),
121        serum_market: get_account(accounts, 8).unwrap_or_default(),
122        serum_bids: get_account(accounts, 9).unwrap_or_default(),
123        serum_asks: get_account(accounts, 10).unwrap_or_default(),
124        serum_event_queue: get_account(accounts, 11).unwrap_or_default(),
125        serum_coin_vault_account: get_account(accounts, 12).unwrap_or_default(),
126        serum_pc_vault_account: get_account(accounts, 13).unwrap_or_default(),
127        serum_vault_signer: get_account(accounts, 14).unwrap_or_default(),
128        user_source_token_account: get_account(accounts, 15).unwrap_or_default(),
129        user_destination_token_account: get_account(accounts, 16).unwrap_or_default(),
130        user_source_owner: get_account(accounts, 17).unwrap_or_default(),
131    }))
132}
133
134/// 解析 SwapBaseOut 指令
135fn parse_swap_base_out_instruction(
136    data: &[u8],
137    accounts: &[Pubkey],
138    signature: Signature,
139    slot: u64,
140    tx_index: u64,
141    block_time: Option<i64>,
142) -> Option<DexEvent> {
143    let mut offset = 0;
144
145    let max_amount_in = read_u64_le(data, offset)?;
146    offset += 8;
147
148    let amount_out = read_u64_le(data, offset)?;
149
150    let amm = get_account(accounts, 1)?;
151    let metadata = create_metadata_simple(signature, slot, tx_index, block_time, amm);
152
153    Some(DexEvent::RaydiumAmmV4Swap(RaydiumAmmV4SwapEvent {
154        metadata,
155        amount_in: 0,
156        minimum_amount_out: 0,
157        max_amount_in,
158        amount_out,
159        token_program: get_account(accounts, 0).unwrap_or_default(),
160        amm,
161        amm_authority: get_account(accounts, 2).unwrap_or_default(),
162        amm_open_orders: get_account(accounts, 3).unwrap_or_default(),
163        amm_target_orders: get_account(accounts, 4),
164        pool_coin_token_account: get_account(accounts, 5).unwrap_or_default(),
165        pool_pc_token_account: get_account(accounts, 6).unwrap_or_default(),
166        serum_program: get_account(accounts, 7).unwrap_or_default(),
167        serum_market: get_account(accounts, 8).unwrap_or_default(),
168        serum_bids: get_account(accounts, 9).unwrap_or_default(),
169        serum_asks: get_account(accounts, 10).unwrap_or_default(),
170        serum_event_queue: get_account(accounts, 11).unwrap_or_default(),
171        serum_coin_vault_account: get_account(accounts, 12).unwrap_or_default(),
172        serum_pc_vault_account: get_account(accounts, 13).unwrap_or_default(),
173        serum_vault_signer: get_account(accounts, 14).unwrap_or_default(),
174        user_source_token_account: get_account(accounts, 15).unwrap_or_default(),
175        user_destination_token_account: get_account(accounts, 16).unwrap_or_default(),
176        user_source_owner: get_account(accounts, 17).unwrap_or_default(),
177    }))
178}
179
180/// 解析存款指令
181fn parse_deposit_instruction(
182    data: &[u8],
183    accounts: &[Pubkey],
184    signature: Signature,
185    slot: u64,
186    tx_index: u64,
187    block_time: Option<i64>,
188) -> Option<DexEvent> {
189    let mut offset = 0;
190
191    let max_coin_amount = read_u64_le(data, offset)?;
192    offset += 8;
193
194    let max_pc_amount = read_u64_le(data, offset)?;
195    offset += 8;
196
197    let base_side = read_u64_le(data, offset)?;
198
199    let amm = get_account(accounts, 1)?;
200    let metadata = create_metadata_simple(signature, slot, tx_index, block_time, amm);
201
202    Some(DexEvent::RaydiumAmmV4Deposit(RaydiumAmmV4DepositEvent {
203        metadata,
204        max_coin_amount,
205        max_pc_amount,
206        base_side,
207        token_program: get_account(accounts, 0).unwrap_or_default(),
208        amm,
209        amm_authority: get_account(accounts, 2).unwrap_or_default(),
210        amm_open_orders: get_account(accounts, 3).unwrap_or_default(),
211        amm_target_orders: get_account(accounts, 4).unwrap_or_default(),
212        lp_mint_address: get_account(accounts, 5).unwrap_or_default(),
213        pool_coin_token_account: get_account(accounts, 6).unwrap_or_default(),
214        pool_pc_token_account: get_account(accounts, 7).unwrap_or_default(),
215        serum_market: get_account(accounts, 8).unwrap_or_default(),
216        user_coin_token_account: get_account(accounts, 9).unwrap_or_default(),
217        user_pc_token_account: get_account(accounts, 10).unwrap_or_default(),
218        user_lp_token_account: get_account(accounts, 11).unwrap_or_default(),
219        user_owner: get_account(accounts, 12).unwrap_or_default(),
220        serum_event_queue: get_account(accounts, 13).unwrap_or_default(),
221    }))
222}
223
224/// 解析提取指令
225fn parse_withdraw_instruction(
226    data: &[u8],
227    accounts: &[Pubkey],
228    signature: Signature,
229    slot: u64,
230    tx_index: u64,
231    block_time: Option<i64>,
232) -> Option<DexEvent> {
233    let amount = read_u64_le(data, 0)?;
234
235    let amm = get_account(accounts, 1)?;
236    let metadata = create_metadata_simple(signature, slot, tx_index, block_time, amm);
237
238    Some(DexEvent::RaydiumAmmV4Withdraw(RaydiumAmmV4WithdrawEvent {
239        metadata,
240        amount,
241        token_program: get_account(accounts, 0).unwrap_or_default(),
242        amm,
243        amm_authority: get_account(accounts, 2).unwrap_or_default(),
244        amm_open_orders: get_account(accounts, 3).unwrap_or_default(),
245        amm_target_orders: get_account(accounts, 4).unwrap_or_default(),
246        lp_mint_address: get_account(accounts, 5).unwrap_or_default(),
247        pool_coin_token_account: get_account(accounts, 6).unwrap_or_default(),
248        pool_pc_token_account: get_account(accounts, 7).unwrap_or_default(),
249        pool_withdraw_queue: get_account(accounts, 8).unwrap_or_default(),
250        pool_temp_lp_token_account: get_account(accounts, 9).unwrap_or_default(),
251        serum_program: get_account(accounts, 10).unwrap_or_default(),
252        serum_market: get_account(accounts, 11).unwrap_or_default(),
253        serum_coin_vault_account: get_account(accounts, 12).unwrap_or_default(),
254        serum_pc_vault_account: get_account(accounts, 13).unwrap_or_default(),
255        serum_vault_signer: get_account(accounts, 14).unwrap_or_default(),
256        user_lp_token_account: get_account(accounts, 15).unwrap_or_default(),
257        user_coin_token_account: get_account(accounts, 16).unwrap_or_default(),
258        user_pc_token_account: get_account(accounts, 17).unwrap_or_default(),
259        user_owner: get_account(accounts, 18).unwrap_or_default(),
260        serum_event_queue: get_account(accounts, 19).unwrap_or_default(),
261        serum_bids: get_account(accounts, 20).unwrap_or_default(),
262        serum_asks: get_account(accounts, 21).unwrap_or_default(),
263    }))
264}
265
266/// 解析初始化指令
267fn parse_initialize2_instruction(
268    data: &[u8],
269    accounts: &[Pubkey],
270    signature: Signature,
271    slot: u64,
272    tx_index: u64,
273    block_time: Option<i64>,
274) -> Option<DexEvent> {
275    let mut offset = 0;
276
277    let nonce = data.get(offset)?.clone();
278    offset += 1;
279
280    let open_time = read_u64_le(data, offset)?;
281    offset += 8;
282
283    let init_pc_amount = read_u64_le(data, offset)?;
284    offset += 8;
285
286    let init_coin_amount = read_u64_le(data, offset)?;
287
288    let amm = get_account(accounts, 4)?;
289    let metadata = create_metadata_simple(signature, slot, tx_index, block_time, amm);
290
291    Some(DexEvent::RaydiumAmmV4Initialize2(RaydiumAmmV4Initialize2Event {
292        metadata,
293        nonce,
294        open_time,
295        init_pc_amount,
296        init_coin_amount,
297        token_program: get_account(accounts, 0).unwrap_or_default(),
298        spl_associated_token_account: get_account(accounts, 1).unwrap_or_default(),
299        system_program: get_account(accounts, 2).unwrap_or_default(),
300        rent: get_account(accounts, 3).unwrap_or_default(),
301        amm,
302        amm_authority: get_account(accounts, 5).unwrap_or_default(),
303        amm_open_orders: get_account(accounts, 6).unwrap_or_default(),
304        lp_mint: get_account(accounts, 7).unwrap_or_default(),
305        coin_mint: get_account(accounts, 8).unwrap_or_default(),
306        pc_mint: get_account(accounts, 9).unwrap_or_default(),
307        pool_coin_token_account: get_account(accounts, 10).unwrap_or_default(),
308        pool_pc_token_account: get_account(accounts, 11).unwrap_or_default(),
309        pool_withdraw_queue: get_account(accounts, 12).unwrap_or_default(),
310        amm_target_orders: get_account(accounts, 13).unwrap_or_default(),
311        pool_temp_lp: get_account(accounts, 14).unwrap_or_default(),
312        serum_program: get_account(accounts, 15).unwrap_or_default(),
313        serum_market: get_account(accounts, 16).unwrap_or_default(),
314        user_wallet: get_account(accounts, 17).unwrap_or_default(),
315        user_token_coin: get_account(accounts, 18).unwrap_or_default(),
316        user_token_pc: get_account(accounts, 19).unwrap_or_default(),
317        user_lp_token_account: get_account(accounts, 20).unwrap_or_default(),
318    }))
319}
320
321/// 解析提取PnL指令
322fn parse_withdraw_pnl_instruction(
323    _data: &[u8],
324    accounts: &[Pubkey],
325    signature: Signature,
326    slot: u64,
327    tx_index: u64,
328    block_time: Option<i64>,
329) -> Option<DexEvent> {
330    let amm = get_account(accounts, 1)?;
331    let metadata = create_metadata_simple(signature, slot, tx_index, block_time, amm);
332
333    Some(DexEvent::RaydiumAmmV4WithdrawPnl(RaydiumAmmV4WithdrawPnlEvent {
334        metadata,
335        token_program: get_account(accounts, 0).unwrap_or_default(),
336        amm,
337        amm_config: get_account(accounts, 2).unwrap_or_default(),
338        amm_authority: get_account(accounts, 3).unwrap_or_default(),
339        amm_open_orders: get_account(accounts, 4).unwrap_or_default(),
340        pool_coin_token_account: get_account(accounts, 5).unwrap_or_default(),
341        pool_pc_token_account: get_account(accounts, 6).unwrap_or_default(),
342        coin_pnl_token_account: get_account(accounts, 7).unwrap_or_default(),
343        pc_pnl_token_account: get_account(accounts, 8).unwrap_or_default(),
344        pnl_owner: get_account(accounts, 9).unwrap_or_default(),
345        amm_target_orders: get_account(accounts, 10).unwrap_or_default(),
346        serum_program: get_account(accounts, 11).unwrap_or_default(),
347        serum_market: get_account(accounts, 12).unwrap_or_default(),
348        serum_event_queue: get_account(accounts, 13).unwrap_or_default(),
349        serum_coin_vault_account: get_account(accounts, 14).unwrap_or_default(),
350        serum_pc_vault_account: get_account(accounts, 15).unwrap_or_default(),
351        serum_vault_signer: get_account(accounts, 16).unwrap_or_default(),
352    }))
353}