mirror of
https://github.com/0xfnzero/solana-streamer.git
synced 2026-11-02 07:16:31 +00:00
feat: expose Pump route trade hints and release v3.0.13
This commit is contained in:
+2
-2
@@ -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"] }
|
||||
|
||||
@@ -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
@@ -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(_))
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user