feat: expose Pump route trade hints and release v3.0.13

This commit is contained in:
0xfnzero
2026-10-19 02:02:47 +08:00
parent f9c5dae90d
commit db615bea7d
10 changed files with 178 additions and 10 deletions
+2 -2
View File
@@ -1,6 +1,6 @@
[package]
name = "solana-streamer-sdk"
version = "3.0.12"
version = "3.0.13"
edition = "2021"
rust-version = "1.91"
authors = ["William <byteblock6@gmail.com>", "sgxiang <sgxiang@gmail.com>", "wei <1415121722@qq.com>"]
@@ -22,7 +22,7 @@ sdk-perf-stats = ["sol-parser-sdk/perf-stats"]
sdk-ultra-perf = ["sol-parser-sdk/ultra-perf"]
[dependencies]
sol-parser-sdk = { path = "../sol-parser-sdk", version = "=0.7.14", default-features = false }
sol-parser-sdk = { path = "../sol-parser-sdk", version = "=0.7.15", default-features = false }
solana-sdk = "=4.0.1"
solana-client = "=4.2.2"
solana-transaction-status = { version = "=4.2.2", features = ["agave-unstable-api"] }
+10 -4
View File
@@ -130,30 +130,36 @@ Add the dependency to your `Cargo.toml`:
```toml
# Add to your Cargo.toml
solana-streamer-sdk = { path = "./solana-streamer", version = "3.0.12" }
solana-streamer-sdk = { path = "./solana-streamer", version = "3.0.13" }
```
### Use crates.io
```toml
# Add to your Cargo.toml
solana-streamer-sdk = "3.0.12"
solana-streamer-sdk = "3.0.13"
```
Parser backend features:
```toml
# Default: sol-parser-sdk parse-borsh backend
solana-streamer-sdk = "3.0.12"
solana-streamer-sdk = "3.0.13"
# Zero-copy parser backend for latency-sensitive bots
solana-streamer-sdk = { version = "3.0.12", default-features = false, features = ["sdk-parse-zero-copy"] }
solana-streamer-sdk = { version = "3.0.13", default-features = false, features = ["sdk-parse-zero-copy"] }
```
If both `sdk-parse-borsh` and `sdk-parse-zero-copy` are enabled, `sol-parser-sdk 0.6.1+` uses the zero-copy backend.
## 🔄 Migration Guide
## v3.0.13 — Pump route event support
Route intents expose `is_buy` and `ix_name`, using existing trade field names and official IDL names. RapidBuy reports a buy intent and `buy_exact_quote_in_v2`; BuyPumpDelta reports only a buy intent. Multi-hop direction and unknown methods remain null. Currencies remain in `hops[].base_mint` / `quote_mint`. These hints do not confirm execution; filtering does not convert intents into trades.
Streamer forwards complete route payloads as `DexEvent::PumpRouteIntentEvent` with protocol/event filtering. Old JSON defaults missing hints to unknown. Rust struct literals need the new fields; exhaustive streamer event matches need the new variant.
## v3.0.12 — Pump routes and offline replay
Pins sol-parser-sdk 0.7.14 and includes the latest streaming facade code and native hot-path benchmark. The raw parser re-export provides the new Pump route-intent and cached-ALT APIs.
+10 -4
View File
@@ -129,30 +129,36 @@ git clone https://github.com/0xfnzero/solana-streamer
```toml
# 添加到您的 Cargo.toml
solana-streamer-sdk = { path = "./solana-streamer", version = "3.0.12" }
solana-streamer-sdk = { path = "./solana-streamer", version = "3.0.13" }
```
### 使用 crates.io
```toml
# 添加到您的 Cargo.toml
solana-streamer-sdk = "3.0.12"
solana-streamer-sdk = "3.0.13"
```
解析后端 feature:
```toml
# 默认:sol-parser-sdk parse-borsh 后端
solana-streamer-sdk = "3.0.12"
solana-streamer-sdk = "3.0.13"
# 面向低延迟 Bot 的 zero-copy 解析后端
solana-streamer-sdk = { version = "3.0.12", default-features = false, features = ["sdk-parse-zero-copy"] }
solana-streamer-sdk = { version = "3.0.13", default-features = false, features = ["sdk-parse-zero-copy"] }
```
如果同时启用 `sdk-parse-borsh` 和 `sdk-parse-zero-copy`,`sol-parser-sdk 0.6.1+` 会优先使用 zero-copy 后端。
## 🔄 迁移指南
## v3.0.13 — Pump 路由事件完善
路由意图新增 `is_buy` 和 `ix_name`,沿用交易事件字段名和官方 IDL 方法名。RapidBuy 为买入意图,方法为 `buy_exact_quote_in_v2`;BuyPumpDelta 仅确定买入方向;多跳方向与未知方法保持为空。币种仍从 `hops[].base_mint` / `quote_mint` 获取。这些信息不代表已成交,过滤器也不会将路由意图转换为交易事件。
Streamer 现在通过 `DexEvent::PumpRouteIntentEvent` 转发完整路由字段并支持过滤。旧 JSON 缺少新字段时保持未知;Rust 结构体字面量需补齐新字段,穷举 streamer 事件的代码需处理新变体。
## v3.0.12 — Pump routes and offline replay
Pins sol-parser-sdk 0.7.14 and includes the latest streaming facade code and native hot-path benchmark. The raw parser re-export provides the new Pump route-intent and cached-ALT APIs.
@@ -82,6 +82,13 @@ impl EventTypeFilter {
#[inline]
pub fn passes_for_event(&self, ev: &DexEvent) -> bool {
if let DexEvent::PumpRouteIntentEvent(_) = ev {
if self.exclude.contains(&EventType::PumpRouteIntent) {
return false;
}
return self.include.contains(&EventType::PumpRouteIntent)
|| self.passes_event_type(&EventType::PumpFunTrade);
}
self.passes_event_type(&ev.metadata().event_type)
}
}
@@ -230,6 +237,7 @@ fn push_streamer_event_sdk_grpc_types(
St::PumpFunTrade => {
out.push(Sdk::PumpFunTrade);
}
St::PumpRouteIntent => out.push(Sdk::PumpRouteIntent),
St::PumpFunBuy => {
out.push(Sdk::PumpFunBuy);
}
@@ -90,6 +90,7 @@ pub enum EventType {
PumpFunCreateToken,
PumpFunCreateV2Token,
PumpFunTrade,
PumpRouteIntent,
PumpFunBuy,
PumpFunBuyExactSolIn,
PumpFunSell,
@@ -45,6 +45,7 @@ pub enum DexEvent {
PumpFunCreateTokenEvent(PumpFunCreateTokenEvent),
PumpFunCreateV2TokenEvent(PumpFunCreateV2TokenEvent),
PumpFunTradeEvent(PumpFunTradeEvent),
PumpRouteIntentEvent(PumpRouteIntentEvent),
PumpFunMigrateEvent(PumpFunMigrateEvent),
PumpFeesCreateFeeSharingConfigEvent(PumpFeesCreateFeeSharingConfigEvent),
PumpFeesInitializeFeeConfigEvent(PumpFeesInitializeFeeConfigEvent),
@@ -212,6 +213,7 @@ impl_dex_event_metadata!(
PumpFunCreateTokenEvent,
PumpFunCreateV2TokenEvent,
PumpFunTradeEvent,
PumpRouteIntentEvent,
PumpFunMigrateEvent,
PumpFeesCreateFeeSharingConfigEvent,
PumpFeesInitializeFeeConfigEvent,
@@ -673,3 +673,26 @@ pub struct PumpFunCompleteEvent {
pub timestamp: i64,
pub quote_mint: Pubkey,
}
pub use sol_parser_sdk::core::events::{PumpRouteHop, PumpRouteKind};
/// Recognized outer route intent, not a confirmed trade or CPI result.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct PumpRouteIntentEvent {
pub metadata: EventMetadata,
pub kind: PumpRouteKind,
/// Intended direction relative to the single wrapper hop's base mint.
#[serde(default)]
pub is_buy: Option<bool>,
/// Official underlying instruction encoded in the wrapper, when known.
#[serde(default)]
pub ix_name: Option<String>,
pub program: Pubkey,
pub instruction_index: usize,
pub user: Pubkey,
pub input_account: Pubkey,
pub output_account: Pubkey,
pub amount_in: Option<u64>,
pub min_amount_out: Option<u64>,
pub hops: Vec<PumpRouteHop>,
pub instruction_data: Vec<u8>,
}
@@ -23,6 +23,39 @@ pub(crate) fn convert_parser_event(
recv_wall_us: i64,
) -> Option<DexEvent> {
match ev {
PbDexEvent::PumpRouteIntent(e) => {
use crate::streaming::event_parser::protocols::pumpfun::events::{
PumpRouteIntentEvent, PumpRouteKind,
};
let protocol = if e.kind == PumpRouteKind::MultiHopSwap {
ProtocolType::PumpSwap
} else {
ProtocolType::PumpFun
};
let metadata = adapt_pm(
e.metadata,
bt,
recv_wall_us,
protocol,
EventType::PumpRouteIntent,
e.program,
);
Some(DexEvent::PumpRouteIntentEvent(PumpRouteIntentEvent {
metadata,
kind: e.kind,
is_buy: e.is_buy,
ix_name: e.ix_name,
program: e.program,
instruction_index: e.instruction_index,
user: e.user,
input_account: e.input_account,
output_account: e.output_account,
amount_in: e.amount_in,
min_amount_out: e.min_amount_out,
hops: e.hops,
instruction_data: e.instruction_data,
}))
}
PbDexEvent::PumpFunPostCompleteBuy(e) => {
let meta = adapt_pm(
e.metadata.clone(),
@@ -29,6 +29,10 @@ fn is_protocol_independent_event(ev: &DexEvent) -> bool {
#[allow(clippy::match_like_matches_macro)]
fn protocol_matches_event(p: &Protocol, ev: &DexEvent) -> bool {
match (p, ev) {
(Protocol::PumpFun, DexEvent::PumpRouteIntentEvent(_)) => true,
(Protocol::PumpSwap, DexEvent::PumpRouteIntentEvent(e)) => {
e.kind == sol_parser_sdk::core::events::PumpRouteKind::MultiHopSwap
}
(Protocol::PumpFun, DexEvent::PumpFunCreateTokenEvent(_))
| (Protocol::PumpFun, DexEvent::PumpFunCreateV2TokenEvent(_))
| (Protocol::PumpFun, DexEvent::PumpFunTradeEvent(_))
+85
View File
@@ -58,6 +58,91 @@ mod tests {
use sol_parser_sdk::DexEvent as PbDexEvent;
use solana_sdk::{pubkey::Pubkey, signature::Signature};
#[test]
fn converts_pump_route_intents_and_filters_without_losing_hints() {
use crate::streaming::event_parser::common::filter::EventTypeFilter;
use sol_parser_sdk::core::events::{PumpRouteHop, PumpRouteIntentEvent, PumpRouteKind};
for kind in [
PumpRouteKind::RapidBuy,
PumpRouteKind::BuyPumpDelta,
PumpRouteKind::MultiHopSwap,
PumpRouteKind::MultiHopCurveSwap,
] {
let is_buy = matches!(kind, PumpRouteKind::RapidBuy | PumpRouteKind::BuyPumpDelta)
.then_some(true);
let ix_name =
(kind == PumpRouteKind::RapidBuy).then(|| "buy_exact_quote_in_v2".to_owned());
let program = Pubkey::new_unique();
let hop = PumpRouteHop {
base_mint: Pubkey::new_unique(),
quote_mint: Pubkey::new_unique(),
venue: Pubkey::new_unique(),
base_vault: Pubkey::new_unique(),
quote_vault: Pubkey::new_unique(),
};
let source = PbDexEvent::PumpRouteIntent(Box::new(PumpRouteIntentEvent {
metadata: EventMetadata { slot: 42, tx_index: 8, ..Default::default() },
kind,
is_buy,
ix_name: ix_name.clone(),
program,
instruction_index: 3,
user: Pubkey::new_unique(),
input_account: Pubkey::new_unique(),
output_account: Pubkey::new_unique(),
amount_in: None,
min_amount_out: None,
hops: vec![hop.clone()],
instruction_data: vec![1, 2, 3],
}));
let event = adapt_parser_event(
source.clone(),
None,
999,
&[Protocol::PumpFun],
Some(&EventTypeFilter::include_only(vec![EventType::PumpFunTrade])),
)
.unwrap();
let DexEvent::PumpRouteIntentEvent(ref route) = event else { panic!("route lost") };
assert_eq!(route.is_buy, is_buy);
assert_eq!(route.ix_name, ix_name);
assert_eq!(route.hops, vec![hop]);
assert_eq!(route.program, program);
assert_eq!(route.instruction_index, 3);
assert_eq!(route.metadata.slot, 42);
assert_eq!(route.metadata.event_type, EventType::PumpRouteIntent);
assert_eq!(route.instruction_data, vec![1, 2, 3]);
let json = serde_json::to_value(&event).unwrap();
assert_eq!(serde_json::from_value::<DexEvent>(json).unwrap(), event);
assert!(adapt_parser_event(
source.clone(),
None,
999,
&[Protocol::PumpFun],
Some(&EventTypeFilter::include_only(vec![EventType::PumpRouteIntent]))
)
.is_some());
for filter in [
EventTypeFilter::include_only(vec![EventType::PumpFunBuy]),
EventTypeFilter::include_only(vec![EventType::PumpFunSell]),
EventTypeFilter::include_exclude(
vec![EventType::PumpFunTrade],
vec![EventType::PumpRouteIntent],
),
] {
assert!(adapt_parser_event(source.clone(), None, 999, &[], Some(&filter)).is_none());
}
assert_eq!(
adapt_parser_event(source.clone(), None, 999, &[Protocol::PumpSwap], None)
.is_some(),
kind == PumpRouteKind::MultiHopSwap
);
assert!(
adapt_parser_event(source, None, 999, &[Protocol::OrcaWhirlpool], None).is_none()
);
}
}
#[test]
fn converts_pumpfun_trade_preserving_amounts() {
let mut t = PbPumpTrade::default();