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    /// buy_exact_sol_in: Given a budget of spendable SOL, buy at least min_tokens_out
19    pub const BUY_EXACT_SOL_IN: [u8; 8] = [56, 252, 116, 8, 158, 223, 205, 95];
20    /// Migrate event log discriminator (CPI)
21    pub const MIGRATE_EVENT_LOG: [u8; 8] = [189, 233, 93, 185, 92, 148, 234, 148];
22}
23
24/// PumpFun Program ID
25pub const PROGRAM_ID_PUBKEY: Pubkey = program_ids::PUMPFUN_PROGRAM_ID;
26
27/// Main PumpFun instruction parser
28///
29/// Note: Full event data (amounts, fees, reserves) is parsed from logs.
30/// Instruction parsing only handles MIGRATE_EVENT_LOG which is not available in logs.
31pub fn parse_instruction(
32    instruction_data: &[u8],
33    accounts: &[Pubkey],
34    signature: Signature,
35    slot: u64,
36    tx_index: u64,
37    block_time_us: Option<i64>,
38    grpc_recv_us: i64,
39) -> Option<DexEvent> {
40    // BUY/SELL/CREATE events are parsed from logs for complete data
41    // Only parse MIGRATE_EVENT_LOG here (CPI instruction not available in logs)
42    if instruction_data.len() < 16 {
43        return None;
44    }
45
46    let cpi_discriminator: [u8; 8] = instruction_data[8..16].try_into().ok()?;
47    if cpi_discriminator == discriminators::MIGRATE_EVENT_LOG {
48        parse_migrate_log_instruction(
49            &instruction_data[16..],
50            accounts,
51            signature,
52            slot,
53            tx_index,
54            block_time_us,
55            grpc_recv_us,
56        )
57    } else {
58        None
59    }
60}
61
62/// Parse buy/buy_exact_sol_in instruction
63///
64/// Account indices (from pump.json):
65/// 0: global, 1: fee_recipient, 2: mint, 3: bonding_curve,
66/// 4: associated_bonding_curve, 5: associated_user, 6: user
67#[allow(dead_code)]
68fn parse_buy_instruction(
69    data: &[u8],
70    accounts: &[Pubkey],
71    signature: Signature,
72    slot: u64,
73    tx_index: u64,
74    block_time_us: Option<i64>,
75    grpc_recv_us: i64,
76) -> Option<DexEvent> {
77    if accounts.len() < 7 {
78        return None;
79    }
80
81    // Parse args: amount/spendable_sol_in (u64), max_sol_cost/min_tokens_out (u64)
82    let (sol_amount, token_amount) = if data.len() >= 16 {
83        (read_u64_le(data, 0).unwrap_or(0), read_u64_le(data, 8).unwrap_or(0))
84    } else {
85        (0, 0)
86    };
87
88    let mint = get_account(accounts, 2)?;
89    let metadata = create_metadata(
90        signature, slot, tx_index,
91        block_time_us.unwrap_or_default(), grpc_recv_us
92    );
93
94    Some(DexEvent::PumpFunTrade(PumpFunTradeEvent {
95        metadata,
96        mint,
97        is_buy: true,
98        bonding_curve: get_account(accounts, 3).unwrap_or_default(),
99        user: get_account(accounts, 6).unwrap_or_default(),
100        sol_amount,
101        token_amount,
102        fee_recipient: get_account(accounts, 1).unwrap_or_default(),
103        ..Default::default()
104    }))
105}
106
107/// Parse sell instruction
108///
109/// Account indices (from pump.json):
110/// 0: global, 1: fee_recipient, 2: mint, 3: bonding_curve,
111/// 4: associated_bonding_curve, 5: associated_user, 6: user
112#[allow(dead_code)]
113fn parse_sell_instruction(
114    data: &[u8],
115    accounts: &[Pubkey],
116    signature: Signature,
117    slot: u64,
118    tx_index: u64,
119    block_time_us: Option<i64>,
120    grpc_recv_us: i64,
121) -> Option<DexEvent> {
122    if accounts.len() < 7 {
123        return None;
124    }
125
126    // Parse args: amount (u64), min_sol_output (u64)
127    let (token_amount, sol_amount) = if data.len() >= 16 {
128        (read_u64_le(data, 0).unwrap_or(0), read_u64_le(data, 8).unwrap_or(0))
129    } else {
130        (0, 0)
131    };
132
133    let mint = get_account(accounts, 2)?;
134    let metadata = create_metadata(
135        signature, slot, tx_index,
136        block_time_us.unwrap_or_default(), grpc_recv_us
137    );
138
139    Some(DexEvent::PumpFunTrade(PumpFunTradeEvent {
140        metadata,
141        mint,
142        is_buy: false,
143        bonding_curve: get_account(accounts, 3).unwrap_or_default(),
144        user: get_account(accounts, 6).unwrap_or_default(),
145        sol_amount,
146        token_amount,
147        fee_recipient: get_account(accounts, 1).unwrap_or_default(),
148        ..Default::default()
149    }))
150}
151
152/// Parse create instruction
153///
154/// Account indices (from pump.json):
155/// 0: mint, 1: mint_authority, 2: bonding_curve, 3: associated_bonding_curve,
156/// 4: global, 5: mpl_token_metadata, 6: metadata, 7: user
157#[allow(dead_code)]
158fn parse_create_instruction(
159    data: &[u8],
160    accounts: &[Pubkey],
161    signature: Signature,
162    slot: u64,
163    tx_index: u64,
164    block_time_us: Option<i64>,
165    grpc_recv_us: i64,
166) -> Option<DexEvent> {
167    if accounts.len() < 8 {
168        return None;
169    }
170
171    let mut offset = 0;
172
173    // Parse args: name (string), symbol (string), uri (string), creator (pubkey)
174    // String format: 4-byte length prefix + content
175    let name = if let Some((s, len)) = read_str_unchecked(data, offset) {
176        offset += len;
177        s.to_string()
178    } else {
179        String::new()
180    };
181
182    let symbol = if let Some((s, len)) = read_str_unchecked(data, offset) {
183        offset += len;
184        s.to_string()
185    } else {
186        String::new()
187    };
188
189    let uri = if let Some((s, len)) = read_str_unchecked(data, offset) {
190        offset += len;
191        s.to_string()
192    } else {
193        String::new()
194    };
195
196    let creator = if offset + 32 <= data.len() {
197        read_pubkey(data, offset).unwrap_or_default()
198    } else {
199        Pubkey::default()
200    };
201
202    let mint = get_account(accounts, 0)?;
203    let metadata = create_metadata(
204        signature, slot, tx_index,
205        block_time_us.unwrap_or_default(), grpc_recv_us
206    );
207
208    Some(DexEvent::PumpFunCreate(PumpFunCreateTokenEvent {
209        metadata,
210        name,
211        symbol,
212        uri,
213        mint,
214        bonding_curve: get_account(accounts, 2).unwrap_or_default(),
215        user: get_account(accounts, 7).unwrap_or_default(),
216        creator,
217        ..Default::default()
218    }))
219}
220
221/// Parse Migrate CPI instruction
222#[allow(unused_variables)]
223fn parse_migrate_log_instruction(
224    data: &[u8],
225    accounts: &[Pubkey],
226    signature: Signature,
227    slot: u64,
228    tx_index: u64,
229    block_time_us: Option<i64>,
230    rpc_recv_us: i64,
231) -> Option<DexEvent> {
232    let mut offset = 0;
233
234    // user (Pubkey - 32 bytes)
235    let user = read_pubkey(data, offset)?;
236    offset += 32;
237
238    // mint (Pubkey - 32 bytes)
239    let mint = read_pubkey(data, offset)?;
240    offset += 32;
241
242    // mintAmount (u64 - 8 bytes)
243    let mint_amount = read_u64_le(data, offset)?;
244    offset += 8;
245
246    // solAmount (u64 - 8 bytes)
247    let sol_amount = read_u64_le(data, offset)?;
248    offset += 8;
249
250    // poolMigrationFee (u64 - 8 bytes)
251    let pool_migration_fee = read_u64_le(data, offset)?;
252    offset += 8;
253
254    // bondingCurve (Pubkey - 32 bytes)
255    let bonding_curve = read_pubkey(data, offset)?;
256    offset += 32;
257
258    // timestamp (i64 - 8 bytes)
259    let timestamp = read_u64_le(data, offset)? as i64;
260    offset += 8;
261
262    // pool (Pubkey - 32 bytes)
263    let pool = read_pubkey(data, offset)?;
264
265    let metadata =
266        create_metadata(signature, slot, tx_index, block_time_us.unwrap_or_default(), rpc_recv_us);
267
268    Some(DexEvent::PumpFunMigrate(PumpFunMigrateEvent {
269        metadata,
270        user,
271        mint,
272        mint_amount,
273        sol_amount,
274        pool_migration_fee,
275        bonding_curve,
276        timestamp,
277        pool,
278    }))
279}