parser-proxy-ws 0.1.1

High-performance Solana DEX event parsing and WebSocket streaming library
Documentation
use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
use std::fs;
use std::path::Path;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Config {
    pub server: ServerConfig,
    pub grpc: GrpcConfig,
    pub protocols: ProtocolsConfig,
    pub events: EventsConfig,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerConfig {
    pub host: String,
    pub port: u16,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GrpcConfig {
    pub endpoint: String,
    pub token: Option<String>,
    pub enable_metrics: bool,
    pub enable_tls: bool,
    pub connection_timeout_ms: u64,
    pub request_timeout_ms: u64,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProtocolsConfig {
    pub pumpfun: bool,
    pub pumpswap: bool,
    pub bonk: bool,
    pub raydium_amm_v4: bool,
    pub raydium_clmm: bool,
    pub raydium_cpmm: bool,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EventsConfig {
    pub block_meta: bool,
    pub bonk_trade: bool,
    pub bonk_pool_create: bool,
    pub bonk_migrate_amm: bool,
    pub pumpfun_trade: bool,
    pub pumpfun_create: bool,
    pub pumpfun_complete: bool,
    pub pumpfun_migrate: bool,
    pub pumpswap_buy: bool,
    pub pumpswap_sell: bool,
    pub pumpswap_create_pool: bool,
    pub pumpswap_pool_created: bool,
    pub pumpswap_trade: bool,
    pub pumpswap_liquidity_added: bool,
    pub pumpswap_liquidity_removed: bool,
    pub pumpswap_pool_updated: bool,
    pub pumpswap_fees_claimed: bool,
    pub raydium_cpmm_swap: bool,
    pub raydium_cpmm_deposit: bool,
    pub raydium_cpmm_withdraw: bool,
    pub raydium_cpmm_initialize: bool,
    pub raydium_clmm_swap: bool,
    pub raydium_clmm_create_pool: bool,
    pub raydium_clmm_open_position: bool,
    pub raydium_clmm_close_position: bool,
    pub raydium_clmm_increase_liquidity: bool,
    pub raydium_clmm_decrease_liquidity: bool,
    pub raydium_clmm_open_position_with_token_ext_nft: bool,
    pub raydium_clmm_collect_fee: bool,
    pub raydium_amm_v4_swap: bool,
    pub raydium_amm_v4_deposit: bool,
    pub raydium_amm_v4_withdraw: bool,
    pub raydium_amm_v4_initialize2: bool,
    pub raydium_amm_v4_withdraw_pnl: bool,
    pub orca_whirlpool_swap: bool,
    pub orca_whirlpool_liquidity_increased: bool,
    pub orca_whirlpool_liquidity_decreased: bool,
    pub orca_whirlpool_pool_initialized: bool,
    pub meteora_pools_swap: bool,
    pub meteora_pools_add_liquidity: bool,
    pub meteora_pools_remove_liquidity: bool,
    pub meteora_pools_bootstrap_liquidity: bool,
    pub meteora_pools_pool_created: bool,
    pub meteora_pools_set_pool_fees: bool,
    pub meteora_damm_v2_swap: bool,
    pub meteora_damm_v2_add_liquidity: bool,
    pub meteora_damm_v2_remove_liquidity: bool,
    pub meteora_damm_v2_initialize_pool: bool,
    pub meteora_damm_v2_create_position: bool,
    pub meteora_damm_v2_close_position: bool,
    pub meteora_damm_v2_claim_position_fee: bool,
    pub meteora_damm_v2_initialize_reward: bool,
    pub meteora_damm_v2_fund_reward: bool,
    pub meteora_damm_v2_claim_reward: bool,
    pub token_account: bool,
    pub nonce_account: bool,
    pub token_info: bool,
}

impl Default for Config {
    fn default() -> Self {
        Self {
            server: ServerConfig {
                host: "127.0.0.1".to_string(),
                port: 9001,
            },
            grpc: GrpcConfig {
                endpoint: "https://solana-yellowstone-grpc.publicnode.com:443".to_string(),
                token: None,
                enable_metrics: true,
                enable_tls: true,
                connection_timeout_ms: 10000,
                request_timeout_ms: 30000,
            },
            protocols: ProtocolsConfig {
                pumpfun: true,
                pumpswap: false,
                bonk: false,
                raydium_amm_v4: false,
                raydium_clmm: false,
                raydium_cpmm: false,
            },
            events: EventsConfig {
                block_meta: false,
                bonk_trade: false,
                bonk_pool_create: false,
                bonk_migrate_amm: false,
                pumpfun_trade: true,
                pumpfun_create: true,
                pumpfun_complete: false,
                pumpfun_migrate: false,
                pumpswap_buy: false,
                pumpswap_sell: false,
                pumpswap_create_pool: false,
                pumpswap_pool_created: false,
                pumpswap_trade: false,
                pumpswap_liquidity_added: false,
                pumpswap_liquidity_removed: false,
                pumpswap_pool_updated: false,
                pumpswap_fees_claimed: false,
                raydium_cpmm_swap: false,
                raydium_cpmm_deposit: false,
                raydium_cpmm_withdraw: false,
                raydium_cpmm_initialize: false,
                raydium_clmm_swap: false,
                raydium_clmm_create_pool: false,
                raydium_clmm_open_position: false,
                raydium_clmm_close_position: false,
                raydium_clmm_increase_liquidity: false,
                raydium_clmm_decrease_liquidity: false,
                raydium_clmm_open_position_with_token_ext_nft: false,
                raydium_clmm_collect_fee: false,
                raydium_amm_v4_swap: false,
                raydium_amm_v4_deposit: false,
                raydium_amm_v4_withdraw: false,
                raydium_amm_v4_initialize2: false,
                raydium_amm_v4_withdraw_pnl: false,
                orca_whirlpool_swap: false,
                orca_whirlpool_liquidity_increased: false,
                orca_whirlpool_liquidity_decreased: false,
                orca_whirlpool_pool_initialized: false,
                meteora_pools_swap: false,
                meteora_pools_add_liquidity: false,
                meteora_pools_remove_liquidity: false,
                meteora_pools_bootstrap_liquidity: false,
                meteora_pools_pool_created: false,
                meteora_pools_set_pool_fees: false,
                meteora_damm_v2_swap: false,
                meteora_damm_v2_add_liquidity: false,
                meteora_damm_v2_remove_liquidity: false,
                meteora_damm_v2_initialize_pool: false,
                meteora_damm_v2_create_position: false,
                meteora_damm_v2_close_position: false,
                meteora_damm_v2_claim_position_fee: false,
                meteora_damm_v2_initialize_reward: false,
                meteora_damm_v2_fund_reward: false,
                meteora_damm_v2_claim_reward: false,
                token_account: false,
                nonce_account: false,
                token_info: false,
            },
        }
    }
}

impl Config {
    pub fn load_from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
        let content = fs::read_to_string(&path)
            .with_context(|| format!("Failed to read config file: {:?}", path.as_ref()))?;

        let config: Config = toml::from_str(&content)
            .with_context(|| format!("Failed to parse config file: {:?}", path.as_ref()))?;

        Ok(config)
    }

    pub fn load_or_default<P: AsRef<Path>>(path: P) -> Self {
        match Self::load_from_file(&path) {
            Ok(config) => {
                tracing::info!("✅ Loaded config from: {:?}", path.as_ref());
                config
            }
            Err(e) => {
                tracing::warn!("⚠️  Failed to load config: {}, using default", e);
                Self::default()
            }
        }
    }

    pub fn save_to_file<P: AsRef<Path>>(&self, path: P) -> Result<()> {
        let content = toml::to_string_pretty(self)
            .context("Failed to serialize config to TOML")?;

        fs::write(&path, content)
            .with_context(|| format!("Failed to write config file: {:?}", path.as_ref()))?;

        Ok(())
    }

    pub fn get_enabled_protocols(&self) -> Vec<sol_parser_sdk::grpc::Protocol> {
        use sol_parser_sdk::grpc::Protocol;
        let mut protocols = Vec::new();

        if self.protocols.pumpfun {
            protocols.push(Protocol::PumpFun);
        }
        if self.protocols.pumpswap {
            protocols.push(Protocol::PumpSwap);
        }
        if self.protocols.bonk {
            protocols.push(Protocol::Bonk);
        }
        if self.protocols.raydium_amm_v4 {
            protocols.push(Protocol::RaydiumAmmV4);
        }
        if self.protocols.raydium_clmm {
            protocols.push(Protocol::RaydiumClmm);
        }
        if self.protocols.raydium_cpmm {
            protocols.push(Protocol::RaydiumCpmm);
        }

        protocols
    }

    pub fn get_enabled_event_types(&self) -> Vec<sol_parser_sdk::grpc::EventType> {
        use sol_parser_sdk::grpc::EventType;
        let mut event_types = Vec::new();

        if self.events.block_meta { event_types.push(EventType::BlockMeta); }
        if self.events.bonk_trade { event_types.push(EventType::BonkTrade); }
        if self.events.bonk_pool_create { event_types.push(EventType::BonkPoolCreate); }
        if self.events.bonk_migrate_amm { event_types.push(EventType::BonkMigrateAmm); }
        if self.events.pumpfun_trade { event_types.push(EventType::PumpFunTrade); }
        if self.events.pumpfun_create { event_types.push(EventType::PumpFunCreate); }
        if self.events.pumpfun_complete { event_types.push(EventType::PumpFunComplete); }
        if self.events.pumpfun_migrate { event_types.push(EventType::PumpFunMigrate); }
        if self.events.pumpswap_buy { event_types.push(EventType::PumpSwapBuy); }
        if self.events.pumpswap_sell { event_types.push(EventType::PumpSwapSell); }
        if self.events.pumpswap_create_pool { event_types.push(EventType::PumpSwapCreatePool); }
        if self.events.pumpswap_pool_created { event_types.push(EventType::PumpSwapPoolCreated); }
        if self.events.pumpswap_trade { event_types.push(EventType::PumpSwapTrade); }
        if self.events.pumpswap_liquidity_added { event_types.push(EventType::PumpSwapLiquidityAdded); }
        if self.events.pumpswap_liquidity_removed { event_types.push(EventType::PumpSwapLiquidityRemoved); }
        if self.events.pumpswap_pool_updated { event_types.push(EventType::PumpSwapPoolUpdated); }
        if self.events.pumpswap_fees_claimed { event_types.push(EventType::PumpSwapFeesClaimed); }
        if self.events.raydium_cpmm_swap { event_types.push(EventType::RaydiumCpmmSwap); }
        if self.events.raydium_cpmm_deposit { event_types.push(EventType::RaydiumCpmmDeposit); }
        if self.events.raydium_cpmm_withdraw { event_types.push(EventType::RaydiumCpmmWithdraw); }
        if self.events.raydium_cpmm_initialize { event_types.push(EventType::RaydiumCpmmInitialize); }
        if self.events.raydium_clmm_swap { event_types.push(EventType::RaydiumClmmSwap); }
        if self.events.raydium_clmm_create_pool { event_types.push(EventType::RaydiumClmmCreatePool); }
        if self.events.raydium_clmm_open_position { event_types.push(EventType::RaydiumClmmOpenPosition); }
        if self.events.raydium_clmm_close_position { event_types.push(EventType::RaydiumClmmClosePosition); }
        if self.events.raydium_clmm_increase_liquidity { event_types.push(EventType::RaydiumClmmIncreaseLiquidity); }
        if self.events.raydium_clmm_decrease_liquidity { event_types.push(EventType::RaydiumClmmDecreaseLiquidity); }
        if self.events.raydium_clmm_open_position_with_token_ext_nft { event_types.push(EventType::RaydiumClmmOpenPositionWithTokenExtNft); }
        if self.events.raydium_clmm_collect_fee { event_types.push(EventType::RaydiumClmmCollectFee); }
        if self.events.raydium_amm_v4_swap { event_types.push(EventType::RaydiumAmmV4Swap); }
        if self.events.raydium_amm_v4_deposit { event_types.push(EventType::RaydiumAmmV4Deposit); }
        if self.events.raydium_amm_v4_withdraw { event_types.push(EventType::RaydiumAmmV4Withdraw); }
        if self.events.raydium_amm_v4_initialize2 { event_types.push(EventType::RaydiumAmmV4Initialize2); }
        if self.events.raydium_amm_v4_withdraw_pnl { event_types.push(EventType::RaydiumAmmV4WithdrawPnl); }
        if self.events.orca_whirlpool_swap { event_types.push(EventType::OrcaWhirlpoolSwap); }
        if self.events.orca_whirlpool_liquidity_increased { event_types.push(EventType::OrcaWhirlpoolLiquidityIncreased); }
        if self.events.orca_whirlpool_liquidity_decreased { event_types.push(EventType::OrcaWhirlpoolLiquidityDecreased); }
        if self.events.orca_whirlpool_pool_initialized { event_types.push(EventType::OrcaWhirlpoolPoolInitialized); }
        if self.events.meteora_pools_swap { event_types.push(EventType::MeteoraPoolsSwap); }
        if self.events.meteora_pools_add_liquidity { event_types.push(EventType::MeteoraPoolsAddLiquidity); }
        if self.events.meteora_pools_remove_liquidity { event_types.push(EventType::MeteoraPoolsRemoveLiquidity); }
        if self.events.meteora_pools_bootstrap_liquidity { event_types.push(EventType::MeteoraPoolsBootstrapLiquidity); }
        if self.events.meteora_pools_pool_created { event_types.push(EventType::MeteoraPoolsPoolCreated); }
        if self.events.meteora_pools_set_pool_fees { event_types.push(EventType::MeteoraPoolsSetPoolFees); }
        if self.events.meteora_damm_v2_swap { event_types.push(EventType::MeteoraDammV2Swap); }
        if self.events.meteora_damm_v2_add_liquidity { event_types.push(EventType::MeteoraDammV2AddLiquidity); }
        if self.events.meteora_damm_v2_remove_liquidity { event_types.push(EventType::MeteoraDammV2RemoveLiquidity); }
        if self.events.meteora_damm_v2_initialize_pool { event_types.push(EventType::MeteoraDammV2InitializePool); }
        if self.events.meteora_damm_v2_create_position { event_types.push(EventType::MeteoraDammV2CreatePosition); }
        if self.events.meteora_damm_v2_close_position { event_types.push(EventType::MeteoraDammV2ClosePosition); }
        if self.events.meteora_damm_v2_claim_position_fee { event_types.push(EventType::MeteoraDammV2ClaimPositionFee); }
        if self.events.meteora_damm_v2_initialize_reward { event_types.push(EventType::MeteoraDammV2InitializeReward); }
        if self.events.meteora_damm_v2_fund_reward { event_types.push(EventType::MeteoraDammV2FundReward); }
        if self.events.meteora_damm_v2_claim_reward { event_types.push(EventType::MeteoraDammV2ClaimReward); }
        if self.events.token_account { event_types.push(EventType::TokenAccount); }
        if self.events.nonce_account { event_types.push(EventType::NonceAccount); }
        if self.events.token_info { event_types.push(EventType::TokenInfo); }

        event_types
    }
}