diff --git a/Cargo.toml b/Cargo.toml index 30bd330..b2a0524 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "solana-streamer-sdk" -version = "1.4.11" +version = "1.4.12" edition = "2021" authors = ["William ", "sgxiang ", "wei <1415121722@qq.com>"] repository = "https://github.com/0xfnzero/solana-streamer" @@ -15,13 +15,13 @@ crate-type = ["cdylib", "rlib"] [features] default = ["sdk-parse-borsh"] sdk-parse-borsh = ["sol-parser-sdk/parse-borsh"] -# If both parser backend features are enabled, sol-parser-sdk 0.4.16+ uses zero-copy. +# If both parser backend features are enabled, sol-parser-sdk 0.4.17+ uses zero-copy. sdk-parse-zero-copy = ["sol-parser-sdk/parse-zero-copy"] sdk-perf-stats = ["sol-parser-sdk/perf-stats"] sdk-ultra-perf = ["sol-parser-sdk/ultra-perf"] [dependencies] -sol-parser-sdk = { version = "0.4.16", default-features = false } +sol-parser-sdk = { version = "0.4.17", default-features = false } solana-sdk = "3.0.0" solana-client = "3.1.12" solana-transaction-status = "3.1.12" diff --git a/README.md b/README.md index 85dfa4a..9007161 100755 --- a/README.md +++ b/README.md @@ -123,33 +123,33 @@ Add the dependency to your `Cargo.toml`: ```toml # Add to your Cargo.toml -solana-streamer-sdk = { path = "./solana-streamer", version = "1.4.11" } +solana-streamer-sdk = { path = "./solana-streamer", version = "1.4.12" } ``` ### Use crates.io ```toml # Add to your Cargo.toml -solana-streamer-sdk = "1.4.11" +solana-streamer-sdk = "1.4.12" ``` Parser backend features: ```toml # Default: sol-parser-sdk parse-borsh backend -solana-streamer-sdk = "1.4.11" +solana-streamer-sdk = "1.4.12" # Zero-copy parser backend for latency-sensitive bots -solana-streamer-sdk = { version = "1.4.11", default-features = false, features = ["sdk-parse-zero-copy"] } +solana-streamer-sdk = { version = "1.4.12", default-features = false, features = ["sdk-parse-zero-copy"] } ``` -If both `sdk-parse-borsh` and `sdk-parse-zero-copy` are enabled, `sol-parser-sdk 0.4.16+` uses the zero-copy backend. +If both `sdk-parse-borsh` and `sdk-parse-zero-copy` are enabled, `sol-parser-sdk 0.4.17+` uses the zero-copy backend. ## 🔄 Migration Guide -### Upgrading to v1.4.11 +### Upgrading to v1.4.12 -Version 1.4.11 uses `sol-parser-sdk 0.4.16` and preserves the SDK-compatible Yellowstone gRPC ordering modes in the streamer facade while aligning PumpFun USDC/v2 create, trade, global, bonding-curve, and volume account fields with the parser SDK. `buy_exact_quote_in(_v2)` is delivered as `PumpFunBuy` with the exact quote fields preserved. +Version 1.4.12 uses `sol-parser-sdk 0.4.17` and normalizes legacy PumpFun SOL quote mints to the Solscan SOL sentinel while preserving real USDC quote mints and quote-reserve fields. Streamer bridge and merger paths keep the parser SDK semantics aligned for Yellowstone gRPC and ShredStream output. New optional capabilities: diff --git a/README_CN.md b/README_CN.md index 13b2075..37d1233 100644 --- a/README_CN.md +++ b/README_CN.md @@ -122,33 +122,33 @@ git clone https://github.com/0xfnzero/solana-streamer ```toml # 添加到您的 Cargo.toml -solana-streamer-sdk = { path = "./solana-streamer", version = "1.4.11" } +solana-streamer-sdk = { path = "./solana-streamer", version = "1.4.12" } ``` ### 使用 crates.io ```toml # 添加到您的 Cargo.toml -solana-streamer-sdk = "1.4.11" +solana-streamer-sdk = "1.4.12" ``` 解析后端 feature: ```toml # 默认:sol-parser-sdk parse-borsh 后端 -solana-streamer-sdk = "1.4.11" +solana-streamer-sdk = "1.4.12" # 面向低延迟 Bot 的 zero-copy 解析后端 -solana-streamer-sdk = { version = "1.4.11", default-features = false, features = ["sdk-parse-zero-copy"] } +solana-streamer-sdk = { version = "1.4.12", default-features = false, features = ["sdk-parse-zero-copy"] } ``` -如果同时启用 `sdk-parse-borsh` 和 `sdk-parse-zero-copy`,`sol-parser-sdk 0.4.16+` 会优先使用 zero-copy 后端。 +如果同时启用 `sdk-parse-borsh` 和 `sdk-parse-zero-copy`,`sol-parser-sdk 0.4.17+` 会优先使用 zero-copy 后端。 ## 🔄 迁移指南 -### 升级到 v1.4.11 +### 升级到 v1.4.12 -v1.4.11 使用 `sol-parser-sdk 0.4.16`,保留 streamer facade 中与 SDK 兼容的 Yellowstone gRPC 输出顺序模式,并将 PumpFun USDC/v2 的 create、trade、global、bonding-curve 和 volume account 字段与 parser SDK 对齐。`buy_exact_quote_in(_v2)` 会作为 `PumpFunBuy` 投递,同时保留 exact quote 相关字段。 +v1.4.12 使用 `sol-parser-sdk 0.4.17`,会把 legacy PumpFun SOL 的 `quote_mint` 归一为 Solscan SOL sentinel,同时保留真实 USDC quote mint 和 quote reserve 字段。Streamer bridge 与 merger 路径继续保持 Yellowstone gRPC 和 ShredStream 输出与 parser SDK 语义一致。 新增可选能力: diff --git a/src/streaming/event_parser/core/merger_event.rs b/src/streaming/event_parser/core/merger_event.rs index 88f3ad8..54447e3 100644 --- a/src/streaming/event_parser/core/merger_event.rs +++ b/src/streaming/event_parser/core/merger_event.rs @@ -1,5 +1,8 @@ use crate::streaming::event_parser::DexEvent; -use solana_sdk::pubkey::Pubkey; +use solana_sdk::{pubkey, pubkey::Pubkey}; + +const PUMPFUN_SOLSCAN_SOL_QUOTE_MINT: Pubkey = + pubkey!("So11111111111111111111111111111111111111111"); #[inline] fn fill_pubkey(to: &mut Pubkey, from: Pubkey) { @@ -8,6 +11,25 @@ fn fill_pubkey(to: &mut Pubkey, from: Pubkey) { } } +#[inline] +fn normalize_pumpfun_quote_mint(quote_mint: Pubkey) -> Pubkey { + if quote_mint == Pubkey::default() { + PUMPFUN_SOLSCAN_SOL_QUOTE_MINT + } else { + quote_mint + } +} + +#[inline] +fn fill_pumpfun_quote_mint(to: &mut Pubkey, from: Pubkey) { + let from = normalize_pumpfun_quote_mint(from); + if (*to == Pubkey::default() || *to == PUMPFUN_SOLSCAN_SOL_QUOTE_MINT) + && from != Pubkey::default() + { + *to = from; + } +} + #[inline] fn fill_u64(to: &mut u64, from: u64) { if *to == 0 && from != 0 { @@ -66,7 +88,7 @@ pub fn merge(instruction_event: &mut DexEvent, cpi_log_event: DexEvent) { if e.shareholders.is_empty() && !cpie.shareholders.is_empty() { e.shareholders = cpie.shareholders; } - fill_pubkey(&mut e.quote_mint, cpie.quote_mint); + fill_pumpfun_quote_mint(&mut e.quote_mint, cpie.quote_mint); fill_u64(&mut e.quote_amount, cpie.quote_amount); fill_u64(&mut e.virtual_quote_reserves, cpie.virtual_quote_reserves); fill_u64(&mut e.real_quote_reserves, cpie.real_quote_reserves); @@ -155,7 +177,7 @@ pub fn merge(instruction_event: &mut DexEvent, cpi_log_event: DexEvent) { if cpie.token_program != solana_sdk::pubkey::Pubkey::default() { e.token_program = cpie.token_program; } - fill_pubkey(&mut e.quote_mint, cpie.quote_mint); + fill_pumpfun_quote_mint(&mut e.quote_mint, cpie.quote_mint); fill_u64(&mut e.virtual_quote_reserves, cpie.virtual_quote_reserves); e.is_mayhem_mode |= cpie.is_mayhem_mode; e.is_cashback_enabled |= cpie.is_cashback_enabled; @@ -177,7 +199,7 @@ pub fn merge(instruction_event: &mut DexEvent, cpi_log_event: DexEvent) { fill_u64(&mut e.real_token_reserves, cpie.real_token_reserves); fill_u64(&mut e.token_total_supply, cpie.token_total_supply); fill_pubkey(&mut e.token_program, cpie.token_program); - fill_pubkey(&mut e.quote_mint, cpie.quote_mint); + fill_pumpfun_quote_mint(&mut e.quote_mint, cpie.quote_mint); fill_u64(&mut e.virtual_quote_reserves, cpie.virtual_quote_reserves); e.is_mayhem_mode |= cpie.is_mayhem_mode; e.is_cashback_enabled |= cpie.is_cashback_enabled; @@ -216,7 +238,7 @@ pub fn merge(instruction_event: &mut DexEvent, cpi_log_event: DexEvent) { if cpie.token_program != solana_sdk::pubkey::Pubkey::default() { e.token_program = cpie.token_program; } - fill_pubkey(&mut e.quote_mint, cpie.quote_mint); + fill_pumpfun_quote_mint(&mut e.quote_mint, cpie.quote_mint); fill_u64(&mut e.virtual_quote_reserves, cpie.virtual_quote_reserves); e.is_mayhem_mode |= cpie.is_mayhem_mode; e.is_cashback_enabled |= cpie.is_cashback_enabled; @@ -883,4 +905,22 @@ mod tests { _ => panic!("expected PumpFunTradeEvent"), } } + + #[test] + fn pumpfun_merge_replaces_sol_quote_sentinel_with_real_quote_mint() { + let quote_mint = Pubkey::new_unique(); + let mut instruction_event = DexEvent::PumpFunTradeEvent(PumpFunTradeEvent { + quote_mint: PUMPFUN_SOLSCAN_SOL_QUOTE_MINT, + ..Default::default() + }); + let cpi_log_event = + DexEvent::PumpFunTradeEvent(PumpFunTradeEvent { quote_mint, ..Default::default() }); + + merge(&mut instruction_event, cpi_log_event); + + match instruction_event { + DexEvent::PumpFunTradeEvent(t) => assert_eq!(t.quote_mint, quote_mint), + _ => panic!("expected PumpFunTradeEvent"), + } + } } diff --git a/src/streaming/parser_sdk_bridge/pump_pumpswap.rs b/src/streaming/parser_sdk_bridge/pump_pumpswap.rs index b90c291..79d302e 100644 --- a/src/streaming/parser_sdk_bridge/pump_pumpswap.rs +++ b/src/streaming/parser_sdk_bridge/pump_pumpswap.rs @@ -21,11 +21,23 @@ use crate::streaming::event_parser::protocols::pumpswap::events::{ }; use crate::streaming::event_parser::DexEvent; use prost_types::Timestamp; -use solana_sdk::pubkey::Pubkey; +use solana_sdk::{pubkey, pubkey::Pubkey}; use super::adapt::adapt_pm; use super::program_ids::{pump_program, pumpswap_program}; +const PUMPFUN_SOLSCAN_SOL_QUOTE_MINT: Pubkey = + pubkey!("So11111111111111111111111111111111111111111"); + +#[inline] +fn normalize_pumpfun_quote_mint(quote_mint: Pubkey) -> Pubkey { + if quote_mint == Pubkey::default() { + PUMPFUN_SOLSCAN_SOL_QUOTE_MINT + } else { + quote_mint + } +} + pub(crate) fn pumpfun_create_token_from_parser( c: sol_parser_sdk::core::events::PumpFunCreateTokenEvent, meta: EventMetadata, @@ -47,7 +59,7 @@ pub(crate) fn pumpfun_create_token_from_parser( token_program: c.token_program, is_mayhem_mode: c.is_mayhem_mode, is_cashback_enabled: c.is_cashback_enabled, - quote_mint: c.quote_mint, + quote_mint: normalize_pumpfun_quote_mint(c.quote_mint), virtual_quote_reserves: c.virtual_quote_reserves, ..Default::default() } @@ -74,7 +86,7 @@ pub(crate) fn pumpfun_create_v2_from_parser( token_program: c.token_program, is_mayhem_mode: c.is_mayhem_mode, is_cashback_enabled: c.is_cashback_enabled, - quote_mint: c.quote_mint, + quote_mint: normalize_pumpfun_quote_mint(c.quote_mint), virtual_quote_reserves: c.virtual_quote_reserves, mint_authority: c.mint_authority, associated_bonding_curve: c.associated_bonding_curve, @@ -365,7 +377,7 @@ pub(crate) fn pumpfun_bonding_curve_account_from_parser( creator: e.bonding_curve.creator, is_mayhem_mode: e.bonding_curve.is_mayhem_mode, is_cashback_coin: e.bonding_curve.is_cashback_coin, - quote_mint: e.bonding_curve.quote_mint, + quote_mint: normalize_pumpfun_quote_mint(e.bonding_curve.quote_mint), }, } } @@ -735,7 +747,7 @@ pub(crate) fn pumpfun_trade_from_parser_with_event_type( buyback_fee_basis_points: t.buyback_fee_basis_points, buyback_fee: t.buyback_fee, shareholders: t.shareholders.into_iter().map(pump_fees_shareholder_from_parser).collect(), - quote_mint: t.quote_mint, + quote_mint: normalize_pumpfun_quote_mint(t.quote_mint), quote_amount: t.quote_amount, virtual_quote_reserves: t.virtual_quote_reserves, real_quote_reserves: t.real_quote_reserves, @@ -840,3 +852,25 @@ pub(crate) fn pumpswap_trade_from_parser( })) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn pumpfun_default_quote_mint_uses_solscan_sol_sentinel() { + let ev = pumpfun_trade_from_parser_with_event_type( + sol_parser_sdk::core::events::PumpFunTradeEvent { is_buy: true, ..Default::default() }, + None, + 0, + EventType::PumpFunBuy, + ); + + match ev { + DexEvent::PumpFunTradeEvent(t) => { + assert_eq!(t.quote_mint.to_string(), "So11111111111111111111111111111111111111111"); + } + _ => panic!("expected PumpFunTradeEvent"), + } + } +}