Skip to main content

sol_parser_sdk/accounts/
mod.rs

1pub mod nonce;
2pub mod program_ids;
3pub mod pumpswap;
4pub mod raydium_clmm;
5pub mod rpc_wallet;
6pub mod token;
7pub mod utils;
8use crate::core::events::EventMetadata;
9use crate::grpc::EventTypeFilter;
10use crate::DexEvent;
11pub use nonce::parse_nonce_account;
12use program_ids::*;
13pub use pumpswap::{
14    parse_global_config as parse_pumpswap_global_config, parse_pool as parse_pumpswap_pool,
15};
16pub use rpc_wallet::rpc_resolve_user_wallet_pubkey;
17pub use token::parse_token_account;
18pub use token::AccountData;
19pub use utils::*;
20
21pub fn parse_account_unified(
22    account: &AccountData,
23    metadata: EventMetadata,
24    event_type_filter: Option<&EventTypeFilter>,
25) -> Option<DexEvent> {
26    if account.data.is_empty() {
27        return None;
28    }
29
30    // Early filtering based on event type filter
31    if let Some(filter) = event_type_filter {
32        if let Some(ref include_only) = filter.include_only {
33            // Check if any of the account event types are in the include list
34            let should_parse = include_only.iter().any(|t| {
35                use crate::grpc::EventType;
36                matches!(
37                    t,
38                    EventType::TokenAccount
39                        | EventType::NonceAccount
40                        | EventType::AccountPumpFunGlobal
41                        | EventType::AccountPumpFunBondingCurve
42                        | EventType::AccountPumpFunFeeConfig
43                        | EventType::AccountPumpFunSharingConfig
44                        | EventType::AccountPumpFunGlobalVolumeAccumulator
45                        | EventType::AccountPumpFunUserVolumeAccumulator
46                        | EventType::AccountPumpSwapGlobalConfig
47                        | EventType::AccountPumpSwapPool
48                        | EventType::AccountRaydiumClmmAmmConfig
49                        | EventType::AccountRaydiumClmmPoolState
50                        | EventType::AccountRaydiumClmmTickArrayState
51                )
52            });
53            if !should_parse {
54                return None;
55            }
56        }
57    }
58
59    if account.owner == PUMPSWAP_PROGRAM_ID {
60        let should_parse = event_type_filter.map_or(true, |filter| {
61            filter.should_include(crate::grpc::EventType::AccountPumpSwapGlobalConfig)
62                || filter.should_include(crate::grpc::EventType::AccountPumpSwapPool)
63        });
64        if should_parse {
65            let event = parse_pumpswap_account(account, metadata.clone());
66            if event.is_some() {
67                return event;
68            }
69        }
70    }
71    if account.owner == crate::instr::program_ids::RAYDIUM_CLMM_PROGRAM_ID {
72        let should_parse = event_type_filter.map_or(true, |filter| {
73            filter.should_include(crate::grpc::EventType::AccountRaydiumClmmAmmConfig)
74                || filter.should_include(crate::grpc::EventType::AccountRaydiumClmmPoolState)
75                || filter.should_include(crate::grpc::EventType::AccountRaydiumClmmTickArrayState)
76        });
77        if should_parse {
78            let event = raydium_clmm::parse_account(account, metadata.clone());
79            if event.is_some() {
80                return event;
81            }
82        }
83    }
84    if account.owner == crate::grpc::program_ids::PUMPFUN_PROGRAM
85        || account.owner == crate::instr::program_ids::PUMP_FEES_PROGRAM_ID
86    {
87        let should_parse = event_type_filter.map_or(true, |filter| {
88            filter.should_include(crate::grpc::EventType::AccountPumpFunGlobal)
89                || filter.should_include(crate::grpc::EventType::AccountPumpFunBondingCurve)
90                || filter.should_include(crate::grpc::EventType::AccountPumpFunFeeConfig)
91                || filter.should_include(crate::grpc::EventType::AccountPumpFunSharingConfig)
92                || filter
93                    .should_include(crate::grpc::EventType::AccountPumpFunGlobalVolumeAccumulator)
94                || filter
95                    .should_include(crate::grpc::EventType::AccountPumpFunUserVolumeAccumulator)
96        });
97        if should_parse {
98            let event = parse_pumpfun_account(account, metadata.clone());
99            if event.is_some() {
100                return event;
101            }
102        }
103    }
104    if nonce::is_nonce_account(&account.data) {
105        // Check filter for NonceAccount specifically
106        if let Some(filter) = event_type_filter {
107            if !filter.should_include(crate::grpc::EventType::NonceAccount) {
108                return None;
109            }
110        }
111        return parse_nonce_account(account, metadata);
112    }
113    // Parse token account (includes both TokenAccount and TokenInfo)
114    if let Some(filter) = event_type_filter {
115        let includes_token = filter.should_include(crate::grpc::EventType::TokenAccount);
116        if !includes_token {
117            return None;
118        }
119    }
120    return parse_token_account(account, metadata);
121}
122
123fn parse_pumpswap_account(account: &AccountData, metadata: EventMetadata) -> Option<DexEvent> {
124    // 检查 discriminator 以确定账户类型
125    if pumpswap::is_global_config_account(&account.data) {
126        return pumpswap::parse_global_config(account, metadata);
127    }
128    if pumpswap::is_pool_account(&account.data) {
129        return pumpswap::parse_pool(account, metadata);
130    }
131    None
132}
133
134fn parse_pumpfun_account(account: &AccountData, metadata: EventMetadata) -> Option<DexEvent> {
135    use crate::core::events::{
136        PumpFeesConfigStatus, PumpFeesFeeTier, PumpFeesFees, PumpFeesShareholder,
137        PumpFunBondingCurve, PumpFunBondingCurveAccountEvent, PumpFunFeeConfig,
138        PumpFunFeeConfigAccountEvent, PumpFunGlobal, PumpFunGlobalAccountEvent,
139        PumpFunGlobalVolumeAccumulator, PumpFunGlobalVolumeAccumulatorAccountEvent,
140        PumpFunSharingConfig, PumpFunSharingConfigAccountEvent, PumpFunUserVolumeAccumulator,
141        PumpFunUserVolumeAccumulatorAccountEvent,
142    };
143
144    const GLOBAL_DISCRIMINATOR: &[u8; 8] = &[167, 232, 232, 177, 200, 108, 114, 127];
145    const BONDING_CURVE_DISCRIMINATOR: &[u8; 8] = &[23, 183, 248, 55, 96, 216, 172, 96];
146    const FEE_CONFIG_DISCRIMINATOR: &[u8; 8] = &[143, 52, 146, 187, 219, 123, 76, 155];
147    const GLOBAL_VOLUME_ACCUMULATOR_DISCRIMINATOR: &[u8; 8] =
148        &[202, 42, 246, 43, 142, 190, 30, 255];
149    const SHARING_CONFIG_DISCRIMINATOR: &[u8; 8] = &[216, 74, 9, 0, 56, 140, 93, 75];
150    const USER_VOLUME_ACCUMULATOR_DISCRIMINATOR: &[u8; 8] = &[86, 255, 112, 14, 102, 53, 154, 250];
151    const MAX_FEE_TIERS: usize = 64;
152    const MAX_SHAREHOLDERS: usize = 64;
153
154    fn read_i64(data: &[u8], offset: &mut usize) -> Option<i64> {
155        let value = i64::from_le_bytes(data.get(*offset..*offset + 8)?.try_into().ok()?);
156        *offset += 8;
157        Some(value)
158    }
159    fn read_u32(data: &[u8], offset: &mut usize) -> Option<u32> {
160        let value = u32::from_le_bytes(data.get(*offset..*offset + 4)?.try_into().ok()?);
161        *offset += 4;
162        Some(value)
163    }
164    fn read_u64(data: &[u8], offset: &mut usize) -> Option<u64> {
165        let value = read_u64_le(data, *offset)?;
166        *offset += 8;
167        Some(value)
168    }
169    fn read_u128(data: &[u8], offset: &mut usize) -> Option<u128> {
170        let value = u128::from_le_bytes(data.get(*offset..*offset + 16)?.try_into().ok()?);
171        *offset += 16;
172        Some(value)
173    }
174    fn read_pk(data: &[u8], offset: &mut usize) -> Option<solana_sdk::pubkey::Pubkey> {
175        let value = read_pubkey(data, *offset)?;
176        *offset += 32;
177        Some(value)
178    }
179    fn read_fees(data: &[u8], offset: &mut usize) -> Option<PumpFeesFees> {
180        Some(PumpFeesFees {
181            lp_fee_bps: read_u64(data, offset)?,
182            protocol_fee_bps: read_u64(data, offset)?,
183            creator_fee_bps: read_u64(data, offset)?,
184        })
185    }
186    fn read_fee_tiers(data: &[u8], offset: &mut usize) -> Option<Vec<PumpFeesFeeTier>> {
187        let len = read_u32(data, offset)? as usize;
188        if len > MAX_FEE_TIERS {
189            return None;
190        }
191        let mut out = Vec::with_capacity(len);
192        for _ in 0..len {
193            out.push(PumpFeesFeeTier {
194                market_cap_lamports_threshold: read_u128(data, offset)?,
195                fees: read_fees(data, offset)?,
196            });
197        }
198        Some(out)
199    }
200    fn read_shareholders(data: &[u8], offset: &mut usize) -> Option<Vec<PumpFeesShareholder>> {
201        let len = read_u32(data, offset)? as usize;
202        if len > MAX_SHAREHOLDERS {
203            return None;
204        }
205        let mut out = Vec::with_capacity(len);
206        for _ in 0..len {
207            let address = read_pk(data, offset)?;
208            let share_bps = u16::from_le_bytes(data.get(*offset..*offset + 2)?.try_into().ok()?);
209            *offset += 2;
210            out.push(PumpFeesShareholder { address, share_bps });
211        }
212        Some(out)
213    }
214    fn read_status(data: &[u8], offset: &mut usize) -> Option<PumpFeesConfigStatus> {
215        let value = *data.get(*offset)?;
216        *offset += 1;
217        match value {
218            0 => Some(PumpFeesConfigStatus::Paused),
219            1 => Some(PumpFeesConfigStatus::Active),
220            _ => None,
221        }
222    }
223
224    if has_discriminator(&account.data, FEE_CONFIG_DISCRIMINATOR) {
225        let data = &account.data[8..];
226        let mut offset = 0usize;
227        let fee_config = PumpFunFeeConfig {
228            bump: *data.get(offset)?,
229            admin: {
230                offset += 1;
231                read_pk(data, &mut offset)?
232            },
233            flat_fees: read_fees(data, &mut offset)?,
234            fee_tiers: read_fee_tiers(data, &mut offset)?,
235            stable_fee_tiers: read_fee_tiers(data, &mut offset)?,
236        };
237        return Some(DexEvent::PumpFunFeeConfigAccount(PumpFunFeeConfigAccountEvent {
238            metadata,
239            pubkey: account.pubkey,
240            fee_config,
241        }));
242    }
243
244    if has_discriminator(&account.data, SHARING_CONFIG_DISCRIMINATOR) {
245        let data = &account.data[8..];
246        let mut offset = 0usize;
247        let bump = *data.get(offset)?;
248        offset += 1;
249        let version = *data.get(offset)?;
250        offset += 1;
251        let status = read_status(data, &mut offset)?;
252        let mint = read_pk(data, &mut offset)?;
253        let admin = read_pk(data, &mut offset)?;
254        let admin_revoked = *data.get(offset)? != 0;
255        offset += 1;
256        let shareholders = read_shareholders(data, &mut offset)?;
257        return Some(DexEvent::PumpFunSharingConfigAccount(PumpFunSharingConfigAccountEvent {
258            metadata,
259            pubkey: account.pubkey,
260            sharing_config: PumpFunSharingConfig {
261                bump,
262                version,
263                status,
264                mint,
265                admin,
266                admin_revoked,
267                shareholders,
268            },
269        }));
270    }
271
272    if has_discriminator(&account.data, GLOBAL_VOLUME_ACCUMULATOR_DISCRIMINATOR) {
273        let data = &account.data[8..];
274        let mut offset = 0usize;
275        let start_time = read_i64(data, &mut offset)?;
276        let end_time = read_i64(data, &mut offset)?;
277        let seconds_in_a_day = read_i64(data, &mut offset)?;
278        let mint = read_pk(data, &mut offset)?;
279        let mut total_token_supply = [0u64; 30];
280        for value in &mut total_token_supply {
281            *value = read_u64(data, &mut offset)?;
282        }
283        let mut sol_volumes = [0u64; 30];
284        for value in &mut sol_volumes {
285            *value = read_u64(data, &mut offset)?;
286        }
287        return Some(DexEvent::PumpFunGlobalVolumeAccumulatorAccount(
288            PumpFunGlobalVolumeAccumulatorAccountEvent {
289                metadata,
290                pubkey: account.pubkey,
291                global_volume_accumulator: PumpFunGlobalVolumeAccumulator {
292                    start_time,
293                    end_time,
294                    seconds_in_a_day,
295                    mint,
296                    total_token_supply,
297                    sol_volumes,
298                },
299            },
300        ));
301    }
302
303    if has_discriminator(&account.data, USER_VOLUME_ACCUMULATOR_DISCRIMINATOR) {
304        let data = &account.data[8..];
305        let mut offset = 0usize;
306        let user = read_pk(data, &mut offset)?;
307        let needs_claim = *data.get(offset)? != 0;
308        offset += 1;
309        let total_unclaimed_tokens = read_u64(data, &mut offset)?;
310        let total_claimed_tokens = read_u64(data, &mut offset)?;
311        let current_sol_volume = read_u64(data, &mut offset)?;
312        let last_update_timestamp = read_i64(data, &mut offset)?;
313        let has_total_claimed_tokens = *data.get(offset)? != 0;
314        offset += 1;
315        let cashback_earned = read_u64(data, &mut offset)?;
316        let total_cashback_claimed = read_u64(data, &mut offset)?;
317        let stable_cashback_earned = read_u64(data, &mut offset)?;
318        let total_stable_cashback_claimed = read_u64(data, &mut offset)?;
319        return Some(DexEvent::PumpFunUserVolumeAccumulatorAccount(
320            PumpFunUserVolumeAccumulatorAccountEvent {
321                metadata,
322                pubkey: account.pubkey,
323                user_volume_accumulator: PumpFunUserVolumeAccumulator {
324                    user,
325                    needs_claim,
326                    total_unclaimed_tokens,
327                    total_claimed_tokens,
328                    current_sol_volume,
329                    last_update_timestamp,
330                    has_total_claimed_tokens,
331                    cashback_earned,
332                    total_cashback_claimed,
333                    stable_cashback_earned,
334                    total_stable_cashback_claimed,
335                },
336            },
337        ));
338    }
339    if has_discriminator(&account.data, BONDING_CURVE_DISCRIMINATOR) {
340        let data = &account.data[8..];
341        let mut offset = 0usize;
342        let virtual_token_reserves = read_u64_le(data, offset)?;
343        offset += 8;
344        let virtual_quote_reserves = read_u64_le(data, offset)?;
345        offset += 8;
346        let real_token_reserves = read_u64_le(data, offset)?;
347        offset += 8;
348        let real_quote_reserves = read_u64_le(data, offset)?;
349        offset += 8;
350        let token_total_supply = read_u64_le(data, offset)?;
351        offset += 8;
352        let complete = read_u8(data, offset)? != 0;
353        offset += 1;
354        let creator = read_pubkey(data, offset)?;
355        offset += 32;
356        let is_mayhem_mode = read_u8(data, offset)? != 0;
357        offset += 1;
358        let is_cashback_coin = read_u8(data, offset)? != 0;
359        offset += 1;
360        let quote_mint = read_pubkey(data, offset)?;
361
362        return Some(DexEvent::PumpFunBondingCurveAccount(PumpFunBondingCurveAccountEvent {
363            metadata,
364            pubkey: account.pubkey,
365            bonding_curve: PumpFunBondingCurve {
366                virtual_token_reserves,
367                virtual_quote_reserves,
368                real_token_reserves,
369                real_quote_reserves,
370                token_total_supply,
371                complete,
372                creator,
373                is_mayhem_mode,
374                is_cashback_coin,
375                quote_mint,
376            },
377        }));
378    }
379    if !has_discriminator(&account.data, GLOBAL_DISCRIMINATOR) {
380        return None;
381    }
382
383    let data = &account.data[8..];
384    let mut offset = 0usize;
385    let initialized = read_u8(data, offset)? != 0;
386    offset += 1;
387    let authority = read_pubkey(data, offset)?;
388    offset += 32;
389    let fee_recipient = read_pubkey(data, offset)?;
390    offset += 32;
391    let initial_virtual_token_reserves = read_u64_le(data, offset)?;
392    offset += 8;
393    let initial_virtual_sol_reserves = read_u64_le(data, offset)?;
394    offset += 8;
395    let initial_real_token_reserves = read_u64_le(data, offset)?;
396    offset += 8;
397    let token_total_supply = read_u64_le(data, offset)?;
398    offset += 8;
399    let fee_basis_points = read_u64_le(data, offset)?;
400    offset += 8;
401    let withdraw_authority = read_pubkey(data, offset)?;
402    offset += 32;
403    let enable_migrate = read_u8(data, offset)? != 0;
404    offset += 1;
405    let pool_migration_fee = read_u64_le(data, offset)?;
406    offset += 8;
407    let creator_fee_basis_points = read_u64_le(data, offset)?;
408    offset += 8;
409    let mut fee_recipients = [solana_sdk::pubkey::Pubkey::default(); 7];
410    for i in 0..7 {
411        fee_recipients[i] = read_pubkey(data, offset)?;
412        offset += 32;
413    }
414    let set_creator_authority = read_pubkey(data, offset)?;
415    offset += 32;
416    let admin_set_creator_authority = read_pubkey(data, offset)?;
417    offset += 32;
418    let create_v2_enabled = read_u8(data, offset)? != 0;
419    offset += 1;
420    let whitelist_pda = read_pubkey(data, offset)?;
421    offset += 32;
422    let reserved_fee_recipient = read_pubkey(data, offset)?;
423    offset += 32;
424    let mayhem_mode_enabled = read_u8(data, offset)? != 0;
425    offset += 1;
426    let mut reserved_fee_recipients = [solana_sdk::pubkey::Pubkey::default(); 7];
427    for i in 0..7 {
428        reserved_fee_recipients[i] = read_pubkey(data, offset)?;
429        offset += 32;
430    }
431    let is_cashback_enabled = read_u8(data, offset)? != 0;
432    offset += 1;
433    let buyback_fee_recipients = {
434        let mut keys = [solana_sdk::pubkey::Pubkey::default(); 8];
435        for i in 0..8 {
436            keys[i] = read_pubkey(data, offset)?;
437            offset += 32;
438        }
439        keys
440    };
441    let buyback_basis_points = read_u64_le(data, offset)?;
442    offset += 8;
443    let initial_virtual_quote_reserves = read_u64_le(data, offset)?;
444    offset += 8;
445    let whitelisted_quote_mints = {
446        let mut keys = [solana_sdk::pubkey::Pubkey::default(); 1];
447        keys[0] = read_pubkey(data, offset)?;
448        keys
449    };
450
451    let global = PumpFunGlobal {
452        initialized,
453        authority,
454        fee_recipient,
455        initial_virtual_token_reserves,
456        initial_virtual_sol_reserves,
457        initial_real_token_reserves,
458        token_total_supply,
459        fee_basis_points,
460        withdraw_authority,
461        enable_migrate,
462        pool_migration_fee,
463        creator_fee_basis_points,
464        fee_recipients,
465        set_creator_authority,
466        admin_set_creator_authority,
467        create_v2_enabled,
468        whitelist_pda,
469        reserved_fee_recipient,
470        mayhem_mode_enabled,
471        reserved_fee_recipients,
472        is_cashback_enabled,
473        buyback_fee_recipients,
474        buyback_basis_points,
475        initial_virtual_quote_reserves,
476        whitelisted_quote_mints,
477    };
478
479    Some(DexEvent::PumpFunGlobalAccount(PumpFunGlobalAccountEvent {
480        metadata,
481        pubkey: account.pubkey,
482        global,
483    }))
484}
485
486#[cfg(test)]
487mod tests {
488    use super::*;
489    use crate::grpc::{EventType, EventTypeFilter};
490    use solana_sdk::pubkey::Pubkey;
491    use solana_sdk::signature::Signature;
492
493    fn metadata() -> EventMetadata {
494        EventMetadata {
495            signature: Signature::default(),
496            slot: 1,
497            tx_index: 0,
498            block_time_us: 0,
499            grpc_recv_us: 0,
500            recent_blockhash: None,
501        }
502    }
503
504    fn push_pk(out: &mut Vec<u8>, seed: u8) -> Pubkey {
505        let key = Pubkey::new_from_array([seed; 32]);
506        out.extend_from_slice(key.as_ref());
507        key
508    }
509
510    #[test]
511    fn parse_pumpfun_bonding_curve_reads_quote_fields() {
512        let mut data = vec![23, 183, 248, 55, 96, 216, 172, 96];
513        data.extend_from_slice(&100u64.to_le_bytes());
514        data.extend_from_slice(&4_292_000_000u64.to_le_bytes());
515        data.extend_from_slice(&200u64.to_le_bytes());
516        data.extend_from_slice(&3_000_000_000u64.to_le_bytes());
517        data.extend_from_slice(&1_000u64.to_le_bytes());
518        data.push(1);
519        let creator = push_pk(&mut data, 7);
520        data.push(1);
521        data.push(0);
522        let quote_mint = push_pk(&mut data, 8);
523        let account = AccountData {
524            pubkey: Pubkey::new_unique(),
525            executable: false,
526            lamports: 0,
527            owner: crate::grpc::program_ids::PUMPFUN_PROGRAM,
528            rent_epoch: 0,
529            data,
530        };
531        let filter = EventTypeFilter::include_only(vec![EventType::AccountPumpFunBondingCurve]);
532
533        let ev = parse_account_unified(&account, metadata(), Some(&filter)).expect("event");
534
535        match ev {
536            DexEvent::PumpFunBondingCurveAccount(e) => {
537                assert_eq!(e.bonding_curve.virtual_quote_reserves, 4_292_000_000);
538                assert_eq!(e.bonding_curve.real_quote_reserves, 3_000_000_000);
539                assert_eq!(e.bonding_curve.creator, creator);
540                assert_eq!(e.bonding_curve.quote_mint, quote_mint);
541                assert!(e.bonding_curve.complete);
542                assert!(e.bonding_curve.is_mayhem_mode);
543                assert!(!e.bonding_curve.is_cashback_coin);
544            }
545            other => panic!("expected bonding curve account, got {other:?}"),
546        }
547    }
548}