sol-parser-sdk 0.6.4

A lightweight Rust library for real-time event streaming from Solana DEX trading programs. Supports PumpFun, PumpSwap, Raydium LaunchLab, and Raydium protocols with Yellowstone gRPC and ShredStream.
Documentation
use sol_parser_sdk::{
    parse_rpc_transaction_cost, parse_transaction_from_rpc, DexEvent, SwqosProvider,
};
use solana_client::rpc_client::RpcClient;
use solana_client::rpc_config::RpcTransactionConfig;
use solana_sdk::signature::Signature;
use solana_transaction_status::UiTransactionEncoding;
use std::str::FromStr;

fn run_mainnet_tests() -> bool {
    std::env::var("RUN_MAINNET_TESTS").as_deref() == Ok("1")
}

fn rpc_client() -> RpcClient {
    RpcClient::new(
        std::env::var("SOLANA_RPC_URL")
            .unwrap_or_else(|_| "https://api.mainnet-beta.solana.com".to_string()),
    )
}

fn parse(signature: &str) -> Vec<DexEvent> {
    let signature = Signature::from_str(signature).expect("valid fixture signature");
    parse_transaction_from_rpc(&rpc_client(), &signature, None)
        .unwrap_or_else(|error| panic!("{signature}: {error}"))
}

fn fetch(signature: &str) -> solana_transaction_status::EncodedConfirmedTransactionWithStatusMeta {
    let signature = Signature::from_str(signature).expect("valid fixture signature");
    rpc_client()
        .get_transaction_with_config(
            &signature,
            RpcTransactionConfig {
                encoding: Some(UiTransactionEncoding::Base64),
                commitment: None,
                max_supported_transaction_version: Some(0),
            },
        )
        .unwrap_or_else(|error| panic!("{signature}: {error}"))
}

// These transactions were captured from current mainnet traffic on 2026-08-13.
// Run with: RUN_MAINNET_TESTS=1 SOLANA_RPC_URL=<optional archive RPC> cargo test --test current_mainnet_transactions

#[test]
fn current_meteora_dlmm_swap_and_nested_orca() {
    if !run_mainnet_tests() {
        return;
    }
    const SIGNATURE: &str =
        "eEWaGsbRPoiD36Xf3epzSMmdtXX36va76b13YfsDV3ncsxQHBTjC68zZ8mbzFXTNWy3n3qKUAHjgHBconX4Gu1i";
    let events = parse(SIGNATURE);
    let swaps: Vec<_> = events
        .iter()
        .filter_map(|event| match event {
            DexEvent::MeteoraDlmmSwap(event) => Some(event),
            _ => None,
        })
        .collect();
    assert_eq!(swaps.len(), 1);
    assert_eq!(swaps[0].metadata.signature.to_string(), SIGNATURE);
    assert_eq!(swaps[0].metadata.slot, 438_873_646);
    assert_eq!(swaps[0].amount_in, 2_738_183_783);
    assert_eq!(swaps[0].amount_out, 81_555_062);
    assert_eq!(swaps[0].fee, 18_486_656);
    assert_eq!(swaps[0].protocol_fee, 2_054_072);

    let orca: Vec<_> = events
        .iter()
        .filter_map(|event| match event {
            DexEvent::OrcaWhirlpoolSwap(event) => Some(event),
            _ => None,
        })
        .collect();
    assert_eq!(orca.len(), 1);
    assert_eq!(orca[0].input_amount, 2_397_194_654);
    assert_eq!(orca[0].output_amount, 942_951_733);
}

#[test]
fn current_meteora_dlmm_add_liquidity_occurrences() {
    if !run_mainnet_tests() {
        return;
    }
    const SIGNATURE: &str =
        "h3sGiriW4jCGgbnNF8DaEKnsWhjtcH5ZM1dkyZFidgD2fx9aDNatray38yRmkxaWez3g5qFNpyXhE8ho716vjgp";
    let adds: Vec<_> = parse(SIGNATURE)
        .into_iter()
        .filter_map(|event| match event {
            DexEvent::MeteoraDlmmAddLiquidity(event) => Some(event),
            _ => None,
        })
        .collect();
    assert_eq!(adds.len(), 3);
    assert_eq!(adds[0].metadata.slot, 438_873_652);
    assert_eq!(adds[0].amounts, [0, 389_400_592]);
    assert_eq!(adds[1].amounts, [4_957_546_677, 65_633_812]);
    assert_eq!(adds[2].amounts, [6_984_260_460, 0]);
}

