From 8c5eb7c214ea03ca44ba33ce399c7bb5b7366694 Mon Sep 17 00:00:00 2001 From: 0xfnzero <0xfnzero@users.noreply.github.com> Date: Thu, 13 Aug 2026 08:13:44 +0800 Subject: [PATCH 1/2] fix: expose LaunchLab USD1 account context --- Cargo.toml | 2 +- .../event_parser/core/merger_event.rs | 52 ++++++++ .../event_parser/protocols/bonk/events.rs | 6 + src/streaming/grpc/subscription.rs | 28 +++++ .../parser_sdk_bridge/bonk_accounts.rs | 115 +++++++++++++++++- src/streaming/parser_sdk_bridge/mod.rs | 20 +++ tests/current_mainnet_transactions.rs | 87 +++++++++++++ 7 files changed, 305 insertions(+), 5 deletions(-) create mode 100644 tests/current_mainnet_transactions.rs diff --git a/Cargo.toml b/Cargo.toml index 7f6ddd4..acf6fb9 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ sdk-perf-stats = ["sol-parser-sdk/perf-stats"] sdk-ultra-perf = ["sol-parser-sdk/ultra-perf"] [dependencies] -sol-parser-sdk = { version = "0.6.2", default-features = false } +sol-parser-sdk = { git = "https://github.com/0xfnzero/sol-parser-sdk.git", rev = "25a494ae33783ceaaa74bd914471fd49af6edbd9", default-features = false } solana-sdk = "3.0.0" solana-client = "3.1.12" solana-transaction-status = "3.1.12" diff --git a/src/streaming/event_parser/core/merger_event.rs b/src/streaming/event_parser/core/merger_event.rs index c88530c..18591ff 100644 --- a/src/streaming/event_parser/core/merger_event.rs +++ b/src/streaming/event_parser/core/merger_event.rs @@ -297,6 +297,17 @@ pub fn merge(instruction_event: &mut DexEvent, cpi_log_event: DexEvent) { e.trade_direction = cpie.trade_direction; e.pool_status = cpie.pool_status; e.exact_in = cpie.exact_in; + fill_pubkey(&mut e.payer, cpie.payer); + fill_pubkey(&mut e.global_config, cpie.global_config); + fill_pubkey(&mut e.platform_config, cpie.platform_config); + fill_pubkey(&mut e.user_base_token, cpie.user_base_token); + fill_pubkey(&mut e.user_quote_token, cpie.user_quote_token); + fill_pubkey(&mut e.base_vault, cpie.base_vault); + fill_pubkey(&mut e.quote_vault, cpie.quote_vault); + fill_pubkey(&mut e.base_token_mint, cpie.base_token_mint); + fill_pubkey(&mut e.quote_token_mint, cpie.quote_token_mint); + fill_pubkey(&mut e.base_token_program, cpie.base_token_program); + fill_pubkey(&mut e.quote_token_program, cpie.quote_token_program); } _ => {} }, @@ -309,6 +320,15 @@ pub fn merge(instruction_event: &mut DexEvent, cpi_log_event: DexEvent) { e.curve_param = cpie.curve_param; e.vesting_param = cpie.vesting_param; e.amm_fee_on = cpie.amm_fee_on; + fill_pubkey(&mut e.payer, cpie.payer); + fill_pubkey(&mut e.base_mint, cpie.base_mint); + fill_pubkey(&mut e.quote_mint, cpie.quote_mint); + fill_pubkey(&mut e.base_vault, cpie.base_vault); + fill_pubkey(&mut e.quote_vault, cpie.quote_vault); + fill_pubkey(&mut e.global_config, cpie.global_config); + fill_pubkey(&mut e.platform_config, cpie.platform_config); + fill_pubkey(&mut e.base_token_program, cpie.base_token_program); + fill_pubkey(&mut e.quote_token_program, cpie.quote_token_program); } _ => {} }, @@ -899,6 +919,7 @@ pub fn merge(instruction_event: &mut DexEvent, cpi_log_event: DexEvent) { #[cfg(test)] mod tests { use super::*; + use crate::streaming::event_parser::protocols::bonk::events::BonkTradeEvent; use crate::streaming::event_parser::protocols::pumpfun::events::{ PumpFeesShareholder, PumpFunTradeEvent, }; @@ -957,6 +978,37 @@ mod tests { } } + #[test] + fn launchlab_merge_preserves_instruction_context_and_fills_missing_accounts() { + let quote_mint = Pubkey::new_unique(); + let cpi_quote_mint = Pubkey::new_unique(); + let global_config = Pubkey::new_unique(); + let quote_token_program = Pubkey::new_unique(); + let mut instruction_event = DexEvent::BonkTradeEvent(BonkTradeEvent { + quote_token_mint: quote_mint, + ..Default::default() + }); + let cpi_event = DexEvent::BonkTradeEvent(BonkTradeEvent { + amount_in: 10, + amount_out: 20, + global_config, + quote_token_mint: cpi_quote_mint, + quote_token_program, + ..Default::default() + }); + + merge(&mut instruction_event, cpi_event); + + let DexEvent::BonkTradeEvent(event) = instruction_event else { + panic!("expected BonkTradeEvent"); + }; + assert_eq!(event.amount_in, 10); + assert_eq!(event.amount_out, 20); + assert_eq!(event.global_config, global_config); + assert_eq!(event.quote_token_mint, quote_mint); + assert_eq!(event.quote_token_program, quote_token_program); + } + #[test] fn pumpswap_merge_preserves_signed_virtual_quote_reserves() { let mut instruction_event = DexEvent::PumpSwapSellEvent(PumpSwapSellEvent::default()); diff --git a/src/streaming/event_parser/protocols/bonk/events.rs b/src/streaming/event_parser/protocols/bonk/events.rs index 9ab98e1..9abbba8 100755 --- a/src/streaming/event_parser/protocols/bonk/events.rs +++ b/src/streaming/event_parser/protocols/bonk/events.rs @@ -99,6 +99,12 @@ pub struct BonkPoolCreateEvent { pub global_config: Pubkey, #[borsh(skip)] pub platform_config: Pubkey, + #[borsh(skip)] + #[serde(default)] + pub base_token_program: Pubkey, + #[borsh(skip)] + #[serde(default)] + pub quote_token_program: Pubkey, } pub const BONK_POOL_CREATE_EVENT_LOG_SIZE: usize = 256; diff --git a/src/streaming/grpc/subscription.rs b/src/streaming/grpc/subscription.rs index 840a56c..d542684 100644 --- a/src/streaming/grpc/subscription.rs +++ b/src/streaming/grpc/subscription.rs @@ -134,3 +134,31 @@ impl SubscriptionManager { &self.config } } + +#[cfg(test)] +mod tests { + use super::*; + use sol_parser_sdk::instr::program_ids::METEORA_DLMM_PROGRAM_ID; + + #[test] + fn meteora_dlmm_program_include_is_forwarded_unchanged() { + let manager = SubscriptionManager::new( + "https://localhost".to_string(), + None, + ClientConfig::default(), + ); + let program = METEORA_DLMM_PROGRAM_ID.to_string(); + let filters = manager + .get_subscribe_request_filter( + vec![TransactionFilter { + account_include: vec![program.clone()], + account_exclude: Vec::new(), + account_required: Vec::new(), + }], + None, + ) + .expect("transaction filter"); + + assert_eq!(filters["transaction_0"].account_include, vec![program]); + } +} diff --git a/src/streaming/parser_sdk_bridge/bonk_accounts.rs b/src/streaming/parser_sdk_bridge/bonk_accounts.rs index 9db7cc5..23eb85f 100644 --- a/src/streaming/parser_sdk_bridge/bonk_accounts.rs +++ b/src/streaming/parser_sdk_bridge/bonk_accounts.rs @@ -16,8 +16,6 @@ use crate::streaming::event_parser::protocols::pumpswap::types::{GlobalConfig, P use sol_parser_sdk::core::events::{ RaydiumLaunchlabTradeEvent as PbBonkTrade, TradeDirection as PbBonkTradeDirection, }; -use solana_sdk::pubkey::Pubkey; - #[inline] pub(crate) fn pb_bonk_trade_direction(d: PbBonkTradeDirection) -> BonkTradeDirection { match d { @@ -66,6 +64,16 @@ pub(crate) fn bonk_trade_from_parser( pool_status: PoolStatus::Trade, exact_in: b.exact_in, payer: b.user, + global_config: b.global_config, + platform_config: b.platform_config, + user_base_token: b.user_base_token, + user_quote_token: b.user_quote_token, + base_vault: b.base_vault, + quote_vault: b.quote_vault, + base_token_mint: b.base_mint, + quote_token_mint: b.quote_mint, + base_token_program: b.base_token_program, + quote_token_program: b.quote_token_program, ..Default::default() } } @@ -77,8 +85,17 @@ pub(crate) fn bonk_pool_create_from_parser( BonkPoolCreateEvent { metadata: meta, pool_state: p.pool_state, + payer: p.payer, creator: p.creator, - config: Pubkey::default(), + config: Default::default(), + global_config: p.global_config, + platform_config: p.platform_config, + base_mint: p.base_mint, + quote_mint: p.quote_mint, + base_vault: p.base_vault, + quote_vault: p.quote_vault, + base_token_program: p.base_token_program, + quote_token_program: p.quote_token_program, base_mint_param: MintParams { decimals: p.base_mint_param.decimals, name: p.base_mint_param.name.clone(), @@ -88,7 +105,6 @@ pub(crate) fn bonk_pool_create_from_parser( curve_param: CurveParams::default(), vesting_param: VestingParams::default(), amm_fee_on: None, - ..Default::default() } } @@ -218,3 +234,94 @@ pub(crate) fn pumpswap_pool_account_from_parser( pool: pumpswap_pool_from_pb(e.pool), } } + +#[cfg(test)] +mod tests { + use super::*; + use sol_parser_sdk::core::events::{ + BaseMintParam, EventMetadata as ParserEventMetadata, RaydiumLaunchlabPoolCreateEvent, + RaydiumLaunchlabTradeEvent, TradeDirection, + }; + use solana_sdk::pubkey::Pubkey; + + #[test] + fn launchlab_trade_preserves_complete_account_context() { + let keys: Vec<_> = (0..11).map(|_| Pubkey::new_unique()).collect(); + let event = RaydiumLaunchlabTradeEvent { + metadata: ParserEventMetadata::default(), + pool_state: keys[0], + user: keys[1], + amount_in: 10, + amount_out: 20, + is_buy: true, + trade_direction: TradeDirection::Buy, + exact_in: true, + global_config: keys[2], + platform_config: keys[3], + user_base_token: keys[4], + user_quote_token: keys[5], + base_vault: keys[6], + quote_vault: keys[7], + base_mint: keys[8], + quote_mint: keys[9], + base_token_program: keys[10], + quote_token_program: Pubkey::new_unique(), + }; + let quote_token_program = event.quote_token_program; + + let mapped = bonk_trade_from_parser(event, EventMetadata::default()); + + assert_eq!(mapped.pool_state, keys[0]); + assert_eq!(mapped.payer, keys[1]); + assert_eq!(mapped.global_config, keys[2]); + assert_eq!(mapped.platform_config, keys[3]); + assert_eq!(mapped.user_base_token, keys[4]); + assert_eq!(mapped.user_quote_token, keys[5]); + assert_eq!(mapped.base_vault, keys[6]); + assert_eq!(mapped.quote_vault, keys[7]); + assert_eq!(mapped.base_token_mint, keys[8]); + assert_eq!(mapped.quote_token_mint, keys[9]); + assert_eq!(mapped.base_token_program, keys[10]); + assert_eq!(mapped.quote_token_program, quote_token_program); + } + + #[test] + fn launchlab_pool_create_preserves_complete_account_context() { + let keys: Vec<_> = (0..11).map(|_| Pubkey::new_unique()).collect(); + let event = RaydiumLaunchlabPoolCreateEvent { + metadata: ParserEventMetadata::default(), + base_mint_param: BaseMintParam { + symbol: "USD1".to_string(), + name: "USD1 pool".to_string(), + uri: String::new(), + decimals: 6, + }, + pool_state: keys[0], + payer: keys[1], + creator: keys[2], + global_config: keys[3], + platform_config: keys[4], + base_mint: keys[5], + quote_mint: keys[6], + base_vault: keys[7], + quote_vault: keys[8], + base_token_program: keys[9], + quote_token_program: keys[10], + }; + + let mapped = bonk_pool_create_from_parser(event, EventMetadata::default()); + + assert_eq!(mapped.pool_state, keys[0]); + assert_eq!(mapped.payer, keys[1]); + assert_eq!(mapped.creator, keys[2]); + assert_eq!(mapped.config, Pubkey::default()); + assert_eq!(mapped.global_config, keys[3]); + assert_eq!(mapped.platform_config, keys[4]); + assert_eq!(mapped.base_mint, keys[5]); + assert_eq!(mapped.quote_mint, keys[6]); + assert_eq!(mapped.base_vault, keys[7]); + assert_eq!(mapped.quote_vault, keys[8]); + assert_eq!(mapped.base_token_program, keys[9]); + assert_eq!(mapped.quote_token_program, keys[10]); + } +} diff --git a/src/streaming/parser_sdk_bridge/mod.rs b/src/streaming/parser_sdk_bridge/mod.rs index 78c9ef6..e5699fb 100644 --- a/src/streaming/parser_sdk_bridge/mod.rs +++ b/src/streaming/parser_sdk_bridge/mod.rs @@ -508,6 +508,16 @@ mod tests { is_buy: true, trade_direction: PbBonkDir::Buy, exact_in: true, + global_config: Pubkey::default(), + platform_config: Pubkey::default(), + user_base_token: Pubkey::default(), + user_quote_token: Pubkey::default(), + base_vault: Pubkey::default(), + quote_vault: Pubkey::default(), + base_mint: Pubkey::default(), + quote_mint: Pubkey::default(), + base_token_program: Pubkey::default(), + quote_token_program: Pubkey::default(), }; let dex = convert_parser_event(PbDexEvent::RaydiumLaunchlabTrade(b), None, 0).expect("convert"); @@ -530,6 +540,16 @@ mod tests { is_buy: false, trade_direction: PbBonkDir::Sell, exact_in: false, + global_config: Pubkey::default(), + platform_config: Pubkey::default(), + user_base_token: Pubkey::default(), + user_quote_token: Pubkey::default(), + base_vault: Pubkey::default(), + quote_vault: Pubkey::default(), + base_mint: Pubkey::default(), + quote_mint: Pubkey::default(), + base_token_program: Pubkey::default(), + quote_token_program: Pubkey::default(), }; let dex = convert_parser_event(PbDexEvent::RaydiumLaunchlabTrade(b), None, 0).expect("convert"); diff --git a/tests/current_mainnet_transactions.rs b/tests/current_mainnet_transactions.rs new file mode 100644 index 0000000..34bbb3a --- /dev/null +++ b/tests/current_mainnet_transactions.rs @@ -0,0 +1,87 @@ +use sol_parser_sdk::instr::program_ids::METEORA_DLMM_PROGRAM_ID; +use solana_client::rpc_client::RpcClient; +use solana_sdk::signature::Signature; +use solana_streamer_sdk::fetch_rpc_transaction_as_streamer_events; +use solana_streamer_sdk::streaming::event_parser::common::filter::EventTypeFilter; +use solana_streamer_sdk::streaming::event_parser::common::EventType; +use solana_streamer_sdk::streaming::event_parser::{DexEvent, Protocol}; +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()), + ) +} + +// These transactions were captured from current mainnet traffic on 2026-08-13. +// Run with: RUN_MAINNET_TESTS=1 SOLANA_RPC_URL= cargo test --test current_mainnet_transactions + +#[test] +fn current_meteora_dlmm_swap_passes_exact_filter() { + if !run_mainnet_tests() { + return; + } + const SIGNATURE: &str = + "eEWaGsbRPoiD36Xf3epzSMmdtXX36va76b13YfsDV3ncsxQHBTjC68zZ8mbzFXTNWy3n3qKUAHjgHBconX4Gu1i"; + let signature = Signature::from_str(SIGNATURE).expect("valid fixture signature"); + let filter = EventTypeFilter::include_only([EventType::MeteoraDlmmSwap]); + let events = fetch_rpc_transaction_as_streamer_events( + &rpc_client(), + &signature, + 0, + &[Protocol::MeteoraDlmm], + Some(&filter), + ) + .expect("parse current Meteora DLMM swap"); + + assert_eq!(METEORA_DLMM_PROGRAM_ID.to_string(), "LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo"); + assert_eq!(events.len(), 1); + let DexEvent::MeteoraDlmmSwapEvent(swap) = &events[0] else { + panic!("expected Meteora DLMM swap"); + }; + assert_eq!(swap.metadata.signature, signature); + assert_eq!(swap.metadata.slot, 438_873_646); + assert_eq!(swap.amount_in, 2_738_183_783); + assert_eq!(swap.amount_out, 81_555_062); + assert_eq!(swap.fee, 18_486_656); + assert_eq!(swap.protocol_fee, 2_054_072); +} + +#[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 signature = Signature::from_str(SIGNATURE).expect("valid fixture signature"); + let events = fetch_rpc_transaction_as_streamer_events( + &rpc_client(), + &signature, + 0, + &[Protocol::RaydiumLaunchpad], + None, + ) + .expect("parse current Raydium LaunchLab USD1 trade"); + let trades: Vec<_> = events + .iter() + .filter_map(|event| match event { + DexEvent::BonkTradeEvent(trade) => Some(trade), + _ => None, + }) + .collect(); + + assert_eq!(trades.len(), 1); + assert_eq!(trades[0].metadata.signature, signature); + assert_eq!(trades[0].metadata.slot, 438_894_516); + assert_eq!(trades[0].quote_token_mint.to_string(), USD1_MINT); + assert_eq!(trades[0].global_config.to_string(), USD1_GLOBAL_CONFIG); + assert_ne!(trades[0].quote_token_program, Default::default()); +} From d6385b78ee3299dfb58208701acbdd9a571dea57 Mon Sep 17 00:00:00 2001 From: 0xfnzero <0xfnzero@users.noreply.github.com> Date: Thu, 13 Aug 2026 08:45:05 +0800 Subject: [PATCH 2/2] chore: use sol-parser-sdk 0.6.3 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index acf6fb9..577f3fb 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ sdk-perf-stats = ["sol-parser-sdk/perf-stats"] sdk-ultra-perf = ["sol-parser-sdk/ultra-perf"] [dependencies] -sol-parser-sdk = { git = "https://github.com/0xfnzero/sol-parser-sdk.git", rev = "25a494ae33783ceaaa74bd914471fd49af6edbd9", default-features = false } +sol-parser-sdk = { version = "0.6.3", default-features = false } solana-sdk = "3.0.0" solana-client = "3.1.12" solana-transaction-status = "3.1.12"