fix: expose LaunchLab USD1 account context

This commit is contained in:
0xfnzero
2026-08-13 08:13:44 +08:00
parent 73ba4d8d09
commit 8c5eb7c214
7 changed files with 305 additions and 5 deletions
+1 -1
View File
@@ -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"
@@ -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());
@@ -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;
+28
View File
@@ -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]);
}
}
@@ -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]);
}
}
+20
View File
@@ -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");
+87
View File
@@ -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=<optional archive RPC> 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());
}