#[test]
fn current_pumpfun_and_pumpswap_trades() {
    if !run_mainnet_tests() {
        return;
    }
    const PUMPFUN_SIGNATURE: &str =
        "QUYUtVPVkkjV2GGFTC4MfxtauNpRvWDViGCGxTckxPWbPZvEY92d1sZD9Lq5iK31sy3Drwy28gHmV89iRt9hz9R";
    let pumpfun: Vec<_> = parse(PUMPFUN_SIGNATURE)
        .into_iter()
        .filter_map(|event| match event {
            DexEvent::PumpFunBuy(event) => Some(event),
            _ => None,
        })
        .collect();
    assert_eq!(pumpfun.len(), 1);
    assert_eq!(pumpfun[0].metadata.slot, 438_880_952);
    assert_eq!(pumpfun[0].sol_amount, 977_777_777);
    assert_eq!(pumpfun[0].token_amount, 30_765_521_374_696);
    assert_eq!(pumpfun[0].ix_name, "buy");

    const PUMPSWAP_SIGNATURE: &str =
        "2qgqjVi7XtBeSudSkZhbQrsdFNeMUB4jApLdcdihAwcWWb5SxQvQKjrfr2ZQ12TrR4BEwvaY7PiHLqE3uZD24iw7";
    let pumpswap: Vec<_> = parse(PUMPSWAP_SIGNATURE)
        .into_iter()
        .filter_map(|event| match event {
            DexEvent::PumpSwapBuy(event) => Some(event),
            _ => None,
        })
        .collect();
    assert_eq!(pumpswap.len(), 1);
    assert_eq!(pumpswap[0].metadata.slot, 438_881_023);
    assert_eq!(pumpswap[0].base_amount_out, 7_317_003_080);
    assert_eq!(pumpswap[0].quote_amount_in, 10_000_000);
    assert_eq!(pumpswap[0].ix_name, "buy_exact_quote_in");
}

#[test]
fn current_raydium_clmm_cpmm_amm_v4_and_launchlab() {
    if !run_mainnet_tests() {
        return;
    }
    const CLMM_SIGNATURE: &str =
        "2TyjCWrh3zqNmDg7NgdGAFqGaCbEkUHE8zrjzsRyqRVTXYZNKFFJgFEDce5mD4se3h2u6GyNJLXbeAW1ad8dsApq";
    let clmm: Vec<_> = parse(CLMM_SIGNATURE)
        .into_iter()
        .filter_map(|event| match event {
            DexEvent::RaydiumClmmSwap(event) => Some(event),
            _ => None,
        })
        .collect();
    assert_eq!(clmm.len(), 1);
    assert_eq!(clmm[0].metadata.slot, 438_880_315);
    assert_eq!((clmm[0].amount_0, clmm[0].amount_1), (156_679, 11_888));

    const CPMM_SIGNATURE: &str =
        "4v27ccyrAgpCdCHLvjvn8smFn4Fb4HGcRVTSt952eNcF5jg5niA5bKRLPoGrzxXZdZULEZujgA5TXdESNbwmFYE8";
    let cpmm: Vec<_> = parse(CPMM_SIGNATURE)
        .into_iter()
        .filter_map(|event| match event {
            DexEvent::RaydiumCpmmSwap(event) => Some(event),
            _ => None,
        })
        .collect();
    assert_eq!(cpmm.len(), 3);
    assert_eq!(cpmm[0].metadata.slot, 438_881_024);
    assert_eq!((cpmm[0].input_amount, cpmm[0].output_amount), (851_111, 3_788_666));
    assert_eq!((cpmm[1].input_amount, cpmm[1].output_amount), (636_739, 1_163_813_842));
    assert_eq!((cpmm[2].input_amount, cpmm[2].output_amount), (1_163_813_842, 2_843_080));

    const AMM_V4_SIGNATURE: &str =
        "2iHYs4AHC5nutcbBxpA5aptBYTGaDUYBgamohfetDnAPiPBW5NkguxgnjVF5886Jy8MZ19UXdeZyPKq9C5wqAki4";
    let amm_v4: Vec<_> = parse(AMM_V4_SIGNATURE)
        .into_iter()
        .filter_map(|event| match event {
            DexEvent::RaydiumAmmV4Swap(event) => Some(event),
            _ => None,
        })
        .collect();
    assert_eq!(amm_v4.len(), 1);
    assert_eq!(amm_v4[0].metadata.slot, 438_881_026);
    assert_eq!(amm_v4[0].amount_in, 28_804_156_949_609);
    assert_eq!(amm_v4[0].amount_out, 428_715_251);

    const LAUNCHLAB_SIGNATURE: &str =
        "4pSXdZEdL3oFCcbccroG7GkV4oEVtbygS2pBVP28chfNETEN8yqE3q6gMws4F2ZsbfE8rDGEbgBqTnv5xahH5RFT";
    let launchlab: Vec<_> = parse(LAUNCHLAB_SIGNATURE)
        .into_iter()
        .filter_map(|event| match event {
            DexEvent::RaydiumLaunchlabTrade(event) => Some(event),
            _ => None,
        })
        .collect();
    assert_eq!(launchlab.len(), 1);
    assert_eq!(launchlab[0].metadata.slot, 438_880_206);
    assert_eq!(launchlab[0].amount_in, 511_580_573);
    assert_eq!(launchlab[0].amount_out, 5_169_841_048_834);
}

