Skip to main content

sol_parser_sdk/instr/
pump.rs

1//! PumpFun instruction parser
2//!
3//! Parse PumpFun instructions using discriminator pattern matching
4
5use super::program_ids;
6use super::utils::*;
7use crate::core::events::*;
8use solana_sdk::{pubkey::Pubkey, signature::Signature};
9
10/// PumpFun discriminator constants
11pub mod discriminators {
12    /// Buy instruction: buy tokens with SOL
13    pub const BUY: [u8; 8] = [102, 6, 61, 18, 1, 218, 235, 234];
14    /// Sell instruction: sell tokens for SOL
15    pub const SELL: [u8; 8] = [51, 230, 133, 164, 1, 127, 131, 173];
16    /// Create instruction: create a new bonding curve
17    pub const CREATE: [u8; 8] = [24, 30, 200, 40, 5, 28, 7, 119];
18    /// CreateV2 instruction: SPL-22 / Mayhem mode (idl create_v2)
19    pub const CREATE_V2: [u8; 8] = [214, 144, 76, 236, 95, 139, 49, 180];
20    /// buy_exact_sol_in: Given a budget of spendable SOL, buy at least min_tokens_out
21    pub const BUY_EXACT_SOL_IN: [u8; 8] = [56, 252, 116, 8, 158, 223, 205, 95];
22    /// Migrate event log discriminator (CPI)
23    pub const MIGRATE_EVENT_LOG: [u8; 8] = [189, 233, 93, 185, 92, 148, 234, 148];
24}
25
26/// PumpFun Program ID
27pub const PROGRAM_ID_PUBKEY: Pubkey = program_ids::PUMPFUN_PROGRAM_ID;
28
29/// Main PumpFun instruction parser
30///
31/// Outer instructions (8-byte discriminator): CREATE, CREATE_V2 从指令解析并返回事件;
32/// BUY/SELL 仍以 log 为主。Inner CPI: MIGRATE_EVENT_LOG 仅在此解析。
33pub fn parse_instruction(
34    instruction_data: &[u8],
35    accounts: &[Pubkey],
36    signature: Signature,
37    slot: u64,
38    tx_index: u64,
39    block_time_us: Option<i64>,
40    grpc_recv_us: i64,
41) -> Option<DexEvent> {
42    if instruction_data.len() < 8 {
43        return None;
44    }
45    let outer_disc: [u8; 8] = instruction_data[0..8].try_into().ok()?;
46    let data = &instruction_data[8..];
47
48    // 外层指令:Create / CreateV2(与 solana-streamer 功能对齐)
49    if outer_disc == discriminators::CREATE_V2 {
50        return parse_create_v2_instruction(data, accounts, signature, slot, tx_index, block_time_us, grpc_recv_us);
51    }
52    if outer_disc == discriminators::CREATE {
53        return parse_create_instruction(data, accounts, signature, slot, tx_index, block_time_us, grpc_recv_us);
54    }
55
56    // Inner CPI:仅 MIGRATE 在此解析
57    if instruction_data.len() >= 16 {
58        let cpi_disc: [u8; 8] = instruction_data[8..16].try_into().ok()?;
59        if cpi_disc == discriminators::MIGRATE_EVENT_LOG {
60            return parse_migrate_log_instruction(
61                &instruction_data[16..],
62                accounts,
63                signature,
64                slot,
65                tx_index,
66                block_time_us,
67                grpc_recv_us,
68            );
69        }
70    }
71    None
72}
73
74/// Parse buy/buy_exact_sol_in instruction
75///
76/// Account indices (from pump.json IDL), 15 个固定账户:
77/// 0: global, 1: fee_recipient, 2: mint, 3: bonding_curve,
78/// 4: associated_bonding_curve, 5: associated_user, 6: user,
79/// 7: system_program, 8: token_program, 9: creator_vault,
80/// 10: event_authority, 11: program, 12: global_volume_accumulator,
81/// 13: user_volume_accumulator, 14: fee_config.
82/// remaining_accounts 可能含 bonding_curve_v2 等。
83#[allow(dead_code)]
84fn parse_buy_instruction(
85    data: &[u8],
86    accounts: &[Pubkey],
87    signature: Signature,
88    slot: u64,
89    tx_index: u64,
90    block_time_us: Option<i64>,
91    grpc_recv_us: i64,
92) -> Option<DexEvent> {
93    if accounts.len() < 7 {
94        return None;
95    }
96
97    // Parse args: amount/spendable_sol_in (u64), max_sol_cost/min_tokens_out (u64)
98    let (sol_amount, token_amount) = if data.len() >= 16 {
99        (read_u64_le(data, 0).unwrap_or(0), read_u64_le(data, 8).unwrap_or(0))
100    } else {
101        (0, 0)
102    };
103
104    let mint = get_account(accounts, 2)?;
105    let metadata = create_metadata(
106        signature, slot, tx_index,
107        block_time_us.unwrap_or_default(), grpc_recv_us
108    );
109
110    Some(DexEvent::PumpFunTrade(PumpFunTradeEvent {
111        metadata,
112        mint,
113        is_buy: true,
114        bonding_curve: get_account(accounts, 3).unwrap_or_default(),
115        user: get_account(accounts, 6).unwrap_or_default(),
116        sol_amount,
117        token_amount,
118        fee_recipient: get_account(accounts, 1).unwrap_or_default(),
119        ..Default::default()
120    }))
121}
122
123/// Parse sell instruction
124///
125/// Account indices (from pump.json IDL), 14 个固定账户:
126/// 0: global, 1: fee_recipient, 2: mint, 3: bonding_curve,
127/// 4: associated_bonding_curve, 5: associated_user, 6: user,
128/// 7: system_program, 8: creator_vault, 9: token_program,
129/// 10: event_authority, 11: program, 12: fee_config, 13: fee_program.
130/// remaining_accounts 可能含 user_volume_accumulator(返现)、bonding_curve_v2 等。
131#[allow(dead_code)]
132fn parse_sell_instruction(
133    data: &[u8],
134    accounts: &[Pubkey],
135    signature: Signature,
136    slot: u64,
137    tx_index: u64,
138    block_time_us: Option<i64>,
139    grpc_recv_us: i64,
140) -> Option<DexEvent> {
141    if accounts.len() < 7 {
142        return None;
143    }
144
145    // Parse args: amount (u64), min_sol_output (u64)
146    let (token_amount, sol_amount) = if data.len() >= 16 {
147        (read_u64_le(data, 0).unwrap_or(0), read_u64_le(data, 8).unwrap_or(0))
148    } else {
149        (0, 0)
150    };
151
152    let mint = get_account(accounts, 2)?;
153    let metadata = create_metadata(
154        signature, slot, tx_index,
155        block_time_us.unwrap_or_default(), grpc_recv_us
156    );
157
158    Some(DexEvent::PumpFunTrade(PumpFunTradeEvent {
159        metadata,
160        mint,
161        is_buy: false,
162        bonding_curve: get_account(accounts, 3).unwrap_or_default(),
163        user: get_account(accounts, 6).unwrap_or_default(),
164        sol_amount,
165        token_amount,
166        fee_recipient: get_account(accounts, 1).unwrap_or_default(),
167        ..Default::default()
168    }))
169}
170
171/// Parse create instruction (legacy)
172///
173/// Account indices (from pump.json):
174/// 0: mint, 1: mint_authority, 2: bonding_curve, 3: associated_bonding_curve,
175/// 4: global, 5: mpl_token_metadata, 6: metadata, 7: user. 共至少 8 个账户。
176fn parse_create_instruction(
177    data: &[u8],
178    accounts: &[Pubkey],
179    signature: Signature,
180    slot: u64,
181    tx_index: u64,
182    block_time_us: Option<i64>,
183    grpc_recv_us: i64,
184) -> Option<DexEvent> {
185    if accounts.len() < 8 {
186        return None;
187    }
188
189    let mut offset = 0;
190
191    // Parse args: name (string), symbol (string), uri (string), creator (pubkey)
192    // String format: 4-byte length prefix + content
193    let name = if let Some((s, len)) = read_str_unchecked(data, offset) {
194        offset += len;
195        s.to_string()
196    } else {
197        String::new()
198    };
199
200    let symbol = if let Some((s, len)) = read_str_unchecked(data, offset) {
201        offset += len;
202        s.to_string()
203    } else {
204        String::new()
205    };
206
207    let uri = if let Some((s, len)) = read_str_unchecked(data, offset) {
208        offset += len;
209        s.to_string()
210    } else {
211        String::new()
212    };
213
214    let creator = if offset + 32 <= data.len() {
215        read_pubkey(data, offset).unwrap_or_default()
216    } else {
217        Pubkey::default()
218    };
219
220    let mint = get_account(accounts, 0)?;
221    let metadata = create_metadata(
222        signature, slot, tx_index,
223        block_time_us.unwrap_or_default(), grpc_recv_us
224    );
225
226    Some(DexEvent::PumpFunCreate(PumpFunCreateTokenEvent {
227        metadata,
228        name,
229        symbol,
230        uri,
231        mint,
232        bonding_curve: get_account(accounts, 2).unwrap_or_default(),
233        user: get_account(accounts, 7).unwrap_or_default(),
234        creator,
235        ..Default::default()
236    }))
237}
238
239/// Parse create_v2 instruction (SPL-22 / Mayhem)
240///
241/// Account indices (idl pumpfun.json create_v2): 0 mint, 1 mint_authority, 2 bonding_curve,
242/// 3 associated_bonding_curve, 4 global, 5 user, 6 system_program, 7 token_program,
243/// 8 associated_token_program, 9 mayhem_program_id, 10 global_params, 11 sol_vault,
244/// 12 mayhem_state, 13 mayhem_token_vault, 14 event_authority, 15 program. 共 16 个账户。
245/// Guard: return None when accounts.len() < 16 to avoid index out of bounds (e.g. ALT-loaded tx).
246fn parse_create_v2_instruction(
247    data: &[u8],
248    accounts: &[Pubkey],
249    signature: Signature,
250    slot: u64,
251    tx_index: u64,
252    block_time_us: Option<i64>,
253    grpc_recv_us: i64,
254) -> Option<DexEvent> {
255    const CREATE_V2_MIN_ACCOUNTS: usize = 16;
256    if accounts.len() < CREATE_V2_MIN_ACCOUNTS {
257        return None;
258    }
259    let acc = &accounts[0..CREATE_V2_MIN_ACCOUNTS];
260
261    let mut offset = 0;
262    let name = if let Some((s, len)) = read_str_unchecked(data, offset) {
263        offset += len;
264        s.to_string()
265    } else {
266        String::new()
267    };
268    let symbol = if let Some((s, len)) = read_str_unchecked(data, offset) {
269        offset += len;
270        s.to_string()
271    } else {
272        String::new()
273    };
274    let uri = if let Some((s, len)) = read_str_unchecked(data, offset) {
275        offset += len;
276        s.to_string()
277    } else {
278        String::new()
279    };
280    let creator = if offset + 32 <= data.len() {
281        read_pubkey(data, offset).unwrap_or_default()
282    } else {
283        Pubkey::default()
284    };
285
286    let mint = acc[0];
287    let metadata = create_metadata(
288        signature, slot, tx_index,
289        block_time_us.unwrap_or_default(), grpc_recv_us,
290    );
291
292    Some(DexEvent::PumpFunCreateV2(PumpFunCreateV2TokenEvent {
293        metadata,
294        name,
295        symbol,
296        uri,
297        mint,
298        bonding_curve: acc[2],
299        user: acc[5],
300        creator,
301        mint_authority: acc[1],
302        associated_bonding_curve: acc[3],
303        global: acc[4],
304        system_program: acc[6],
305        token_program: acc[7],
306        associated_token_program: acc[8],
307        mayhem_program_id: acc[9],
308        global_params: acc[10],
309        sol_vault: acc[11],
310        mayhem_state: acc[12],
311        mayhem_token_vault: acc[13],
312        event_authority: acc[14],
313        program: acc[15],
314        ..Default::default()
315    }))
316}
317
318/// Parse Migrate CPI instruction
319#[allow(unused_variables)]
320fn parse_migrate_log_instruction(
321    data: &[u8],
322    accounts: &[Pubkey],
323    signature: Signature,
324    slot: u64,
325    tx_index: u64,
326    block_time_us: Option<i64>,
327    rpc_recv_us: i64,
328) -> Option<DexEvent> {
329    let mut offset = 0;
330
331    // user (Pubkey - 32 bytes)
332    let user = read_pubkey(data, offset)?;
333    offset += 32;
334
335    // mint (Pubkey - 32 bytes)
336    let mint = read_pubkey(data, offset)?;
337    offset += 32;
338
339    // mintAmount (u64 - 8 bytes)
340    let mint_amount = read_u64_le(data, offset)?;
341    offset += 8;
342
343    // solAmount (u64 - 8 bytes)
344    let sol_amount = read_u64_le(data, offset)?;
345    offset += 8;
346
347    // poolMigrationFee (u64 - 8 bytes)
348    let pool_migration_fee = read_u64_le(data, offset)?;
349    offset += 8;
350
351    // bondingCurve (Pubkey - 32 bytes)
352    let bonding_curve = read_pubkey(data, offset)?;
353    offset += 32;
354
355    // timestamp (i64 - 8 bytes)
356    let timestamp = read_u64_le(data, offset)? as i64;
357    offset += 8;
358
359    // pool (Pubkey - 32 bytes)
360    let pool = read_pubkey(data, offset)?;
361
362    let metadata =
363        create_metadata(signature, slot, tx_index, block_time_us.unwrap_or_default(), rpc_recv_us);
364
365    Some(DexEvent::PumpFunMigrate(PumpFunMigrateEvent {
366        metadata,
367        user,
368        mint,
369        mint_amount,
370        sol_amount,
371        pool_migration_fee,
372        bonding_curve,
373        timestamp,
374        pool,
375    }))
376}