1use solana_sdk::{pubkey::Pubkey, signature::Signature};
6use crate::core::events::*;
7use super::utils::*;
8use super::program_ids;
9
10#[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 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
36pub 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
46pub const PROGRAM_ID_PUBKEY: Pubkey = program_ids::RAYDIUM_AMM_V4_PROGRAM_ID;
48
49pub 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
88fn 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
134fn 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
180fn 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
224fn 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
266fn 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
321fn 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}