sol-parser-sdk 0.3.1

A lightweight Rust library for real-time event streaming from Solana DEX trading programs. Supports PumpFun, PumpSwap, Bonk, and Raydium protocols with Yellowstone gRPC and ShredStream.
Documentation
//! SPL Token 和 Token-2022 账户解析
//!
//! 提供 Token Account 和 Mint 账户的解析功能,支持:
//! - SPL Token (标准 Token Program)
//! - Token-2022 (Token Extensions Program)
//!
//! ## 性能优化
//! - 零拷贝解析:直接从字节切片读取,避免反序列化开销
//! - 快速路径:优先使用零拷贝,失败时回退到完整解析
//! - 智能检测:根据数据长度和 owner 自动识别账户类型

use crate::core::events::{EventMetadata, TokenAccountEvent, TokenInfoEvent};
use crate::DexEvent;
use solana_sdk::pubkey::Pubkey;
use spl_token::solana_program::program_pack::Pack;
use spl_token::state::{Account, Mint};
use spl_token_2022::{
    extension::StateWithExtensions,
    state::{Account as Account2022, Mint as Mint2022},
};

#[derive(Clone, Debug)]
pub struct AccountData {
    pub pubkey: Pubkey,
    pub executable: bool,
    pub lamports: u64,
    pub owner: Pubkey,
    pub rent_epoch: u64,
    pub data: Vec<u8>,
}

/// 解析 Token 账户(支持 SPL Token 和 Token-2022)
///
/// # 解析策略
/// 1. 优先尝试零拷贝快速解析(Mint 和 Token Account)
/// 2. 如果快速解析失败,回退到完整的 Pack/StateWithExtensions 解析
/// 3. 支持 Token-2022 扩展状态
///
/// # 性能
/// - 快速路径:~50ns(零拷贝)
/// - 完整解析:~200ns(Pack/StateWithExtensions)
pub fn parse_token_account(account: &AccountData, metadata: EventMetadata) -> Option<DexEvent> {
    // 快速路径:尝试零拷贝解析
    if account.data.len() <= 100 {
        if let Some(event) = parse_mint_fast(account, metadata.clone()) {
            return Some(event);
        }
    }

    if let Some(event) = parse_token_fast(account, metadata.clone()) {
        return Some(event);
    }

    // 完整解析路径:支持 Token-2022 扩展
    parse_token_with_extensions(account, metadata)
}

/// 快速解析 Mint 账户(零拷贝)
///
/// 直接从字节切片读取 supply 和 decimals,避免完整反序列化
#[inline]
fn parse_mint_fast(account: &AccountData, metadata: EventMetadata) -> Option<DexEvent> {
    const MINT_SIZE: usize = 82;
    const SUPPLY_OFFSET: usize = 36;
    const DECIMALS_OFFSET: usize = 44;

    if account.data.len() < MINT_SIZE {
        return None;
    }

    let supply_bytes: [u8; 8] = account.data[SUPPLY_OFFSET..SUPPLY_OFFSET + 8].try_into().ok()?;
    let supply = u64::from_le_bytes(supply_bytes);
    let decimals = account.data[DECIMALS_OFFSET];

    let event = TokenInfoEvent {
        metadata,
        pubkey: account.pubkey,
        executable: account.executable,
        lamports: account.lamports,
        owner: account.owner,
        rent_epoch: account.rent_epoch,
        supply,
        decimals,
    };

    Some(DexEvent::TokenInfo(event))
}

/// 快速解析 Token Account(零拷贝)
///
/// 直接从字节切片读取 amount,避免完整反序列化
#[inline]
fn parse_token_fast(account: &AccountData, metadata: EventMetadata) -> Option<DexEvent> {
    const TOKEN_ACCOUNT_SIZE: usize = 165;
    const AMOUNT_OFFSET: usize = 64;

    if account.data.len() < AMOUNT_OFFSET + 8 {
        return None;
    }

    // 只解析标准大小的 Token Account(不包含扩展)
    if account.data.len() != TOKEN_ACCOUNT_SIZE {
        return None;
    }

    let amount_bytes: [u8; 8] = account.data[AMOUNT_OFFSET..AMOUNT_OFFSET + 8].try_into().ok()?;
    let amount = u64::from_le_bytes(amount_bytes);

    let event = TokenAccountEvent {
        metadata,
        pubkey: account.pubkey,
        executable: account.executable,
        lamports: account.lamports,
        owner: account.owner,
        rent_epoch: account.rent_epoch,
        amount: Some(amount),
        token_owner: account.owner,
    };

    Some(DexEvent::TokenAccount(event))
}