#[test]
fn current_raydium_launchlab_usd1_trade_exposes_quote_context() {
    if !run_mainnet_tests() {
        return;
    }
    const SIGNATURE: &str =
        "zuaKyxjpM7G5et2XqZofjjGNczNduGs6g8ipCEeZKKV7h6FFgRNJbXnzfufSZWD3bEacmf8sVktXpZaadQhmVuJ";
    const USD1_MINT: &str = "USD1ttGY1N17NEEHLmELoaybftRBUSErhqYiQzvEmuB";
    const USD1_GLOBAL_CONFIG: &str = "EPiZbnrThjyLnoQ6QQzkxeFqyL5uyg9RzNHHAudUPxBz";

    let trades: Vec<_> = parse(SIGNATURE)
        .into_iter()
        .filter_map(|event| match event {
            DexEvent::RaydiumLaunchlabTrade(event) => Some(event),
            _ => None,
        })
        .collect();

    assert_eq!(trades.len(), 1);
    assert_eq!(trades[0].metadata.slot, 438_894_516);
    assert_eq!(trades[0].quote_mint.to_string(), USD1_MINT);
    assert_eq!(trades[0].global_config.to_string(), USD1_GLOBAL_CONFIG);
}

#[test]
fn current_transaction_cost_with_jito_tip() {
    if !run_mainnet_tests() {
        return;
    }
    const SIGNATURE: &str =
        "4yaaD6ywu8epxVTvZEDAGPhdKK2V73XqvLqQWm1KbSFQ1uTk2nnC4uW7xTrpSuQYpTivmDQQawu7x3dFbYC1KuZ6";
    const TIP_RECIPIENT: &str = "96gYZGLnJYVFmbjzopPSU6QiEV5fGqZNyN9nmNhvrZU5";

    let transaction = fetch(SIGNATURE);
    let cost = parse_rpc_transaction_cost(&transaction).expect("parse current transaction cost");

    assert_eq!(transaction.slot, 438_900_232);
    assert_eq!(cost.transaction_fee_lamports, Some(29_242));
    assert_eq!(cost.compute_units_consumed, Some(135_026));
    assert_eq!(cost.compute_unit_limit, Some(300_000));
    assert_eq!(cost.compute_unit_price_micro_lamports, Some(80_805));
    assert_eq!(cost.priority_fee_lamports, Some(24_242));
    assert_eq!(cost.tip_lamports, 137_273);
    assert_eq!(cost.total_fee_and_tip_lamports, Some(166_515));
    assert_eq!(cost.tip_payments.len(), 1);
    assert_eq!(cost.tip_payments[0].provider, SwqosProvider::Jito);
    assert_eq!(cost.tip_payments[0].recipient.to_string(), TIP_RECIPIENT);
    assert_eq!(cost.tip_lamports_for(SwqosProvider::Jito), 137_273);
}