mirror of
https://github.com/0xfnzero/solana-streamer.git
synced 2026-08-14 17:38:05 +00:00
fix: expose LaunchLab USD1 account context
This commit is contained in:
+1
-1
@@ -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;
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
Reference in New Issue
Block a user