/// 完整解析 Token 账户(支持 Token-2022 扩展)
///
/// 使用 Pack 和 StateWithExtensions 进行完整解析
fn parse_token_with_extensions(account: &AccountData, metadata: EventMetadata) -> Option<DexEvent> {
    // 尝试解析为 Token-2022 Mint(带扩展)
    if account.data.len() >= Mint2022::LEN {
        if let Ok(mint_state) = StateWithExtensions::<Mint2022>::unpack(&account.data) {
            let event = TokenInfoEvent {
                metadata,
                pubkey: account.pubkey,
                executable: account.executable,
                lamports: account.lamports,
                owner: account.owner,
                rent_epoch: account.rent_epoch,
                supply: mint_state.base.supply,
                decimals: mint_state.base.decimals,
            };
            return Some(DexEvent::TokenInfo(event));
        }
    }

    // 尝试解析为标准 SPL Token Mint
    if account.data.len() >= Mint::LEN {
        if let Ok(mint) = Mint::unpack_from_slice(&account.data) {
            let event = TokenInfoEvent {
                metadata,
                pubkey: account.pubkey,
                executable: account.executable,
                lamports: account.lamports,
                owner: account.owner,
                rent_epoch: account.rent_epoch,
                supply: mint.supply,
                decimals: mint.decimals,
            };
            return Some(DexEvent::TokenInfo(event));
        }
    }

    // 尝试解析为 Token-2022 Account(带扩展)
    if account.owner.to_bytes() == spl_token_2022::ID.to_bytes() {
        if let Ok(account_state) = StateWithExtensions::<Account2022>::unpack(&account.data) {
            // 转换 spl_token_2022::Pubkey 到 solana_sdk::Pubkey
            let token_owner = Pubkey::new_from_array(account_state.base.owner.to_bytes());
            let event = TokenAccountEvent {
                metadata,
                pubkey: account.pubkey,
                executable: account.executable,
                lamports: account.lamports,
                owner: account.owner,
                rent_epoch: account.rent_epoch,
                amount: Some(account_state.base.amount),
                token_owner,
            };
            return Some(DexEvent::TokenAccount(event));
        }
    }

    // 尝试解析为标准 SPL Token Account
    if let Ok(token_account) = Account::unpack(&account.data) {
        // 转换 spl_token::Pubkey 到 solana_sdk::Pubkey
        let token_owner = Pubkey::new_from_array(token_account.owner.to_bytes());
        let event = TokenAccountEvent {
            metadata,
            pubkey: account.pubkey,
            executable: account.executable,
            lamports: account.lamports,
            owner: account.owner,
            rent_epoch: account.rent_epoch,
            amount: Some(token_account.amount),
            token_owner,
        };
        return Some(DexEvent::TokenAccount(event));
    }

    None
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_parse_mint_fast() {
        // 创建一个模拟的 Mint 账户数据
        let mut data = vec![0u8; 82];
        // 设置 supply (offset 36)
        data[36..44].copy_from_slice(&1000000u64.to_le_bytes());
        // 设置 decimals (offset 44)
        data[44] = 6;

        let account = AccountData {
            pubkey: Pubkey::new_unique(),
            executable: false,
            lamports: 1000000,
            owner: Pubkey::new_from_array(spl_token::ID.to_bytes()),
            rent_epoch: 0,
            data,
        };

        let metadata = EventMetadata::default();
        let event = parse_mint_fast(&account, metadata);

        assert!(event.is_some());
        if let Some(DexEvent::TokenInfo(info)) = event {
            assert_eq!(info.supply, 1000000);
            assert_eq!(info.decimals, 6);
        }
    }

    #[test]
    fn test_parse_token_fast() {
        // 创建一个模拟的 Token Account 数据
        let mut data = vec![0u8; 165];
        // 设置 amount (offset 64)
        data[64..72].copy_from_slice(&5000u64.to_le_bytes());

        let account = AccountData {
            pubkey: Pubkey::new_unique(),
            executable: false,
            lamports: 2039280,
            owner: Pubkey::new_from_array(spl_token::ID.to_bytes()),
            rent_epoch: 0,
            data,
        };

        let metadata = EventMetadata::default();
        let event = parse_token_fast(&account, metadata);

        assert!(event.is_some());
        if let Some(DexEvent::TokenAccount(token_account)) = event {
            assert_eq!(token_account.amount, Some(5000));
        }
    }
}