From bec65dea21fb05e700a62132367c0b6633571692 Mon Sep 17 00:00:00 2001 From: Estereg Date: Wed, 17 Dec 2025 01:05:44 -0300 Subject: [PATCH 1/9] chore: clean up unused dependencies --- Cargo.toml | 31 +------------------------------ 1 file changed, 1 insertion(+), 30 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 702ef7a..5237dbe 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,24 +16,13 @@ crate-type = ["cdylib", "rlib"] solana-sdk = "3.0.0" solana-client = "3.1.4" solana-program = "3.0.0" -solana-rpc-client = "3.1.4" -solana-rpc-client-api = "3.1.4" solana-transaction-status = "3.1.4" solana-account-decoder = "3.1.4" -solana-hash = "4.0.1" solana-entry = "3.1.4" -solana-rpc-client-nonce-utils = "3.1.4" -solana-perf = "3.1.4" -solana-metrics = "3.1.4" -spl-associated-token-account = "8.0.0" borsh = { version = "1.6.0", features = ["derive"] } serde = { version = "1.0.228", features = ["derive"] } -serde_json = "1.0.134" serde-big-array = "0.5.1" futures = "0.3.31" -futures-util = "0.3.31" -base64 = "0.22.1" -rand = "0.9.2" bincode = "1.3" anyhow = "1.0.90" yellowstone-grpc-client = { version = "10.2.0" } @@ -41,37 +30,19 @@ yellowstone-grpc-proto = { version = "10.1.1" } tokio = { version = "1.42.0", features = ["full", "rt-multi-thread"]} tonic = { version = "0.14.2", features = ["transport"] } rustls = { version = "0.23.35", features = ["ring"], default-features = false } -rustls-native-certs = "0.8.2" -tokio-rustls = "0.26.4" log = "0.4.29" chrono = "0.4.42" -regex = "1" -tracing = "0.1.43" -thiserror = "2.0.17" -async-trait = "0.1.86" lazy_static = "1.5.0" once_cell = "1.20.3" dashmap = "6.1.0" prost = "0.14.1" prost-types = "0.14.1" -num_enum = "0.7.5" -num-derive = "0.4.2" -num-traits = "0.2.19" -hex = "0.4.3" -bytemuck = { version = "1.24.0" } -arrayref = "0.3.6" -borsh-derive = "1.6.0" -indicatif = "0.18.3" maplit = "1.0.2" env_logger = "0.11.8" -crossbeam = "0.8.4" crossbeam-queue = "0.3.12" parking_lot = "0.12.5" wide = "1.1.0" spl-token = "9.0.0" spl-token-2022 = "10.0.0" solana-commitment-config = { version = "3.1.0", features = ["serde"] } -tonic-prost = "0.14.2" - -[dev-dependencies] -criterion = { version = "0.8.1", features = ["html_reports"] } \ No newline at end of file +tonic-prost = "0.14.2" \ No newline at end of file From e1c537554e59714229dcf3f1d09fc992b1a08178 Mon Sep 17 00:00:00 2001 From: Estereg Date: Wed, 17 Dec 2025 20:06:14 -0300 Subject: [PATCH 2/9] feat: translate comments to English and add PumpSwap buy_exact_quote_in - Translate all Chinese comments to English - Add PumpSwap buy_exact_quote_in instruction and event support - Update event parser to handle new buy_exact_quote_in event type - Improve code readability across codebase --- src/streaming/common/constants.rs | 12 +-- src/streaming/common/event_processor.rs | 4 +- src/streaming/common/metrics.rs | 72 ++++++------- src/streaming/common/mod.rs | 4 +- .../common/high_performance_clock.rs | 44 ++++---- src/streaming/event_parser/common/types.rs | 26 +++-- src/streaming/event_parser/common/utils.rs | 20 ++-- .../event_parser/core/account_event_parser.rs | 28 ++--- .../event_parser/core/common_event_parser.rs | 14 +-- src/streaming/event_parser/core/dispatcher.rs | 96 ++++++++--------- .../event_parser/core/event_parser.rs | 101 +++++++++-------- .../event_parser/core/merger_event.rs | 42 ++++++++ src/streaming/event_parser/core/traits.rs | 3 + .../protocols/block/block_meta_event.rs | 2 +- .../event_parser/protocols/bonk/parser.rs | 12 +-- .../event_parser/protocols/pumpfun/parser.rs | 14 +-- .../event_parser/protocols/pumpswap/events.rs | 94 +++++++++++++--- .../event_parser/protocols/pumpswap/parser.rs | 102 +++++++++++++----- src/streaming/event_parser/protocols/types.rs | 2 +- src/streaming/grpc/connection.rs | 2 +- src/streaming/grpc/mod.rs | 6 +- src/streaming/grpc/pool.rs | 74 ++++++------- src/streaming/grpc/types.rs | 6 +- src/streaming/shred/connection.rs | 20 ++-- src/streaming/shred/mod.rs | 6 +- src/streaming/shred/pool.rs | 30 +++--- src/streaming/shred/types.rs | 4 +- src/streaming/shred_stream.rs | 10 +- src/streaming/yellowstone_grpc.rs | 34 +++--- 29 files changed, 533 insertions(+), 351 deletions(-) diff --git a/src/streaming/common/constants.rs b/src/streaming/common/constants.rs index 6be8e37..916ae4e 100644 --- a/src/streaming/common/constants.rs +++ b/src/streaming/common/constants.rs @@ -1,18 +1,18 @@ -// 流处理相关的常量定义 +// Constants related to stream processing -// 默认配置常量 +// Default configuration constants pub const DEFAULT_CONNECT_TIMEOUT: u64 = 10; pub const DEFAULT_REQUEST_TIMEOUT: u64 = 60; pub const DEFAULT_CHANNEL_SIZE: usize = 1000; pub const DEFAULT_MAX_DECODING_MESSAGE_SIZE: usize = 1024 * 1024 * 10; -// 性能监控相关常量 +// Performance monitoring related constants pub const DEFAULT_METRICS_WINDOW_SECONDS: u64 = 5; pub const DEFAULT_METRICS_PRINT_INTERVAL_SECONDS: u64 = 10; pub const SLOW_PROCESSING_THRESHOLD_US: f64 = 3000.0; -// gRPC 延迟监控 -// Solana 不存储毫秒,所以我们用500ms来校准以获得更好的近似值 +// gRPC latency monitoring +// Solana doesn't store milliseconds, so we use 500ms to calibrate for better approximation pub const SOLANA_BLOCK_TIME_ADJUSTMENT_MS: i64 = 500; -// 默认最大延迟阈值(毫秒) +// Default maximum latency threshold (milliseconds) pub const MAX_LATENCY_THRESHOLD_MS: i64 = 1000; diff --git a/src/streaming/common/event_processor.rs b/src/streaming/common/event_processor.rs index 87e986d..462fd14 100644 --- a/src/streaming/common/event_processor.rs +++ b/src/streaming/common/event_processor.rs @@ -10,9 +10,9 @@ use crate::streaming::shred::TransactionWithSlot; use solana_sdk::pubkey::Pubkey; use std::sync::Arc; -/// 创建带 metrics 统计的 callback 包装器 +/// Create callback wrapper with metrics statistics /// -/// 用于 Transaction 事件处理,在调用原始 callback 的同时更新 metrics +/// Used for Transaction event processing, updates metrics while calling the original callback #[inline] fn create_metrics_callback( callback: Arc, diff --git a/src/streaming/common/metrics.rs b/src/streaming/common/metrics.rs index 86e9666..f790304 100644 --- a/src/streaming/common/metrics.rs +++ b/src/streaming/common/metrics.rs @@ -200,7 +200,7 @@ pub struct HighPerformanceMetrics { start_nanos: AtomicU64, event_metrics: [AtomicEventMetrics; 3], processing_stats: AtomicProcessingTimeStats, - // 丢弃事件指标 + // Dropped events metrics dropped_events_count: AtomicU64, } @@ -219,7 +219,7 @@ impl HighPerformanceMetrics { } } - /// 获取运行时长(秒) + /// Get uptime (seconds) #[inline] pub fn get_uptime_seconds(&self) -> f64 { let now_nanos = @@ -244,7 +244,7 @@ impl HighPerformanceMetrics { (now_nanos - start) as f64 / 1_000_000_000.0 } - /// 获取事件指标快照 + /// Get event metrics snapshot #[inline] pub fn get_event_metrics(&self, event_type: EventType) -> EventMetricsSnapshot { let index = event_type.as_index(); @@ -254,19 +254,19 @@ impl HighPerformanceMetrics { EventMetricsSnapshot { process_count, events_processed, processing_stats } } - /// 获取处理时间统计 + /// Get processing time statistics #[inline] pub fn get_processing_stats(&self) -> ProcessingTimeStats { self.processing_stats.get_stats() } - /// 获取丢弃事件计数 + /// Get dropped events count #[inline] pub fn get_dropped_events_count(&self) -> u64 { self.dropped_events_count.load(Ordering::Relaxed) } - /// 更新窗口指标(后台任务调用) + /// Update window metrics (called by background task) fn update_window_metrics(&self, event_type: EventType, window_duration_nanos: u64) { let now_nanos = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_nanos() @@ -291,7 +291,7 @@ static BACKGROUND_TASK_STARTED: AtomicBool = AtomicBool::new(false); /// Metrics enabled flag static METRICS_ENABLED: AtomicBool = AtomicBool::new(true); -/// 高性能指标管理器 (Singleton) +/// High-performance metrics manager (Singleton) #[derive(Clone, Copy)] pub struct MetricsManager; @@ -334,7 +334,7 @@ impl MetricsManager { METRICS_ENABLED.load(Ordering::Relaxed) } - /// 记录处理次数(非阻塞) + /// Record process count (non-blocking) #[inline] pub fn record_process(&self, event_type: EventType) { if self.is_enabled() { @@ -342,7 +342,7 @@ impl MetricsManager { } } - /// 记录事件处理(非阻塞) + /// Record event processing (non-blocking) #[inline] pub fn record_events(&self, event_type: EventType, count: u64, processing_time_us: f64) { if !self.is_enabled() { @@ -351,17 +351,17 @@ impl MetricsManager { let index = event_type.as_index(); - // 原子更新事件计数 + // Atomically update event count GLOBAL_METRICS.event_metrics[index].add_events_processed(count); - // 原子更新该事件类型的处理时间统计 + // Atomically update processing time statistics for this event type GLOBAL_METRICS.event_metrics[index].update_processing_stats(processing_time_us, count); - // 保持全局处理时间统计的兼容性 + // Maintain compatibility with global processing time statistics GLOBAL_METRICS.processing_stats.update(processing_time_us, count); } - /// 记录慢处理操作 + /// Record slow processing operation #[inline] pub fn log_slow_processing(&self, processing_time_us: f64, event_count: usize) { if processing_time_us > SLOW_PROCESSING_THRESHOLD_US { @@ -369,12 +369,12 @@ impl MetricsManager { } } - /// 检查并警告高延迟 (校准后的 gRPC latency) + /// Check and warn about high latency (calibrated gRPC latency) /// latency = recv_time - (block_time + 500ms) #[inline] pub fn check_and_warn_high_latency(&self, recv_us: i64, block_time_ms: i64) { let recv_ms = recv_us / 1000; - // 校准延迟: recv_time - (block_time + 500ms) + // Calibrate latency: recv_time - (block_time + 500ms) let adjusted_latency_ms = recv_ms - (block_time_ms + SOLANA_BLOCK_TIME_ADJUSTMENT_MS); if adjusted_latency_ms > MAX_LATENCY_THRESHOLD_MS { @@ -388,38 +388,38 @@ impl MetricsManager { } } - /// 获取运行时长 + /// Get uptime pub fn get_uptime(&self) -> std::time::Duration { std::time::Duration::from_secs_f64(GLOBAL_METRICS.get_uptime_seconds()) } - /// 获取事件指标 + /// Get event metrics pub fn get_event_metrics(&self, event_type: EventType) -> EventMetricsSnapshot { GLOBAL_METRICS.get_event_metrics(event_type) } - /// 获取处理时间统计 + /// Get processing time statistics pub fn get_processing_stats(&self) -> ProcessingTimeStats { GLOBAL_METRICS.get_processing_stats() } - /// 获取丢弃事件计数 + /// Get dropped events count pub fn get_dropped_events_count(&self) -> u64 { GLOBAL_METRICS.get_dropped_events_count() } - /// 打印性能指标(非阻塞) + /// Print performance metrics (non-blocking) pub fn print_metrics(&self) { println!("\n📊 Performance Metrics"); println!(" Run Time: {:?}", self.get_uptime()); - // 打印丢弃事件指标 + // Print dropped events metrics let dropped_count = self.get_dropped_events_count(); if dropped_count > 0 { println!("\n⚠️ Dropped Events: {}", dropped_count); } - // 打印事件指标表格(包含处理时间统计) + // Print event metrics table (including processing time statistics) println!("┌─────────────┬──────────────┬──────────────────┬─────────────┬─────────────┐"); println!("│ Event Type │ Process Count│ Events Processed │ Last(μs) │ Avg(μs) │"); println!("├─────────────┼──────────────┼──────────────────┼─────────────┼─────────────┤"); @@ -440,7 +440,7 @@ impl MetricsManager { println!(); } - /// 启动自动性能监控任务 + /// Start automatic performance monitoring task pub async fn start_auto_monitoring(&self) -> Option> { if !self.is_enabled() { return None; @@ -458,7 +458,7 @@ impl MetricsManager { Some(handle) } - /// 获取完整的性能指标(兼容性方法) + /// Get complete performance metrics (compatibility method) pub fn get_metrics(&self) -> PerformanceMetrics { PerformanceMetrics { uptime: self.get_uptime(), @@ -470,25 +470,25 @@ impl MetricsManager { } } - /// 兼容性方法 - 添加交易处理计数 + /// Compatibility method - add transaction process count #[inline] pub fn add_tx_process_count(&self) { self.record_process(EventType::Transaction); } - /// 兼容性方法 - 添加账户处理计数 + /// Compatibility method - add account process count #[inline] pub fn add_account_process_count(&self) { self.record_process(EventType::Account); } - /// 兼容性方法 - 添加区块元数据处理计数 + /// Compatibility method - add block meta process count #[inline] pub fn add_block_meta_process_count(&self) { self.record_process(EventType::BlockMeta); } - /// 兼容性方法 - 更新指标 + /// Compatibility method - update metrics #[inline] pub fn update_metrics( &self, @@ -500,7 +500,7 @@ impl MetricsManager { self.log_slow_processing(processing_time_us, events_processed as usize); } - /// 更新指标并检查延迟 + /// Update metrics and check latency #[inline] pub fn update_metrics_with_latency( &self, @@ -514,39 +514,39 @@ impl MetricsManager { self.update_metrics(event_type, events_processed, processing_time_us); } - /// 增加丢弃事件计数 + /// Increment dropped events count #[inline] pub fn increment_dropped_events(&self) { if !self.is_enabled() { return; } - // 原子地增加丢弃事件计数 + // Atomically increment dropped events count let new_count = GLOBAL_METRICS.dropped_events_count.fetch_add(1, Ordering::Relaxed) + 1; - // 每丢弃1000个事件记录一次警告日志 + // Log warning every 1000 dropped events if new_count % 1000 == 0 { log::debug!("Dropped events count reached: {}", new_count); } } - /// 批量增加丢弃事件计数 + /// Batch increment dropped events count #[inline] pub fn increment_dropped_events_by(&self, count: u64) { if !self.is_enabled() || count == 0 { return; } - // 原子地增加丢弃事件计数 + // Atomically increment dropped events count let new_count = GLOBAL_METRICS.dropped_events_count.fetch_add(count, Ordering::Relaxed) + count; - // 记录批量丢弃事件的日志 + // Log batch dropped events if count > 1 { log::debug!("Dropped batch of {} events, total dropped: {}", count, new_count); } - // 每丢弃1000个事件记录一次警告日志 + // Log warning every 1000 dropped events if new_count % 1000 == 0 || (new_count / 1000) != ((new_count - count) / 1000) { log::debug!("Dropped events count reached: {}", new_count); } diff --git a/src/streaming/common/mod.rs b/src/streaming/common/mod.rs index 55a76aa..56e6c29 100644 --- a/src/streaming/common/mod.rs +++ b/src/streaming/common/mod.rs @@ -1,4 +1,4 @@ -// 公用模块 - 包含流处理相关的通用功能 +// Common modules - contains common functionality related to stream processing pub mod config; pub mod metrics; pub mod constants; @@ -6,7 +6,7 @@ pub mod subscription; pub mod event_processor; pub mod simd_utils; -// 重新导出主要类型 +// Re-export main types pub use config::*; pub use metrics::*; pub use constants::*; diff --git a/src/streaming/event_parser/common/high_performance_clock.rs b/src/streaming/event_parser/common/high_performance_clock.rs index 3faf27c..24e1b68 100644 --- a/src/streaming/event_parser/common/high_performance_clock.rs +++ b/src/streaming/event_parser/common/high_performance_clock.rs @@ -1,33 +1,33 @@ use std::fmt::Debug; use std::time::Instant; -/// 高性能时钟管理器,减少系统调用开销并最小化延迟 +/// High-performance clock manager, reduces system call overhead and minimizes latency #[derive(Debug)] pub struct HighPerformanceClock { - /// 基准时间点(程序启动时的单调时钟时间) + /// Base time point (monotonic clock time at program startup) base_instant: Instant, - /// 基准时间点对应的UTC时间戳(微秒) + /// UTC timestamp (microseconds) corresponding to base time point base_timestamp_us: i64, - /// 上次校准时间(用于检测是否需要重新校准) + /// Last calibration time (used to detect if recalibration is needed) last_calibration: Instant, - /// 校准间隔(秒) + /// Calibration interval (seconds) calibration_interval_secs: u64, } impl HighPerformanceClock { - /// 创建新的高性能时钟 + /// Create new high-performance clock pub fn new() -> Self { - Self::new_with_calibration_interval(300) // 默认5分钟校准一次 + Self::new_with_calibration_interval(300) // Default: calibrate every 5 minutes } - /// 创建带自定义校准间隔的高性能时钟 + /// Create high-performance clock with custom calibration interval pub fn new_with_calibration_interval(calibration_interval_secs: u64) -> Self { - // 通过多次采样来减少初始化误差 + // Reduce initialization error through multiple samples let mut best_offset = i64::MAX; let mut best_instant = Instant::now(); let mut best_timestamp = chrono::Utc::now().timestamp_micros(); - // 进行3次采样,选择延迟最小的 + // Perform 3 samples, choose the one with minimum latency for _ in 0..3 { let instant_before = Instant::now(); let timestamp = chrono::Utc::now().timestamp_micros(); @@ -50,35 +50,35 @@ impl HighPerformanceClock { } } - /// 获取当前时间戳(微秒),使用单调时钟计算,避免系统调用 + /// Get current timestamp (microseconds), calculated using monotonic clock to avoid system calls #[inline(always)] pub fn now_micros(&self) -> i64 { let elapsed = self.base_instant.elapsed(); self.base_timestamp_us + elapsed.as_micros() as i64 } - /// 获取高精度当前时间戳(微秒),在必要时进行校准 + /// Get high-precision current timestamp (microseconds), calibrate when necessary pub fn now_micros_with_calibration(&mut self) -> i64 { - // 检查是否需要重新校准 + // Check if recalibration is needed if self.last_calibration.elapsed().as_secs() >= self.calibration_interval_secs { self.recalibrate(); } self.now_micros() } - /// 重新校准时钟,减少累积漂移 + /// Recalibrate clock to reduce accumulated drift fn recalibrate(&mut self) { let current_monotonic = Instant::now(); let current_utc = chrono::Utc::now().timestamp_micros(); - // 计算预期的UTC时间戳(基于单调时钟) + // Calculate expected UTC timestamp (based on monotonic clock) let expected_utc = self.base_timestamp_us + current_monotonic.duration_since(self.base_instant).as_micros() as i64; - // 计算漂移量 + // Calculate drift amount let drift_us = current_utc - expected_utc; - // 如果漂移超过1毫秒,进行校准 + // If drift exceeds 1 millisecond, perform calibration if drift_us.abs() > 1000 { self.base_instant = current_monotonic; self.base_timestamp_us = current_utc; @@ -87,20 +87,20 @@ impl HighPerformanceClock { self.last_calibration = current_monotonic; } - /// 计算从指定时间戳到现在的消耗时间(微秒) + /// Calculate elapsed time (microseconds) from specified timestamp to now #[inline(always)] pub fn elapsed_micros_since(&self, start_timestamp_us: i64) -> i64 { self.now_micros() - start_timestamp_us } - /// 获取高精度纳秒时间戳 + /// Get high-precision nanosecond timestamp #[inline(always)] pub fn now_nanos(&self) -> i128 { let elapsed = self.base_instant.elapsed(); (self.base_timestamp_us as i128 * 1000) + elapsed.as_nanos() as i128 } - /// 重置时钟(强制重新初始化) + /// Reset clock (force re-initialization) pub fn reset(&mut self) { *self = Self::new_with_calibration_interval(self.calibration_interval_secs); } @@ -112,11 +112,11 @@ impl Default for HighPerformanceClock { } } -/// 全局高性能时钟实例 +/// Global high-performance clock instance static HIGH_PERF_CLOCK: once_cell::sync::OnceCell = once_cell::sync::OnceCell::new(); -/// 获取全局高性能时钟实例(最简单的实现) +/// Get global high-performance clock instance (simplest implementation) #[inline(always)] pub fn get_high_perf_clock() -> i64 { let clock = HIGH_PERF_CLOCK.get_or_init(HighPerformanceClock::new); diff --git a/src/streaming/event_parser/common/types.rs b/src/streaming/event_parser/common/types.rs index 7bcaa96..31672c9 100755 --- a/src/streaming/event_parser/common/types.rs +++ b/src/streaming/event_parser/common/types.rs @@ -30,7 +30,7 @@ impl EventMetadataPool { } pub fn release(&self, metadata: EventMetadata) { - // 如果队列已满,push 会失败,但不会阻塞 + // If queue is full, push will fail but won't block let _ = self.pool.push(metadata); } } @@ -63,6 +63,7 @@ pub enum EventType { // PumpSwap events #[default] PumpSwapBuy, + PumpSwapBuyExactQuoteIn, PumpSwapSell, PumpSwapCreatePool, PumpSwapDeposit, @@ -168,6 +169,7 @@ impl fmt::Display for EventType { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { EventType::PumpSwapBuy => write!(f, "PumpSwapBuy"), + EventType::PumpSwapBuyExactQuoteIn => write!(f, "PumpSwapBuyExactQuoteIn"), EventType::PumpSwapSell => write!(f, "PumpSwapSell"), EventType::PumpSwapCreatePool => write!(f, "PumpSwapCreatePool"), EventType::PumpSwapDeposit => write!(f, "PumpSwapDeposit"), @@ -301,7 +303,7 @@ pub struct SwapData { pub struct EventMetadata { pub signature: Signature, pub slot: u64, - pub transaction_index: Option, // 新增:交易在slot中的索引 + pub transaction_index: Option, // New: transaction index within the slot pub block_time: i64, pub block_time_ms: i64, pub recv_us: i64, @@ -380,7 +382,7 @@ pub fn parse_swap_data_from_next_instructions( description: None, }; - // 先根据 event 取出关键信息 + // First extract key information from event // let mut user: Option = None; let mut from_mint: Option = None; let mut to_mint: Option = None; @@ -407,6 +409,10 @@ pub fn parse_swap_data_from_next_instructions( swap_data.from_mint = e.quote_mint; swap_data.to_mint = e.base_mint; } + DexEvent::PumpSwapBuyExactQuoteInEvent(e) => { + swap_data.from_mint = e.quote_mint; + swap_data.to_mint = e.base_mint; + } DexEvent::PumpSwapSellEvent(e) => { swap_data.from_mint = e.base_mint; swap_data.to_mint = e.quote_mint; @@ -457,7 +463,7 @@ pub fn parse_swap_data_from_next_instructions( let to_mint = to_mint.unwrap_or_default(); let from_mint = from_mint.unwrap_or_default(); - // 单次循环完成提取和判断 + // Single loop to complete extraction and validation for instruction in inner_instruction.instructions.iter().skip((current_index + 1) as usize) { let compiled = &instruction.instruction; let program_id = accounts[compiled.program_id_index as usize]; @@ -466,7 +472,7 @@ pub fn parse_swap_data_from_next_instructions( } let data = &compiled.data; - // 使用 SIMD 验证数据格式 + // Use SIMD to validate data format if !SimdUtils::validate_data_format(data, 8) { continue; } @@ -550,7 +556,7 @@ pub fn parse_swap_data_from_next_grpc_instructions( description: None, }; - // 先根据 event 取出关键信息 + // First extract key information from event // let mut user: Option = None; let mut from_mint: Option = None; let mut to_mint: Option = None; @@ -577,6 +583,10 @@ pub fn parse_swap_data_from_next_grpc_instructions( swap_data.from_mint = e.quote_mint; swap_data.to_mint = e.base_mint; } + DexEvent::PumpSwapBuyExactQuoteInEvent(e) => { + swap_data.from_mint = e.quote_mint; + swap_data.to_mint = e.base_mint; + } DexEvent::PumpSwapSellEvent(e) => { swap_data.from_mint = e.base_mint; swap_data.to_mint = e.quote_mint; @@ -627,7 +637,7 @@ pub fn parse_swap_data_from_next_grpc_instructions( let to_mint = to_mint.unwrap_or_default(); let from_mint = from_mint.unwrap_or_default(); - // 单次循环完成提取和判断 + // Single loop to complete extraction and validation for instruction in inner_instruction.instructions.iter().skip((current_index + 1) as usize) { let compiled = &instruction; let program_id = accounts[compiled.program_id_index as usize]; @@ -636,7 +646,7 @@ pub fn parse_swap_data_from_next_grpc_instructions( } let data = &compiled.data; - // 使用 SIMD 验证数据格式 + // Use SIMD to validate data format if !SimdUtils::validate_data_format(data, 8) { continue; } diff --git a/src/streaming/event_parser/common/utils.rs b/src/streaming/event_parser/common/utils.rs index 56ec1a5..99c389f 100755 --- a/src/streaming/event_parser/common/utils.rs +++ b/src/streaming/event_parser/common/utils.rs @@ -1,11 +1,11 @@ use std::time::{SystemTime, UNIX_EPOCH}; -/// 获取当前时间戳 +/// Get current timestamp pub fn current_timestamp() -> i64 { SystemTime::now().duration_since(UNIX_EPOCH).expect("Time went backwards").as_secs() as i64 } -/// 从字节数组中提取鉴别器和剩余数据 +/// Extract discriminator and remaining data from byte array pub fn extract_discriminator(length: usize, data: &[u8]) -> Option<(&[u8], &[u8])> { if data.len() < length { return None; @@ -13,18 +13,18 @@ pub fn extract_discriminator(length: usize, data: &[u8]) -> Option<(&[u8], &[u8] Some((&data[..length], &data[length..])) } -/// 从日志中提取程序数据 +/// Extract program data from log pub fn extract_program_data(log: &str) -> Option<&str> { const PROGRAM_DATA_PREFIX: &str = "Program data: "; log.strip_prefix(PROGRAM_DATA_PREFIX) } -/// 从日志中提取程序日志 +/// Extract program log from log pub fn extract_program_log<'a>(log: &'a str, prefix: &str) -> Option<&'a str> { log.strip_prefix(prefix) } -/// 安全地从字节数组中读取u64 +/// Safely read u64 from byte array pub fn read_u64_le(data: &[u8], offset: usize) -> Option { if data.len() < offset + 8 { return None; @@ -71,7 +71,7 @@ pub fn read_option_bool(data: &[u8], offset: &mut usize) -> Option> Some(Some(value != 0)) } -/// 安全地从字节数组中读取u32 +/// Safely read u32 from byte array pub fn read_u32_le(data: &[u8], offset: usize) -> Option { if data.len() < offset + 4 { return None; @@ -80,7 +80,7 @@ pub fn read_u32_le(data: &[u8], offset: usize) -> Option { Some(u32::from_le_bytes(bytes)) } -/// 安全地从字节数组中读取u16 +/// Safely read u16 from byte array pub fn read_u16_le(data: &[u8], offset: usize) -> Option { if data.len() < offset + 2 { return None; @@ -89,17 +89,17 @@ pub fn read_u16_le(data: &[u8], offset: usize) -> Option { Some(u16::from_le_bytes(bytes)) } -/// 安全地从字节数组中读取u8 +/// Safely read u8 from byte array pub fn read_u8(data: &[u8], offset: usize) -> Option { data.get(offset).copied() } -/// 验证账户索引的有效性 +/// Validate account index validity pub fn validate_account_indices(indices: &[u8], account_count: usize) -> bool { indices.iter().all(|&idx| (idx as usize) < account_count) } -/// 格式化公钥为短字符串 +/// Format pubkey as short string pub fn format_pubkey_short(pubkey: &solana_sdk::pubkey::Pubkey) -> String { let s = pubkey.to_string(); if s.len() <= 8 { diff --git a/src/streaming/event_parser/core/account_event_parser.rs b/src/streaming/event_parser/core/account_event_parser.rs index 5711d04..890fa47 100644 --- a/src/streaming/event_parser/core/account_event_parser.rs +++ b/src/streaming/event_parser/core/account_event_parser.rs @@ -14,7 +14,7 @@ use spl_token_2022::{ state::{Account as Account2022, Mint as Mint2022}, }; -/// 通用账户事件 +/// Generic account event #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] pub struct TokenAccountEvent { pub metadata: EventMetadata, @@ -63,39 +63,39 @@ impl AccountEventParser { ) -> Option { use crate::streaming::event_parser::core::dispatcher::EventDispatcher; - // 1. 尝试从账户 discriminator 解析(协议特定账户) + // 1. Try to parse from account discriminator (protocol-specific accounts) if account.data.len() >= 8 { let discriminator = &account.data[0..8]; - // 尝试识别协议类型 + // Try to identify protocol type if let Some(protocol) = EventDispatcher::match_protocol_by_program_id(&account.owner) { - // 检查是否在请求的协议列表中 + // Check if in the requested protocol list if protocols.contains(&protocol) { - // 构建临时元数据(protocol会被dispatcher设置,event_type会在parser中设置) + // Build temporary metadata (protocol will be set by dispatcher, event_type will be set by parser) let metadata = EventMetadata { slot: account.slot, signature: account.signature, - protocol: ProtocolType::Common, // 会被 EventDispatcher::dispatch_account 设置 - event_type: EventType::default(), // 会被具体 parser 设置 + protocol: ProtocolType::Common, // Will be set by EventDispatcher::dispatch_account + event_type: EventType::default(), // Will be set by specific parser program_id: account.owner, recv_us: account.recv_us, handle_us: elapsed_micros_since(account.recv_us), ..Default::default() }; - // 使用 dispatcher 解析 + // Use dispatcher to parse if let Some(event) = EventDispatcher::dispatch_account( protocol, discriminator, &account, metadata, ) { - // 应用事件类型过滤 + // Apply event type filter if let Some(filter) = event_type_filter { if filter.include.contains(&event.metadata().event_type) { return Some(event); } - // 不匹配过滤器,继续尝试其他解析方式 + // Doesn't match filter, continue trying other parsing methods } else { return Some(event); } @@ -104,8 +104,8 @@ impl AccountEventParser { } } - // 2. 尝试解析特殊账户类型(Token、Nonce等) - // 这些是通用的,不属于特定协议 + // 2. Try to parse special account types (Token, Nonce, etc.) + // These are generic and don't belong to specific protocols let metadata = EventMetadata { slot: account.slot, signature: account.signature, @@ -117,7 +117,7 @@ impl AccountEventParser { ..Default::default() }; - // 尝试解析 Nonce 账户 + // Try to parse Nonce account if let Some(event) = Self::parse_nonce_account_event(&account, metadata.clone()) { if let Some(filter) = event_type_filter { if filter.include.contains(&event.metadata().event_type) { @@ -128,7 +128,7 @@ impl AccountEventParser { } } - // 尝试解析 Token 账户 + // Try to parse Token account if let Some(event) = Self::parse_token_account_event(&account, metadata) { if let Some(filter) = event_type_filter { if filter.include.contains(&event.metadata().event_type) { diff --git a/src/streaming/event_parser/core/common_event_parser.rs b/src/streaming/event_parser/core/common_event_parser.rs index 948676b..14970d8 100644 --- a/src/streaming/event_parser/core/common_event_parser.rs +++ b/src/streaming/event_parser/core/common_event_parser.rs @@ -11,21 +11,21 @@ use solana_sdk::pubkey::Pubkey; pub const COMPUTE_BUDGET_PROGRAM_ID: Pubkey = solana_sdk::pubkey!("ComputeBudget111111111111111111111111111111"); -/// SetComputeUnitLimit 事件 +/// SetComputeUnitLimit event #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)] pub struct SetComputeUnitLimitEvent { #[borsh(skip)] pub metadata: EventMetadata, - /// 请求的计算单元数量 + /// Number of compute units requested pub units: u32, } -/// SetComputeUnitPrice 事件 +/// SetComputeUnitPrice event #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)] pub struct SetComputeUnitPriceEvent { #[borsh(skip)] pub metadata: EventMetadata, - /// 每个计算单元的价格 (micro-lamports) + /// Price per compute unit (micro-lamports) pub micro_lamports: u64, } @@ -43,7 +43,7 @@ impl CommonEventParser { DexEvent::BlockMetaEvent(block_meta_event) } - /// 解析 Compute Budget 指令 + /// Parse Compute Budget instruction pub fn parse_compute_budget_instruction( instruction_data: &[u8], mut metadata: EventMetadata, @@ -52,10 +52,10 @@ impl CommonEventParser { return None; } - // 设置 protocol 为 Common + // Set protocol to Common metadata.protocol = ProtocolType::Common; - // Compute Budget 指令使用单字节判别器 + // Compute Budget instructions use single-byte discriminator match instruction_data[0] { // SetComputeUnitLimit: discriminator = 2 2 => { diff --git a/src/streaming/event_parser/core/dispatcher.rs b/src/streaming/event_parser/core/dispatcher.rs index 77691ce..67bef51 100644 --- a/src/streaming/event_parser/core/dispatcher.rs +++ b/src/streaming/event_parser/core/dispatcher.rs @@ -1,11 +1,11 @@ -//! 中心事件解析调度器 +//! Central event parsing dispatcher //! -//! 根据协议类型路由到对应的解析函数,替代原有的静态 CONFIGS 数组架构 +//! Routes to corresponding parsing functions based on protocol type, replacing the original static CONFIGS array architecture //! -//! ## 设计原则 -//! - **单一职责**: 每个函数只负责一件事(路由、解析、合并分离) -//! - **灵活性**: 调用方可以选择是否合并,或自定义合并逻辑 -//! - **可测试性**: 每个函数都可以独立测试 +//! ## Design Principles +//! - **Single Responsibility**: Each function is responsible for one thing (routing, parsing, merging separated) +//! - **Flexibility**: Callers can choose whether to merge or customize merge logic +//! - **Testability**: Each function can be tested independently use crate::streaming::event_parser::{ common::EventMetadata, @@ -19,23 +19,23 @@ use crate::streaming::event_parser::{ }; use solana_sdk::pubkey::Pubkey; -/// 中心事件解析调度器 +/// Central event parsing dispatcher /// -/// 负责将解析请求路由到对应协议的解析函数 +/// Responsible for routing parsing requests to corresponding protocol parsing functions pub struct EventDispatcher; impl EventDispatcher { - /// 解析 instruction 事件(只解析,不合并) + /// Parse instruction event (parse only, no merging) /// - /// # 参数 - /// - `protocol`: 协议类型 - /// - `instruction_discriminator`: 指令判别器 (8 bytes) - /// - `instruction_data`: 指令数据 - /// - `accounts`: 账户公钥列表 - /// - `metadata`: 事件元数据 + /// # Parameters + /// - `protocol`: Protocol type + /// - `instruction_discriminator`: Instruction discriminator (8 bytes) + /// - `instruction_data`: Instruction data + /// - `accounts`: Account public key list + /// - `metadata`: Event metadata /// - /// # 返回 - /// 解析成功返回 `Some(DexEvent)`,否则返回 `None` + /// # Returns + /// Returns `Some(DexEvent)` on successful parsing, otherwise `None` #[inline] pub fn dispatch_instruction( protocol: Protocol, @@ -44,7 +44,7 @@ impl EventDispatcher { accounts: &[Pubkey], mut metadata: EventMetadata, ) -> Option { - // 根据协议类型设置 metadata.protocol + // Set metadata.protocol based on protocol type use crate::streaming::event_parser::common::ProtocolType; metadata.protocol = match protocol { Protocol::PumpFun => ProtocolType::PumpFun, @@ -102,16 +102,16 @@ impl EventDispatcher { } } - /// 解析 inner instruction 事件(只解析,不合并) + /// Parse inner instruction event (parse only, no merging) /// - /// # 参数 - /// - `protocol`: 协议类型 - /// - `inner_instruction_discriminator`: 内联指令判别器 (16 bytes) - /// - `inner_instruction_data`: 内联指令数据 - /// - `metadata`: 事件元数据 + /// # Parameters + /// - `protocol`: Protocol type + /// - `inner_instruction_discriminator`: Inner instruction discriminator (16 bytes) + /// - `inner_instruction_data`: Inner instruction data + /// - `metadata`: Event metadata /// - /// # 返回 - /// 解析成功返回 `Some(DexEvent)`,否则返回 `None` + /// # Returns + /// Returns `Some(DexEvent)` on successful parsing, otherwise `None` #[inline] pub fn dispatch_inner_instruction( protocol: Protocol, @@ -119,7 +119,7 @@ impl EventDispatcher { inner_instruction_data: &[u8], mut metadata: EventMetadata, ) -> Option { - // 根据协议类型设置 metadata.protocol + // Set metadata.protocol based on protocol type use crate::streaming::event_parser::common::ProtocolType; metadata.protocol = match protocol { Protocol::PumpFun => ProtocolType::PumpFun, @@ -170,7 +170,7 @@ impl EventDispatcher { } } - /// 通过 program_id 匹配协议类型 + /// Match protocol type by program_id #[inline] pub fn match_protocol_by_program_id(program_id: &Pubkey) -> Option { if program_id == &pumpfun::PUMPFUN_PROGRAM_ID { @@ -192,20 +192,20 @@ impl EventDispatcher { } } - /// 检查是否为 Compute Budget Program + /// Check if it's a Compute Budget Program #[inline] pub fn is_compute_budget_program(program_id: &Pubkey) -> bool { program_id == &COMPUTE_BUDGET_PROGRAM_ID } - /// 解析 Compute Budget 指令 + /// Parse Compute Budget instruction /// - /// # 参数 - /// - `instruction_data`: 指令数据 - /// - `metadata`: 事件元数据 + /// # Parameters + /// - `instruction_data`: Instruction data + /// - `metadata`: Event metadata /// - /// # 返回 - /// 解析成功返回 `Some(DexEvent)`,否则返回 `None` + /// # Returns + /// Returns `Some(DexEvent)` on successful parsing, otherwise `None` #[inline] pub fn dispatch_compute_budget_instruction( instruction_data: &[u8], @@ -214,7 +214,7 @@ impl EventDispatcher { CommonEventParser::parse_compute_budget_instruction(instruction_data, metadata) } - /// 获取指定协议的 program_id + /// Get program_id for specified protocol #[inline] pub fn get_program_id(protocol: Protocol) -> Pubkey { match protocol { @@ -228,30 +228,30 @@ impl EventDispatcher { } } - /// 批量获取 program_ids + /// Batch get program_ids pub fn get_program_ids(protocols: &[Protocol]) -> Vec { protocols.iter().map(|p| Self::get_program_id(p.clone())).collect() } - /// 解析账户数据 + /// Parse account data /// - /// 根据账户的 discriminator 路由到对应协议的账户解析函数 + /// Route to corresponding protocol account parsing function based on account discriminator /// - /// # 参数 - /// - `protocol`: 协议类型 - /// - `discriminator`: 账户判别器 - /// - `account`: 账户信息 - /// - `metadata`: 事件元数据 + /// # Parameters + /// - `protocol`: Protocol type + /// - `discriminator`: Account discriminator + /// - `account`: Account information + /// - `metadata`: Event metadata /// - /// # 返回 - /// 解析成功返回 `Some(DexEvent)`,否则返回 `None` + /// # Returns + /// Returns `Some(DexEvent)` on successful parsing, otherwise `None` pub fn dispatch_account( protocol: Protocol, discriminator: &[u8], account: &crate::streaming::grpc::AccountPretty, mut metadata: crate::streaming::event_parser::common::EventMetadata, ) -> Option { - // 根据协议类型设置 metadata.protocol + // Set metadata.protocol based on protocol type use crate::streaming::event_parser::common::ProtocolType; metadata.protocol = match protocol { Protocol::PumpFun => ProtocolType::PumpFun, @@ -281,7 +281,7 @@ impl EventDispatcher { raydium_amm_v4::parse_raydium_amm_v4_account_data(discriminator, account, metadata) } Protocol::MeteoraDammV2 => { - // Meteora DAMM 目前不需要解析账户数据,返回 None + // Meteora DAMM currently doesn't need to parse account data, return None None } } diff --git a/src/streaming/event_parser/core/event_parser.rs b/src/streaming/event_parser/core/event_parser.rs index 848c20a..bcd7e27 100644 --- a/src/streaming/event_parser/core/event_parser.rs +++ b/src/streaming/event_parser/core/event_parser.rs @@ -42,7 +42,7 @@ impl EventParser { transaction_index: Option, callback: Arc, ) -> anyhow::Result<()> { - // 创建适配器回调,将所有权回调转换为引用回调 + // Create adapter callback to convert ownership callback to reference callback let adapter_callback = Arc::new(move |event: &DexEvent| { callback(event.clone()); }); @@ -69,7 +69,7 @@ impl EventParser { Vec::with_capacity(message.account_keys.len() + address_table_lookups.len()); accounts_bytes.extend_from_slice(&message.account_keys); accounts_bytes.extend(address_table_lookups); - // 转换为 Pubkey + // Convert to Pubkey let accounts: Vec = accounts_bytes .iter() .filter_map(|account| { @@ -80,7 +80,7 @@ impl EventParser { } }) .collect(); - // 解析指令事件 + // Parse instruction events let instructions = &message.instructions; Self::parse_instruction_events_from_grpc_transaction( protocols, @@ -122,28 +122,28 @@ impl EventParser { transaction_index: Option, callback: Arc, ) -> anyhow::Result<()> { - // 创建适配器回调,将所有权回调转换为引用回调 + // Create adapter callback to convert ownership callback to reference callback let adapter_callback = Arc::new(move |event: &DexEvent| { callback(event.clone()); }); - // 获取交易的指令和账户 + // Get transaction instructions and accounts let compiled_instructions = transaction.message.instructions(); let mut accounts: Vec = accounts.to_vec(); - // 检查交易中是否包含程序 + // Check if transaction contains the program let has_program = accounts .iter() .any(|account| Self::should_handle(protocols, event_type_filter, account)); if has_program { - // 解析每个指令 + // Parse each instruction for (index, instruction) in compiled_instructions.iter().enumerate() { if let Some(program_id) = accounts.get(instruction.program_id_index as usize) { - let program_id = *program_id; // 克隆程序ID,避免借用冲突 + let program_id = *program_id; // Clone program ID to avoid borrow conflicts let inner_instructions = inner_instructions .iter() .find(|inner_instruction| inner_instruction.index == index as u8); if Self::should_handle(protocols, event_type_filter, &program_id) { let max_idx = instruction.accounts.iter().max().unwrap_or(&0); - // 补齐accounts(使用Pubkey::default()) + // Pad accounts (using Pubkey::default()) if *max_idx as usize >= accounts.len() { accounts.resize(*max_idx as usize + 1, Pubkey::default()); } @@ -216,22 +216,22 @@ impl EventParser { transaction_index: Option, callback: Arc Fn(&'a DexEvent) + Send + Sync>, ) -> anyhow::Result<()> { - // 获取交易的指令和账户 + // Get transaction instructions and accounts let mut accounts = accounts.to_vec(); - // 检查交易中是否包含程序 + // Check if transaction contains the program let has_program = accounts .iter() .any(|account| Self::should_handle(protocols, event_type_filter, account)); if has_program { - // 解析每个指令 + // Parse each instruction for (index, instruction) in compiled_instructions.iter().enumerate() { if let Some(program_id) = accounts.get(instruction.program_id_index as usize) { - let program_id = *program_id; // 克隆程序ID,避免借用冲突 + let program_id = *program_id; // Clone program ID to avoid borrow conflicts let inner_instructions = inner_instructions .iter() .find(|inner_instruction| inner_instruction.index == index as u32); let max_idx = instruction.accounts.iter().max().unwrap_or(&0); - // 补齐accounts(使用Pubkey::default()) + // Pad accounts (using Pubkey::default()) if *max_idx as usize >= accounts.len() { accounts.resize(*max_idx as usize + 1, Pubkey::default()); } @@ -311,7 +311,7 @@ impl EventParser { inner_instructions: Option<&yellowstone_grpc_proto::prelude::InnerInstructions>, callback: Arc Fn(&'a DexEvent) + Send + Sync>, ) -> anyhow::Result<()> { - // 添加边界检查以防止越界访问 + // Add bounds check to prevent out-of-bounds access let program_id_index = instruction.program_id_index as usize; if program_id_index >= accounts.len() { return Ok(()); @@ -328,11 +328,11 @@ impl EventParser { _ => 8, }; - // 检查指令数据长度(至少需要 disc_len 字节的 discriminator) + // Check instruction data length (at least disc_len bytes for discriminator) if !is_cu_program && instruction.data.len() < disc_len { return Ok(()); } - // 创建元数据 + // Create metadata let timestamp = block_time.unwrap_or(Timestamp { seconds: 0, nanos: 0 }); let block_time_ms = timestamp.seconds * 1000 + (timestamp.nanos as i64) / 1_000_000; let metadata = EventMetadata::new( @@ -359,24 +359,24 @@ impl EventParser { return Ok(()); } - // 使用 EventDispatcher 匹配协议 + // Use EventDispatcher to match protocol let protocol = match EventDispatcher::match_protocol_by_program_id(&program_id) { Some(p) => p, None => return Ok(()), }; - // 提取 discriminator 和数据 + // Extract discriminator and data let instruction_discriminator = &instruction.data[..disc_len]; let instruction_data = &instruction.data[disc_len..]; - // 构建账户公钥列表 + // Build account pubkey list let account_pubkeys: Vec = instruction .accounts .iter() .filter_map(|&idx| accounts.get(idx as usize).copied()) .collect(); - // 使用 EventDispatcher 解析 instruction 事件 + // Use EventDispatcher to parse instruction event let mut event = match EventDispatcher::dispatch_instruction( protocol.clone(), instruction_discriminator, @@ -388,23 +388,23 @@ impl EventParser { None => return Ok(()), }; - // 处理 inner instructions - 查找对应的 CPI log 进行 merge - // 当 inner_index 有值时,只查找索引大于当前 inner_index 的 CPI log + // Process inner instructions - find corresponding CPI log for merge + // When inner_index has a value, only search for CPI logs with index greater than current inner_index let mut inner_instruction_event: Option = None; if let Some(inner_instructions_ref) = inner_instructions { let current_inner_idx = inner_index.unwrap_or(-1) as i32; - // 并行执行两个任务: 解析 inner event 和提取 swap_data + // Execute two tasks in parallel: parse inner event and extract swap_data let (inner_event_result, swap_data_result) = std::thread::scope(|s| { let inner_event_handle = s.spawn(|| { for (idx, inner_instruction) in inner_instructions_ref.instructions.iter().enumerate() { - // 只查找索引大于当前 inner_index 的 CPI log + // Only search for CPI logs with index greater than current inner_index if (idx as i32) <= current_inner_idx { continue; } let inner_data = &inner_instruction.data; - // 检查长度(需要 16 字节的 discriminator) + // Check length (needs 16 bytes for discriminator) if inner_data.len() < 16 { continue; } @@ -436,7 +436,7 @@ impl EventParser { } }); - // 等待两个任务完成 + // Wait for both tasks to complete (inner_event_handle.join().unwrap(), swap_data_handle.join().unwrap()) }); @@ -446,7 +446,7 @@ impl EventParser { } } - // 特殊处理: PumpFun MIGRATE 指令需要 inner instruction data + // Special handling: PumpFun MIGRATE instruction requires inner instruction data if matches!(protocol, Protocol::PumpFun) { const PUMPFUN_MIGRATE_IX: &[u8] = &[155, 234, 231, 146, 236, 158, 162, 30]; if instruction_discriminator == PUMPFUN_MIGRATE_IX && inner_instruction_event.is_none() @@ -455,12 +455,12 @@ impl EventParser { } } - // 合并事件 + // Merge events if let Some(inner_instruction_event) = inner_instruction_event { merge(&mut event, inner_instruction_event); } - // 设置处理时间(使用高性能时钟) + // Set processing time (using high-performance clock) event.metadata_mut().handle_us = elapsed_micros_since(recv_us); event = Self::process_event(event, bot_wallet); callback(&event); @@ -493,7 +493,7 @@ impl EventParser { inner_instructions: Option<&InnerInstructions>, callback: Arc Fn(&'a DexEvent) + Send + Sync>, ) -> anyhow::Result<()> { - // 添加边界检查以防止越界访问 + // Add bounds check to prevent out-of-bounds access let program_id_index = instruction.program_id_index as usize; if program_id_index >= accounts.len() { return Ok(()); @@ -510,12 +510,12 @@ impl EventParser { _ => 8, }; - // 检查指令数据长度(至少需要 8 字节的 discriminator) + // Check instruction data length (at least 8 bytes for discriminator) if !is_cu_program && instruction.data.len() < disc_len { return Ok(()); } - // 创建元数据 + // Create metadata let timestamp = block_time.unwrap_or(Timestamp { seconds: 0, nanos: 0 }); let block_time_ms = timestamp.seconds * 1000 + (timestamp.nanos as i64) / 1_000_000; let metadata = EventMetadata::new( @@ -542,24 +542,24 @@ impl EventParser { return Ok(()); } - // 使用 EventDispatcher 匹配协议 + // Use EventDispatcher to match protocol let protocol = match EventDispatcher::match_protocol_by_program_id(&program_id) { Some(p) => p, None => return Ok(()), }; - // 提取 discriminator 和数据 + // Extract discriminator and data let instruction_discriminator = &instruction.data[..disc_len]; let instruction_data = &instruction.data[disc_len..]; - // 构建账户公钥列表 + // Build account pubkey list let account_pubkeys: Vec = instruction .accounts .iter() .filter_map(|&idx| accounts.get(idx as usize).copied()) .collect(); - // 使用 EventDispatcher 解析 instruction 事件 + // Use EventDispatcher to parse instruction event let mut event = match EventDispatcher::dispatch_instruction( protocol.clone(), instruction_discriminator, @@ -571,23 +571,23 @@ impl EventParser { None => return Ok(()), }; - // 处理 inner instructions - 查找对应的 CPI log 进行 merge - // 当 inner_index 有值时,只查找索引大于当前 inner_index 的 CPI log + // Process inner instructions - find corresponding CPI log for merge + // When inner_index has a value, only search for CPI logs with index greater than current inner_index let mut inner_instruction_event: Option = None; if let Some(inner_instructions_ref) = inner_instructions { let current_inner_idx = inner_index.unwrap_or(-1) as i32; - // 并行执行两个任务: 解析 inner event 和提取 swap_data + // Execute two tasks in parallel: parse inner event and extract swap_data let (inner_event_result, swap_data_result) = std::thread::scope(|s| { let inner_event_handle = s.spawn(|| { for (idx, inner_instruction) in inner_instructions_ref.instructions.iter().enumerate() { - // 只查找索引大于当前 inner_index 的 CPI log + // Only search for CPI logs with index greater than current inner_index if (idx as i32) <= current_inner_idx { continue; } let inner_data = &inner_instruction.instruction.data; - // 检查长度(需要 16 字节的 discriminator) + // Check length (needs 16 bytes for discriminator) if inner_data.len() < 16 { continue; } @@ -619,7 +619,7 @@ impl EventParser { } }); - // 等待两个任务完成 + // Wait for both tasks to complete (inner_event_handle.join().unwrap(), swap_data_handle.join().unwrap()) }); @@ -629,7 +629,7 @@ impl EventParser { } } - // 特殊处理: PumpFun MIGRATE 指令需要 inner instruction data + // Special handling: PumpFun MIGRATE instruction requires inner instruction data if matches!(protocol, Protocol::PumpFun) { const PUMPFUN_MIGRATE_IX: &[u8] = &[155, 234, 231, 146, 236, 158, 162, 30]; if instruction_discriminator == PUMPFUN_MIGRATE_IX && inner_instruction_event.is_none() @@ -638,12 +638,12 @@ impl EventParser { } } - // 合并事件 + // Merge events if let Some(inner_instruction_event) = inner_instruction_event { merge(&mut event, inner_instruction_event); } - // 设置处理时间(使用高性能时钟) + // Set processing time (using high-performance clock) event.metadata_mut().handle_us = elapsed_micros_since(recv_us); event = Self::process_event(event, bot_wallet); callback(&event); @@ -663,7 +663,7 @@ impl EventParser { _event_type_filter: Option<&EventTypeFilter>, program_id: &Pubkey, ) -> bool { - // 使用 EventDispatcher 来匹配协议 + // Use EventDispatcher to match protocol if let Some(protocol) = EventDispatcher::match_protocol_by_program_id(program_id) { protocols.contains(&protocol) } else if EventDispatcher::is_compute_budget_program(program_id) { @@ -730,6 +730,13 @@ impl EventParser { } DexEvent::PumpSwapBuyEvent(trade_info) } + DexEvent::PumpSwapBuyExactQuoteInEvent(mut trade_info) => { + if let Some(swap_data) = trade_info.metadata.swap_data.as_mut() { + swap_data.from_amount = trade_info.user_quote_amount_in; + swap_data.to_amount = trade_info.base_amount_out; + } + DexEvent::PumpSwapBuyExactQuoteInEvent(trade_info) + } DexEvent::PumpSwapSellEvent(mut trade_info) => { if let Some(swap_data) = trade_info.metadata.swap_data.as_mut() { swap_data.from_amount = trade_info.base_amount_in; diff --git a/src/streaming/event_parser/core/merger_event.rs b/src/streaming/event_parser/core/merger_event.rs index 8fcfd12..2a8fc0d 100644 --- a/src/streaming/event_parser/core/merger_event.rs +++ b/src/streaming/event_parser/core/merger_event.rs @@ -140,6 +140,48 @@ pub fn merge(instruction_event: &mut DexEvent, cpi_log_event: DexEvent) { e.coin_creator = cpie.coin_creator; e.coin_creator_fee_basis_points = cpie.coin_creator_fee_basis_points; e.coin_creator_fee = cpie.coin_creator_fee; + e.track_volume = cpie.track_volume; + e.total_unclaimed_tokens = cpie.total_unclaimed_tokens; + e.total_claimed_tokens = cpie.total_claimed_tokens; + e.current_sol_volume = cpie.current_sol_volume; + e.last_update_timestamp = cpie.last_update_timestamp; + e.min_base_amount_out = cpie.min_base_amount_out; + e.ix_name = cpie.ix_name; + } + _ => {} + }, + DexEvent::PumpSwapBuyExactQuoteInEvent(e) => match cpi_log_event { + DexEvent::PumpSwapBuyExactQuoteInEvent(cpie) => { + e.timestamp = cpie.timestamp; + e.base_amount_out = cpie.base_amount_out; + e.max_quote_amount_in = cpie.max_quote_amount_in; + e.user_base_token_reserves = cpie.user_base_token_reserves; + e.user_quote_token_reserves = cpie.user_quote_token_reserves; + e.pool_base_token_reserves = cpie.pool_base_token_reserves; + e.pool_quote_token_reserves = cpie.pool_quote_token_reserves; + e.quote_amount_in = cpie.quote_amount_in; + e.lp_fee_basis_points = cpie.lp_fee_basis_points; + e.lp_fee = cpie.lp_fee; + e.protocol_fee_basis_points = cpie.protocol_fee_basis_points; + e.protocol_fee = cpie.protocol_fee; + e.quote_amount_in_with_lp_fee = cpie.quote_amount_in_with_lp_fee; + e.user_quote_amount_in = cpie.user_quote_amount_in; + e.pool = cpie.pool; + e.user = cpie.user; + e.user_base_token_account = cpie.user_base_token_account; + e.user_quote_token_account = cpie.user_quote_token_account; + e.protocol_fee_recipient = cpie.protocol_fee_recipient; + e.protocol_fee_recipient_token_account = cpie.protocol_fee_recipient_token_account; + e.coin_creator = cpie.coin_creator; + e.coin_creator_fee_basis_points = cpie.coin_creator_fee_basis_points; + e.coin_creator_fee = cpie.coin_creator_fee; + e.track_volume = cpie.track_volume; + e.total_unclaimed_tokens = cpie.total_unclaimed_tokens; + e.total_claimed_tokens = cpie.total_claimed_tokens; + e.current_sol_volume = cpie.current_sol_volume; + e.last_update_timestamp = cpie.last_update_timestamp; + e.min_base_amount_out = cpie.min_base_amount_out; + e.ix_name = cpie.ix_name; } _ => {} }, diff --git a/src/streaming/event_parser/core/traits.rs b/src/streaming/event_parser/core/traits.rs index 0dc95f8..1e8b434 100755 --- a/src/streaming/event_parser/core/traits.rs +++ b/src/streaming/event_parser/core/traits.rs @@ -38,6 +38,7 @@ pub enum DexEvent { // PumpSwap events PumpSwapBuyEvent(PumpSwapBuyEvent), + PumpSwapBuyExactQuoteInEvent(PumpSwapBuyExactQuoteInEvent), PumpSwapSellEvent(PumpSwapSellEvent), PumpSwapCreatePoolEvent(PumpSwapCreatePoolEvent), PumpSwapDepositEvent(PumpSwapDepositEvent), @@ -107,6 +108,7 @@ impl DexEvent { DexEvent::PumpFunBondingCurveAccountEvent(e) => &e.metadata, DexEvent::PumpFunGlobalAccountEvent(e) => &e.metadata, DexEvent::PumpSwapBuyEvent(e) => &e.metadata, + DexEvent::PumpSwapBuyExactQuoteInEvent(e) => &e.metadata, DexEvent::PumpSwapSellEvent(e) => &e.metadata, DexEvent::PumpSwapCreatePoolEvent(e) => &e.metadata, DexEvent::PumpSwapDepositEvent(e) => &e.metadata, @@ -166,6 +168,7 @@ impl DexEvent { DexEvent::PumpFunBondingCurveAccountEvent(e) => &mut e.metadata, DexEvent::PumpFunGlobalAccountEvent(e) => &mut e.metadata, DexEvent::PumpSwapBuyEvent(e) => &mut e.metadata, + DexEvent::PumpSwapBuyExactQuoteInEvent(e) => &mut e.metadata, DexEvent::PumpSwapSellEvent(e) => &mut e.metadata, DexEvent::PumpSwapCreatePoolEvent(e) => &mut e.metadata, DexEvent::PumpSwapDepositEvent(e) => &mut e.metadata, diff --git a/src/streaming/event_parser/protocols/block/block_meta_event.rs b/src/streaming/event_parser/protocols/block/block_meta_event.rs index 6d606c6..7d68a50 100644 --- a/src/streaming/event_parser/protocols/block/block_meta_event.rs +++ b/src/streaming/event_parser/protocols/block/block_meta_event.rs @@ -3,7 +3,7 @@ use borsh::BorshDeserialize; use serde::{Deserialize, Serialize}; use solana_sdk::signature::Signature; -/// Block元数据事件 +/// Block metadata event #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)] pub struct BlockMetaEvent { #[borsh(skip)] diff --git a/src/streaming/event_parser/protocols/bonk/parser.rs b/src/streaming/event_parser/protocols/bonk/parser.rs index bc7bcfd..e6ebed7 100755 --- a/src/streaming/event_parser/protocols/bonk/parser.rs +++ b/src/streaming/event_parser/protocols/bonk/parser.rs @@ -15,9 +15,9 @@ use crate::streaming::event_parser::{ pub const BONK_PROGRAM_ID: Pubkey = solana_sdk::pubkey!("LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj"); -/// 解析 Bonk instruction data +/// Parse Bonk instruction data /// -/// 根据判别器路由到具体的 instruction 解析函数 +/// Route to specific instruction parser based on discriminator pub fn parse_bonk_instruction_data( discriminator: &[u8], data: &[u8], @@ -56,9 +56,9 @@ pub fn parse_bonk_instruction_data( } } -/// 解析 Bonk inner instruction data +/// Parse Bonk inner instruction data /// -/// 根据判别器路由到具体的 inner instruction 解析函数 +/// Route to specific inner instruction parser based on discriminator pub fn parse_bonk_inner_instruction_data( discriminator: &[u8], data: &[u8], @@ -75,9 +75,9 @@ pub fn parse_bonk_inner_instruction_data( } } -/// 解析 Bonk 账户数据 +/// Parse Bonk account data /// -/// 根据判别器路由到具体的账户解析函数 +/// Route to specific account parser based on discriminator pub fn parse_bonk_account_data( discriminator: &[u8], account: &crate::streaming::grpc::AccountPretty, diff --git a/src/streaming/event_parser/protocols/pumpfun/parser.rs b/src/streaming/event_parser/protocols/pumpfun/parser.rs index 5c228fb..c56be4d 100755 --- a/src/streaming/event_parser/protocols/pumpfun/parser.rs +++ b/src/streaming/event_parser/protocols/pumpfun/parser.rs @@ -9,13 +9,13 @@ use crate::streaming::event_parser::{ }; use solana_sdk::pubkey::Pubkey; -/// PumpFun程序ID +/// PumpFun program ID pub const PUMPFUN_PROGRAM_ID: Pubkey = solana_sdk::pubkey!("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"); -/// 解析 PumpFun instruction data +/// Parse PumpFun instruction data /// -/// 根据判别器路由到具体的 instruction 解析函数 +/// Route to specific instruction parser based on discriminator pub fn parse_pumpfun_instruction_data( discriminator: &[u8], data: &[u8], @@ -34,9 +34,9 @@ pub fn parse_pumpfun_instruction_data( } } -/// 解析 PumpFun inner instruction data +/// Parse PumpFun inner instruction data /// -/// 根据判别器路由到具体的 inner instruction 解析函数 +/// Route to specific inner instruction parser based on discriminator pub fn parse_pumpfun_inner_instruction_data( discriminator: &[u8], data: &[u8], @@ -52,9 +52,9 @@ pub fn parse_pumpfun_inner_instruction_data( } } -/// 解析 PumpFun 账户数据 +/// Parse PumpFun account data /// -/// 根据判别器路由到具体的账户解析函数 +/// Route to specific account parser based on discriminator pub fn parse_pumpfun_account_data( discriminator: &[u8], account: &crate::streaming::grpc::AccountPretty, diff --git a/src/streaming/event_parser/protocols/pumpswap/events.rs b/src/streaming/event_parser/protocols/pumpswap/events.rs index 67b1188..9b03456 100755 --- a/src/streaming/event_parser/protocols/pumpswap/events.rs +++ b/src/streaming/event_parser/protocols/pumpswap/events.rs @@ -5,7 +5,7 @@ use solana_sdk::pubkey::Pubkey; use crate::streaming::event_parser::common::EventMetadata; use crate::streaming::event_parser::protocols::pumpswap::types::{GlobalConfig, Pool}; -/// 买入事件 +/// Buy event #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)] pub struct PumpSwapBuyEvent { #[borsh(skip)] @@ -38,6 +38,66 @@ pub struct PumpSwapBuyEvent { pub total_claimed_tokens: u64, pub current_sol_volume: u64, pub last_update_timestamp: i64, + pub min_base_amount_out: u64, + pub ix_name: String, + #[borsh(skip)] + pub base_mint: Pubkey, + #[borsh(skip)] + pub quote_mint: Pubkey, + #[borsh(skip)] + pub pool_base_token_account: Pubkey, + #[borsh(skip)] + pub pool_quote_token_account: Pubkey, + #[borsh(skip)] + pub coin_creator_vault_ata: Pubkey, + #[borsh(skip)] + pub coin_creator_vault_authority: Pubkey, + #[borsh(skip)] + pub base_token_program: Pubkey, + #[borsh(skip)] + pub quote_token_program: Pubkey, +} + +/// Buy event (ix: `buy_exact_quote_in`) +/// +/// Same on-chain layout as `PumpSwapBuyEvent` (IDL `BuyEvent`), +/// but represented as a different type to distinguish in the streamer. +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)] +pub struct PumpSwapBuyExactQuoteInEvent { + #[borsh(skip)] + pub metadata: EventMetadata, + pub timestamp: i64, + pub base_amount_out: u64, + pub max_quote_amount_in: u64, + pub user_base_token_reserves: u64, + pub user_quote_token_reserves: u64, + pub pool_base_token_reserves: u64, + pub pool_quote_token_reserves: u64, + pub quote_amount_in: u64, + pub lp_fee_basis_points: u64, + pub lp_fee: u64, + pub protocol_fee_basis_points: u64, + pub protocol_fee: u64, + pub quote_amount_in_with_lp_fee: u64, + pub user_quote_amount_in: u64, + pub pool: Pubkey, + pub user: Pubkey, + pub user_base_token_account: Pubkey, + pub user_quote_token_account: Pubkey, + pub protocol_fee_recipient: Pubkey, + pub protocol_fee_recipient_token_account: Pubkey, + pub coin_creator: Pubkey, + pub coin_creator_fee_basis_points: u64, + pub coin_creator_fee: u64, + pub track_volume: bool, + pub total_unclaimed_tokens: u64, + pub total_claimed_tokens: u64, + pub current_sol_volume: u64, + pub last_update_timestamp: i64, + pub min_base_amount_out: u64, + pub ix_name: String, + #[borsh(skip)] + pub spendable_quote_in: u64, #[borsh(skip)] pub base_mint: Pubkey, #[borsh(skip)] @@ -62,10 +122,19 @@ pub fn pump_swap_buy_event_log_decode(data: &[u8]) -> Option { if data.len() < PUMP_SWAP_BUY_EVENT_LOG_SIZE { return None; } - borsh::from_slice::(&data[..PUMP_SWAP_BUY_EVENT_LOG_SIZE]).ok() + // Use the entire buffer to correctly deserialize the String ix_name field + borsh::from_slice::(data).ok() } -/// 卖出事件 +pub fn pump_swap_buy_exact_quote_in_event_log_decode(data: &[u8]) -> Option { + if data.len() < PUMP_SWAP_BUY_EVENT_LOG_SIZE { + return None; + } + // Use the entire buffer to correctly deserialize the String ix_name field + borsh::from_slice::(data).ok() +} + +/// Sell event #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)] pub struct PumpSwapSellEvent { #[borsh(skip)] @@ -120,7 +189,7 @@ pub fn pump_swap_sell_event_log_decode(data: &[u8]) -> Option borsh::from_slice::(&data[..PUMP_SWAP_SELL_EVENT_LOG_SIZE]).ok() } -/// 创建池子事件 +/// Create pool event #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)] pub struct PumpSwapCreatePoolEvent { #[borsh(skip)] @@ -162,7 +231,7 @@ pub fn pump_swap_create_pool_event_log_decode(data: &[u8]) -> Option(&data[..PUMP_SWAP_CREATE_POOL_EVENT_LOG_SIZE]).ok() } -/// 存款事件 +/// Deposit event #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)] pub struct PumpSwapDepositEvent { #[borsh(skip)] @@ -202,7 +271,7 @@ pub fn pump_swap_deposit_event_log_decode(data: &[u8]) -> Option(&data[..PUMP_SWAP_DEPOSIT_EVENT_LOG_SIZE]).ok() } -/// 提款事件 +/// Withdraw event #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)] pub struct PumpSwapWithdrawEvent { #[borsh(skip)] @@ -242,7 +311,7 @@ pub fn pump_swap_withdraw_event_log_decode(data: &[u8]) -> Option(&data[..PUMP_SWAP_WITHDRAW_EVENT_LOG_SIZE]).ok() } -/// 全局配置 +/// Global config account event #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)] pub struct PumpSwapGlobalConfigAccountEvent { #[borsh(skip)] @@ -255,7 +324,7 @@ pub struct PumpSwapGlobalConfigAccountEvent { pub global_config: GlobalConfig, } -/// 池 +/// Pool account event #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)] pub struct PumpSwapPoolAccountEvent { #[borsh(skip)] @@ -268,9 +337,9 @@ pub struct PumpSwapPoolAccountEvent { pub pool: Pool, } -/// 事件鉴别器常量 +/// Event discriminator constants pub mod discriminators { - // 事件鉴别器 + // Event discriminators // pub const BUY_EVENT: &str = "0xe445a52e51cb9a1d67f4521f2cf57777"; pub const BUY_EVENT: &[u8] = &[228, 69, 165, 46, 81, 203, 154, 29, 103, 244, 82, 31, 44, 245, 119, 119]; @@ -287,14 +356,15 @@ pub mod discriminators { pub const WITHDRAW_EVENT: &[u8] = &[228, 69, 165, 46, 81, 203, 154, 29, 22, 9, 133, 26, 160, 44, 71, 192]; - // 指令鉴别器 + // Instruction discriminators pub const BUY_IX: &[u8] = &[102, 6, 61, 18, 1, 218, 235, 234]; + pub const BUY_EXACT_QUOTE_IN_IX: &[u8] = &[198, 46, 21, 82, 180, 217, 232, 112]; pub const SELL_IX: &[u8] = &[51, 230, 133, 164, 1, 127, 131, 173]; pub const CREATE_POOL_IX: &[u8] = &[233, 146, 209, 142, 207, 104, 64, 188]; pub const DEPOSIT_IX: &[u8] = &[242, 35, 198, 137, 82, 225, 242, 182]; pub const WITHDRAW_IX: &[u8] = &[183, 18, 70, 156, 148, 109, 161, 34]; - // 账户鉴别器 + // Account discriminators pub const GLOBAL_CONFIG_ACCOUNT: &[u8] = &[149, 8, 156, 202, 160, 252, 176, 217]; pub const POOL_ACCOUNT: &[u8] = &[241, 154, 109, 4, 17, 177, 109, 188]; } diff --git a/src/streaming/event_parser/protocols/pumpswap/parser.rs b/src/streaming/event_parser/protocols/pumpswap/parser.rs index 5bc5dd9..9178930 100755 --- a/src/streaming/event_parser/protocols/pumpswap/parser.rs +++ b/src/streaming/event_parser/protocols/pumpswap/parser.rs @@ -1,22 +1,24 @@ use crate::streaming::event_parser::{ common::{read_u64_le, EventMetadata, EventType}, protocols::pumpswap::{ - discriminators, pump_swap_buy_event_log_decode, pump_swap_create_pool_event_log_decode, - pump_swap_deposit_event_log_decode, pump_swap_sell_event_log_decode, - pump_swap_withdraw_event_log_decode, PumpSwapBuyEvent, PumpSwapCreatePoolEvent, - PumpSwapDepositEvent, PumpSwapSellEvent, PumpSwapWithdrawEvent, + discriminators, pump_swap_buy_event_log_decode, + pump_swap_buy_exact_quote_in_event_log_decode, + pump_swap_create_pool_event_log_decode, pump_swap_deposit_event_log_decode, + pump_swap_sell_event_log_decode, pump_swap_withdraw_event_log_decode, PumpSwapBuyEvent, + PumpSwapBuyExactQuoteInEvent, PumpSwapCreatePoolEvent, PumpSwapDepositEvent, + PumpSwapSellEvent, PumpSwapWithdrawEvent, }, DexEvent, }; use solana_sdk::pubkey::Pubkey; -/// PumpSwap程序ID +/// PumpSwap program ID pub const PUMPSWAP_PROGRAM_ID: Pubkey = solana_sdk::pubkey!("pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA"); -/// 解析 PumpSwap instruction data +/// Parse PumpSwap instruction data /// -/// 根据判别器路由到具体的 instruction 解析函数 +/// Routes to specific instruction parser based on discriminator pub fn parse_pumpswap_instruction_data( discriminator: &[u8], data: &[u8], @@ -25,6 +27,7 @@ pub fn parse_pumpswap_instruction_data( ) -> Option { match discriminator { discriminators::BUY_IX => parse_buy_instruction(data, accounts, metadata), + discriminators::BUY_EXACT_QUOTE_IN_IX => parse_buy_exact_quote_in_instruction(data, accounts, metadata), discriminators::SELL_IX => parse_sell_instruction(data, accounts, metadata), discriminators::CREATE_POOL_IX => { parse_create_pool_instruction(data, accounts, metadata) @@ -35,9 +38,9 @@ pub fn parse_pumpswap_instruction_data( } } -/// 解析 PumpSwap inner instruction data +/// Parse PumpSwap inner instruction data /// -/// 根据判别器路由到具体的 inner instruction 解析函数 +/// Routes to specific inner instruction parser based on discriminator pub fn parse_pumpswap_inner_instruction_data( discriminator: &[u8], data: &[u8], @@ -56,9 +59,9 @@ pub fn parse_pumpswap_inner_instruction_data( } -/// 解析 PumpSwap 账户数据 +/// Parse PumpSwap account data /// -/// 根据判别器路由到具体的账户解析函数 +/// Routes to specific account parser based on discriminator pub fn parse_pumpswap_account_data( discriminator: &[u8], account: &crate::streaming::grpc::AccountPretty, @@ -75,17 +78,25 @@ pub fn parse_pumpswap_account_data( } } -/// 解析买入日志事件 +/// Parse buy event log fn parse_buy_inner_instruction(data: &[u8], metadata: EventMetadata) -> Option { - // Note: event_type will be set by instruction parser - if let Some(event) = pump_swap_buy_event_log_decode(data) { - Some(DexEvent::PumpSwapBuyEvent(PumpSwapBuyEvent { metadata, ..event })) - } else { - None + // First try to decode as buy_exact_quote_in to read ix_name + if let Some(event) = pump_swap_buy_exact_quote_in_event_log_decode(data) { + if event.ix_name == "buy_exact_quote_in" { + return Some(DexEvent::PumpSwapBuyExactQuoteInEvent(PumpSwapBuyExactQuoteInEvent { + metadata, + ..event + })); + } } + + // If not buy_exact_quote_in, decode as normal buy + pump_swap_buy_event_log_decode(data).map(|event| { + DexEvent::PumpSwapBuyEvent(PumpSwapBuyEvent { metadata, ..event }) + }) } -/// 解析卖出日志事件 +/// Parse sell event log fn parse_sell_inner_instruction(data: &[u8], metadata: EventMetadata) -> Option { // Note: event_type will be set by instruction parser if let Some(event) = pump_swap_sell_event_log_decode(data) { @@ -95,7 +106,7 @@ fn parse_sell_inner_instruction(data: &[u8], metadata: EventMetadata) -> Option< } } -/// 解析创建池子日志事件 +/// Parse create pool event log fn parse_create_pool_inner_instruction( data: &[u8], metadata: EventMetadata, @@ -108,7 +119,7 @@ fn parse_create_pool_inner_instruction( } } -/// 解析存款日志事件 +/// Parse deposit event log fn parse_deposit_inner_instruction(data: &[u8], metadata: EventMetadata) -> Option { // Note: event_type will be set by instruction parser if let Some(event) = pump_swap_deposit_event_log_decode(data) { @@ -118,7 +129,7 @@ fn parse_deposit_inner_instruction(data: &[u8], metadata: EventMetadata) -> Opti } } -/// 解析提款日志事件 +/// Parse withdraw event log fn parse_withdraw_inner_instruction(data: &[u8], metadata: EventMetadata) -> Option { // Note: event_type will be set by instruction parser if let Some(event) = pump_swap_withdraw_event_log_decode(data) { @@ -128,7 +139,7 @@ fn parse_withdraw_inner_instruction(data: &[u8], metadata: EventMetadata) -> Opt } } -/// 解析买入指令事件 +/// Parse buy instruction fn parse_buy_instruction( data: &[u8], accounts: &[Pubkey], @@ -165,7 +176,46 @@ fn parse_buy_instruction( })) } -/// 解析卖出指令事件 +/// Parse `buy_exact_quote_in` instruction +fn parse_buy_exact_quote_in_instruction( + data: &[u8], + accounts: &[Pubkey], + mut metadata: EventMetadata, +) -> Option { + metadata.event_type = EventType::PumpSwapBuyExactQuoteIn; + + if data.len() < 16 || accounts.len() < 13 { + return None; + } + + let spendable_quote_in = read_u64_le(data, 0)?; + let min_base_amount_out = read_u64_le(data, 8)?; + + Some(DexEvent::PumpSwapBuyExactQuoteInEvent( + PumpSwapBuyExactQuoteInEvent { + metadata, + spendable_quote_in, + min_base_amount_out, + pool: accounts[0], + user: accounts[1], + base_mint: accounts[3], + quote_mint: accounts[4], + user_base_token_account: accounts[5], + user_quote_token_account: accounts[6], + pool_base_token_account: accounts[7], + pool_quote_token_account: accounts[8], + protocol_fee_recipient: accounts[9], + protocol_fee_recipient_token_account: accounts[10], + base_token_program: accounts[11], + quote_token_program: accounts[12], + coin_creator_vault_ata: accounts.get(17).copied().unwrap_or_default(), + coin_creator_vault_authority: accounts.get(18).copied().unwrap_or_default(), + ..Default::default() + }, + )) +} + +/// Parse sell instruction fn parse_sell_instruction( data: &[u8], accounts: &[Pubkey], @@ -202,7 +252,7 @@ fn parse_sell_instruction( })) } -/// 解析创建池子指令事件 +/// Parse create pool instruction fn parse_create_pool_instruction( data: &[u8], accounts: &[Pubkey], @@ -243,7 +293,7 @@ fn parse_create_pool_instruction( })) } -/// 解析存款指令事件 +/// Parse deposit instruction fn parse_deposit_instruction( data: &[u8], accounts: &[Pubkey], @@ -277,7 +327,7 @@ fn parse_deposit_instruction( })) } -/// 解析提款指令事件 +/// Parse withdraw instruction fn parse_withdraw_instruction( data: &[u8], accounts: &[Pubkey], diff --git a/src/streaming/event_parser/protocols/types.rs b/src/streaming/event_parser/protocols/types.rs index 3d864dc..c1647a3 100755 --- a/src/streaming/event_parser/protocols/types.rs +++ b/src/streaming/event_parser/protocols/types.rs @@ -7,7 +7,7 @@ use crate::streaming::event_parser::protocols::{ use anyhow::{anyhow, Result}; use solana_sdk::pubkey::Pubkey; -/// 支持的协议 +/// Supported protocols #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum Protocol { PumpSwap, diff --git a/src/streaming/grpc/connection.rs b/src/streaming/grpc/connection.rs index bfbc8f5..6261526 100644 --- a/src/streaming/grpc/connection.rs +++ b/src/streaming/grpc/connection.rs @@ -6,7 +6,7 @@ use crate::streaming::common::constants::{ DEFAULT_CONNECT_TIMEOUT, DEFAULT_REQUEST_TIMEOUT, DEFAULT_MAX_DECODING_MESSAGE_SIZE }; -/// gRPC连接池 - 简化版本 +/// gRPC connection pool - simplified version pub struct GrpcConnectionPool { endpoint: String, x_token: Option, diff --git a/src/streaming/grpc/mod.rs b/src/streaming/grpc/mod.rs index 842e798..ed2772e 100644 --- a/src/streaming/grpc/mod.rs +++ b/src/streaming/grpc/mod.rs @@ -1,16 +1,16 @@ -// gRPC 相关模块 +// gRPC related modules pub mod connection; pub mod pool; pub mod subscription; pub mod types; -// 重新导出主要类型 +// Re-export main types pub use connection::*; pub use pool::*; pub use subscription::*; pub use types::*; -// 从公用模块重新导出 +// Re-export from common modules pub use crate::streaming::common::{ ConnectionConfig, MetricsManager, PerformanceMetrics, StreamClientConfig as ClientConfig, }; diff --git a/src/streaming/grpc/pool.rs b/src/streaming/grpc/pool.rs index c8e6b88..4e993e2 100644 --- a/src/streaming/grpc/pool.rs +++ b/src/streaming/grpc/pool.rs @@ -9,13 +9,13 @@ use yellowstone_grpc_proto::{ prost_types::Timestamp, }; -/// 通用对象池特征 +/// Generic object pool trait pub trait ObjectPool { fn acquire(&self) -> PooledObject; fn return_object(&self, obj: Box); } -/// 带自动归还的智能指针 +/// Smart pointer with automatic return pub struct PooledObject { object: Option>, pool: Arc>>>, @@ -36,7 +36,7 @@ impl Drop for PooledObject { if pool.len() < self.max_size { pool.push_back(obj); } - // 超过最大容量时直接丢弃 + // Discard when exceeding max capacity } } } @@ -55,7 +55,7 @@ impl std::ops::DerefMut for PooledObject { } } -/// AccountPretty 对象池 +/// AccountPretty object pool pub struct AccountPrettyPool { pool: Arc>>>, max_size: usize, @@ -65,7 +65,7 @@ impl AccountPrettyPool { pub fn new(initial_size: usize, max_size: usize) -> Self { let mut pool = VecDeque::with_capacity(initial_size); - // 预分配对象 + // Pre-allocate objects for _ in 0..initial_size { pool.push_back(Box::new(AccountPretty::default())); } @@ -84,7 +84,7 @@ impl AccountPrettyPool { } } -/// 带自动归还的 AccountPretty +/// AccountPretty with automatic return pub struct PooledAccountPretty { account: Box, pool: Arc>>>, @@ -92,7 +92,7 @@ pub struct PooledAccountPretty { } impl PooledAccountPretty { - /// 从 gRPC 更新重置数据 + /// Reset data from gRPC update pub fn reset_from_update(&mut self, account_update: SubscribeUpdateAccount) { let account_info = account_update.account.unwrap(); @@ -109,7 +109,7 @@ impl PooledAccountPretty { self.account.owner = Pubkey::try_from(account_info.owner.as_slice()).expect("valid pubkey"); self.account.rent_epoch = account_info.rent_epoch; - // 优化数据字段的重用 + // Optimize data field reuse let new_data = account_info.data; if self.account.data.capacity() >= new_data.len() { self.account.data.clear(); @@ -126,7 +126,7 @@ impl Drop for PooledAccountPretty { fn drop(&mut self) { let mut pool = self.pool.lock().unwrap(); if pool.len() < self.max_size { - // 清理敏感数据 + // Clear sensitive data self.account.data.clear(); self.account.signature = Signature::default(); self.account.pubkey = Pubkey::default(); @@ -150,7 +150,7 @@ impl std::ops::DerefMut for PooledAccountPretty { } } -/// BlockMetaPretty 对象池 +/// BlockMetaPretty object pool pub struct BlockMetaPrettyPool { pool: Arc>>>, max_size: usize, @@ -160,7 +160,7 @@ impl BlockMetaPrettyPool { pub fn new(initial_size: usize, max_size: usize) -> Self { let mut pool = VecDeque::with_capacity(initial_size); - // 预分配对象 + // Pre-allocate objects for _ in 0..initial_size { pool.push_back(Box::new(BlockMetaPretty::default())); } @@ -179,7 +179,7 @@ impl BlockMetaPrettyPool { } } -/// 带自动归还的 BlockMetaPretty +/// BlockMetaPretty with automatic return pub struct PooledBlockMetaPretty { block_meta: Box, pool: Arc>>>, @@ -187,7 +187,7 @@ pub struct PooledBlockMetaPretty { } impl PooledBlockMetaPretty { - /// 从 gRPC 更新重置数据 + /// Reset data from gRPC update pub fn reset_from_update( &mut self, block_update: SubscribeUpdateBlockMeta, @@ -204,7 +204,7 @@ impl Drop for PooledBlockMetaPretty { fn drop(&mut self) { let mut pool = self.pool.lock().unwrap(); if pool.len() < self.max_size { - // 清理数据 + // Clear data self.block_meta.block_hash.clear(); self.block_meta.block_time = None; pool.push_back(std::mem::take(&mut self.block_meta)); @@ -226,7 +226,7 @@ impl std::ops::DerefMut for PooledBlockMetaPretty { } } -/// TransactionPretty 对象池 +/// TransactionPretty object pool pub struct TransactionPrettyPool { pool: Arc>>>, max_size: usize, @@ -236,7 +236,7 @@ impl TransactionPrettyPool { pub fn new(initial_size: usize, max_size: usize) -> Self { let mut pool = VecDeque::with_capacity(initial_size); - // 预分配对象 + // Pre-allocate objects for _ in 0..initial_size { pool.push_back(Box::new(TransactionPretty::default())); } @@ -259,7 +259,7 @@ impl TransactionPrettyPool { } } -/// 带自动归还的 TransactionPretty +/// TransactionPretty with automatic return pub struct PooledTransactionPretty { transaction: Box, pool: Arc>>>, @@ -267,7 +267,7 @@ pub struct PooledTransactionPretty { } impl PooledTransactionPretty { - /// 从 gRPC 更新重置数据 + /// Reset data from gRPC update pub fn reset_from_update( &mut self, tx_update: SubscribeUpdateTransaction, @@ -278,7 +278,7 @@ impl PooledTransactionPretty { self.transaction.slot = tx_update.slot; self.transaction.transaction_index = Some(tx.index); self.transaction.block_time = block_time; - self.transaction.block_hash.clear(); // 重置 block_hash + self.transaction.block_hash.clear(); // Reset block_hash self.transaction.signature = Signature::try_from(tx.signature.as_slice()).expect("valid signature"); self.transaction.is_vote = tx.is_vote; @@ -291,7 +291,7 @@ impl Drop for PooledTransactionPretty { fn drop(&mut self) { let mut pool = self.pool.lock().unwrap(); if pool.len() < self.max_size { - // 清理数据 + // Clear data self.transaction.block_hash.clear(); self.transaction.block_time = None; self.transaction.signature = Signature::default(); @@ -314,7 +314,7 @@ impl std::ops::DerefMut for PooledTransactionPretty { } } -/// EventPretty 对象池(组合池) +/// EventPretty object pool (composite pool) pub struct EventPrettyPool { account_pool: AccountPrettyPool, block_pool: BlockMetaPrettyPool, @@ -330,23 +330,23 @@ impl EventPrettyPool { } } - /// 获取账户事件对象 + /// Get account event object pub fn acquire_account(&self) -> PooledAccountPretty { self.account_pool.acquire() } - /// 获取区块事件对象 + /// Get block event object pub fn acquire_block(&self) -> PooledBlockMetaPretty { self.block_pool.acquire() } - /// 获取交易事件对象 + /// Get transaction event object pub fn acquire_transaction(&self) -> PooledTransactionPretty { self.transaction_pool.acquire() } } -/// 对象池管理器(单例) +/// Object pool manager (singleton) pub struct PoolManager { event_pool: EventPrettyPool, } @@ -367,18 +367,18 @@ impl Default for PoolManager { } } -/// 工厂函数用于创建优化的 EventPretty +/// Factory functions for creating optimized EventPretty impl EventPrettyPool { - /// 创建账户事件 - 使用对象池优化 + /// Create account event - optimized with object pool pub fn create_account_event_optimized(&self, update: SubscribeUpdateAccount) -> AccountPretty { let mut pooled_account = self.acquire_account(); pooled_account.reset_from_update(update); - // 移动数据而不是克隆,避免多余的内存分配 + // Move data instead of cloning to avoid unnecessary memory allocation let result = std::mem::replace(pooled_account.deref_mut(), AccountPretty::default()); result } - /// 创建区块事件 - 使用对象池优化 + /// Create block event - optimized with object pool pub fn create_block_event_optimized( &self, update: SubscribeUpdateBlockMeta, @@ -386,12 +386,12 @@ impl EventPrettyPool { ) -> BlockMetaPretty { let mut pooled_block = self.acquire_block(); pooled_block.reset_from_update(update, block_time); - // 移动数据而不是克隆 + // Move data instead of cloning let result = std::mem::replace(pooled_block.deref_mut(), BlockMetaPretty::default()); result } - /// 创建交易事件 - 使用对象池优化 + /// Create transaction event - optimized with object pool pub fn create_transaction_event_optimized( &self, update: SubscribeUpdateTransaction, @@ -399,27 +399,27 @@ impl EventPrettyPool { ) -> TransactionPretty { let mut pooled_tx = self.acquire_transaction(); pooled_tx.reset_from_update(update, block_time); - // 移动数据而不是克隆 + // Move data instead of cloning let result = std::mem::replace(pooled_tx.deref_mut(), TransactionPretty::default()); result } } -// 全局池管理器实例 +// Global pool manager instance lazy_static::lazy_static! { pub static ref GLOBAL_POOL_MANAGER: PoolManager = PoolManager::new(); } -/// 便捷的全局工厂函数 +/// Convenient global factory functions pub mod factory { use super::*; - /// 使用对象池创建账户事件(推荐用于高性能场景) + /// Create account event using object pool (recommended for high-performance scenarios) pub fn create_account_pretty_pooled(update: SubscribeUpdateAccount) -> AccountPretty { GLOBAL_POOL_MANAGER.get_event_pool().create_account_event_optimized(update) } - /// 使用对象池创建区块事件(推荐用于高性能场景) + /// Create block event using object pool (recommended for high-performance scenarios) pub fn create_block_meta_pretty_pooled( update: SubscribeUpdateBlockMeta, block_time: Option, @@ -427,7 +427,7 @@ pub mod factory { GLOBAL_POOL_MANAGER.get_event_pool().create_block_event_optimized(update, block_time) } - /// 使用对象池创建交易事件(推荐用于高性能场景) + /// Create transaction event using object pool (recommended for high-performance scenarios) pub fn create_transaction_pretty_pooled( update: SubscribeUpdateTransaction, block_time: Option, diff --git a/src/streaming/grpc/types.rs b/src/streaming/grpc/types.rs index 6abfca3..c5ada6b 100644 --- a/src/streaming/grpc/types.rs +++ b/src/streaming/grpc/types.rs @@ -68,7 +68,7 @@ impl fmt::Debug for BlockMetaPretty { #[derive(Clone)] pub struct TransactionPretty { pub slot: u64, - pub transaction_index: Option, // 新增:交易在slot中的索引 + pub transaction_index: Option, // New: transaction index within the slot pub block_hash: String, pub block_time: Option, pub signature: Signature, @@ -149,11 +149,11 @@ impl Default for TransactionPretty { // ), // ) -> Self { // let tx = transaction.expect("should be defined"); -// // 根据用户说明,交易索引在 transaction.index 中 +// // According to user notes, transaction index is in transaction.index // let transaction_index = tx.index; // Self { // slot, -// transaction_index: Some(transaction_index), // 提取交易索引 +// transaction_index: Some(transaction_index), // Extract transaction index // block_time, // block_hash: String::new(), // signature: Signature::try_from(tx.signature.as_slice()).expect("valid signature"), diff --git a/src/streaming/shred/connection.rs b/src/streaming/shred/connection.rs index 8013930..dcc529e 100644 --- a/src/streaming/shred/connection.rs +++ b/src/streaming/shred/connection.rs @@ -8,7 +8,7 @@ use crate::streaming::common::{ MetricsManager, PerformanceMetrics, StreamClientConfig, SubscriptionHandle, }; -/// ShredStream gRPC 客户端 +/// ShredStream gRPC client #[derive(Clone)] pub struct ShredStreamGrpc { pub shredstream_client: Arc>, @@ -17,12 +17,12 @@ pub struct ShredStreamGrpc { } impl ShredStreamGrpc { - /// 创建客户端,使用默认配置 + /// Create client with default configuration pub async fn new(endpoint: String) -> AnyResult { Self::new_with_config(endpoint, StreamClientConfig::default()).await } - /// 创建客户端,使用自定义配置 + /// Create client with custom configuration pub async fn new_with_config(endpoint: String, config: StreamClientConfig) -> AnyResult { let shredstream_client = ShredstreamProxyClient::connect(endpoint.clone()).await?; MetricsManager::init(config.enable_metrics); @@ -33,37 +33,37 @@ impl ShredStreamGrpc { }) } - /// 获取当前配置 + /// Get current configuration pub fn get_config(&self) -> &StreamClientConfig { &self.config } - /// 更新配置 + /// Update configuration pub fn update_config(&mut self, config: StreamClientConfig) { self.config = config; } - /// 获取性能指标 + /// Get performance metrics pub fn get_metrics(&self) -> PerformanceMetrics { MetricsManager::global().get_metrics() } - /// 启用或禁用性能监控 + /// Enable or disable performance monitoring pub fn set_enable_metrics(&mut self, enabled: bool) { self.config.enable_metrics = enabled; } - /// 打印性能指标 + /// Print performance metrics pub fn print_metrics(&self) { MetricsManager::global().print_metrics(); } - /// 启动自动性能监控任务 + /// Start automatic performance monitoring task pub async fn start_auto_metrics_monitoring(&self) { MetricsManager::global().start_auto_monitoring().await; } - /// 停止当前订阅 + /// Stop current subscription pub async fn stop(&self) { let mut handle_guard = self.subscription_handle.lock().await; if let Some(handle) = handle_guard.take() { diff --git a/src/streaming/shred/mod.rs b/src/streaming/shred/mod.rs index 622225b..89e94c6 100644 --- a/src/streaming/shred/mod.rs +++ b/src/streaming/shred/mod.rs @@ -1,14 +1,14 @@ -// ShredStream 相关模块 +// ShredStream related modules pub mod connection; pub mod pool; pub mod types; -// 重新导出主要类型 +// Re-export main types pub use connection::*; pub use pool::*; pub use types::*; -// 从公用模块重新导出 +// Re-export from common modules pub use crate::streaming::common::{ ConnectionConfig, MetricsEventType, MetricsManager, PerformanceMetrics, StreamClientConfig, }; diff --git a/src/streaming/shred/pool.rs b/src/streaming/shred/pool.rs index 5dea151..d5f8138 100644 --- a/src/streaming/shred/pool.rs +++ b/src/streaming/shred/pool.rs @@ -6,7 +6,7 @@ use solana_sdk::transaction::VersionedTransaction; use super::TransactionWithSlot; -/// TransactionWithSlot 对象池 +/// TransactionWithSlot object pool pub struct TransactionWithSlotPool { pool: Arc>>>, max_size: usize, @@ -16,7 +16,7 @@ impl TransactionWithSlotPool { pub fn new(initial_size: usize, max_size: usize) -> Self { let mut pool = VecDeque::with_capacity(initial_size); - // 预分配对象 + // Pre-allocate objects for _ in 0..initial_size { pool.push_back(Box::new(TransactionWithSlot::default())); } @@ -39,7 +39,7 @@ impl TransactionWithSlotPool { } } -/// 带自动归还的 TransactionWithSlot +/// TransactionWithSlot with automatic return pub struct PooledTransactionWithSlot { transaction: Box, pool: Arc>>>, @@ -47,7 +47,7 @@ pub struct PooledTransactionWithSlot { } impl PooledTransactionWithSlot { - /// 从原始数据重置 + /// Reset from raw data pub fn reset_from_data( &mut self, transaction: VersionedTransaction, @@ -59,9 +59,9 @@ impl PooledTransactionWithSlot { self.transaction.recv_us = recv_us; } - /// 使用优化的工厂方法创建 TransactionWithSlot(移动数据而不是克隆) + /// Create TransactionWithSlot using optimized factory method (move data instead of cloning) pub fn into_transaction_with_slot(mut self) -> TransactionWithSlot { - // 移动数据而不是克隆,避免多余的内存分配 + // Move data instead of cloning to avoid unnecessary memory allocation std::mem::replace(self.deref_mut(), TransactionWithSlot::default()) } } @@ -70,10 +70,10 @@ impl Drop for PooledTransactionWithSlot { fn drop(&mut self) { let mut pool = self.pool.lock().unwrap(); if pool.len() < self.max_size { - // 清理敏感数据 + // Clear sensitive data self.transaction.slot = 0; self.transaction.recv_us = 0; - // 重置交易为默认值以清理敏感数据 + // Reset transaction to default to clear sensitive data self.transaction.transaction = VersionedTransaction::default(); pool.push_back(std::mem::take(&mut self.transaction)); } @@ -94,7 +94,7 @@ impl std::ops::DerefMut for PooledTransactionWithSlot { } } -/// Shred 对象池管理器 +/// Shred object pool manager pub struct ShredPoolManager { transaction_pool: TransactionWithSlotPool, } @@ -103,8 +103,8 @@ impl ShredPoolManager { pub fn new() -> Self { Self { transaction_pool: TransactionWithSlotPool::new( - 5000, // 初始大小 - Shred 事件通常较多 - 15000, // 最大大小 + 5000, // Initial size - Shred events are usually numerous + 15000, // Max size ), } } @@ -113,7 +113,7 @@ impl ShredPoolManager { &self.transaction_pool } - /// 创建优化的 TransactionWithSlot + /// Create optimized TransactionWithSlot pub fn create_transaction_with_slot_optimized( &self, transaction: VersionedTransaction, @@ -132,16 +132,16 @@ impl Default for ShredPoolManager { } } -// 全局 Shred 池管理器实例 +// Global Shred pool manager instance lazy_static::lazy_static! { pub static ref GLOBAL_SHRED_POOL_MANAGER: ShredPoolManager = ShredPoolManager::new(); } -/// 便捷的全局工厂函数 +/// Convenient global factory functions pub mod factory { use super::*; - /// 使用对象池创建 TransactionWithSlot(推荐用于高性能场景) + /// Create TransactionWithSlot using object pool (recommended for high-performance scenarios) pub fn create_transaction_with_slot_pooled( transaction: VersionedTransaction, slot: u64, diff --git a/src/streaming/shred/types.rs b/src/streaming/shred/types.rs index ca87cc4..0c5f25b 100644 --- a/src/streaming/shred/types.rs +++ b/src/streaming/shred/types.rs @@ -1,6 +1,6 @@ use solana_sdk::transaction::VersionedTransaction; -/// 携带槽位信息的交易 +/// Transaction with slot information #[derive(Debug, Clone, Default)] pub struct TransactionWithSlot { pub transaction: VersionedTransaction, @@ -9,7 +9,7 @@ pub struct TransactionWithSlot { } impl TransactionWithSlot { - /// 创建新的带槽位的交易 + /// Create new transaction with slot pub fn new( transaction: VersionedTransaction, slot: u64, diff --git a/src/streaming/shred_stream.rs b/src/streaming/shred_stream.rs index f98a412..ed06e23 100755 --- a/src/streaming/shred_stream.rs +++ b/src/streaming/shred_stream.rs @@ -17,7 +17,7 @@ use solana_entry::entry::Entry; use super::ShredStreamGrpc; impl ShredStreamGrpc { - /// 订阅ShredStream事件(支持批处理和即时处理) + /// Subscribe to ShredStream events (supports batch and real-time processing) pub async fn shredstream_subscribe( &self, protocols: Vec, @@ -28,16 +28,16 @@ impl ShredStreamGrpc { where F: Fn(DexEvent) + Send + Sync + 'static, { - // 如果已有活跃订阅,先停止它 + // If there's an active subscription, stop it first self.stop().await; let mut metrics_handle = None; - // 启动自动性能监控(如果启用) + // Start automatic performance monitoring (if enabled) if self.config.enable_metrics { metrics_handle = MetricsManager::global().start_auto_monitoring().await; } - // 启动流处理 + // Start stream processing let mut client = (*self.shredstream_client).clone(); let request = tonic::Request::new(SubscribeEntriesRequest {}); let mut stream = client.subscribe_entries(request).await?.into_inner(); @@ -83,7 +83,7 @@ impl ShredStreamGrpc { } }); - // 保存订阅句柄 + // Save subscription handle let subscription_handle = SubscriptionHandle::new(stream_task, None, metrics_handle); let mut handle_guard = self.subscription_handle.lock().await; *handle_guard = Some(subscription_handle); diff --git a/src/streaming/yellowstone_grpc.rs b/src/streaming/yellowstone_grpc.rs index 2a6907f..0de7c11 100644 --- a/src/streaming/yellowstone_grpc.rs +++ b/src/streaming/yellowstone_grpc.rs @@ -21,7 +21,7 @@ use yellowstone_grpc_proto::geyser::{ CommitmentLevel, SubscribeRequest, SubscribeRequestFilterAccountsFilter, SubscribeRequestPing, }; -/// 交易过滤器 +/// Transaction filter #[derive(Debug, Clone)] pub struct TransactionFilter { pub account_include: Vec, @@ -29,7 +29,7 @@ pub struct TransactionFilter { pub account_required: Vec, } -/// 账户过滤器 +/// Account filter #[derive(Debug, Clone)] pub struct AccountFilter { pub account: Vec, @@ -52,12 +52,12 @@ pub struct YellowstoneGrpc { } impl YellowstoneGrpc { - /// 创建客户端,使用默认配置 + /// Create client with default configuration pub fn new(endpoint: String, x_token: Option) -> AnyResult { Self::new_with_config(endpoint, x_token, StreamClientConfig::default()) } - /// 创建客户端,使用自定义配置 + /// Create client with custom configuration pub fn new_with_config( endpoint: String, x_token: Option, @@ -81,32 +81,32 @@ impl YellowstoneGrpc { }) } - /// 获取配置 + /// Get configuration pub fn get_config(&self) -> &StreamClientConfig { &self.config } - /// 更新配置 + /// Update configuration pub fn update_config(&mut self, config: StreamClientConfig) { self.config = config; } - /// 获取性能指标 + /// Get performance metrics pub fn get_metrics(&self) -> PerformanceMetrics { MetricsManager::global().get_metrics() } - /// 打印性能指标 + /// Print performance metrics pub fn print_metrics(&self) { MetricsManager::global().print_metrics(); } - /// 启用或禁用性能监控 + /// Enable or disable performance monitoring pub fn set_enable_metrics(&mut self, enabled: bool) { self.config.enable_metrics = enabled; } - /// 停止当前订阅 + /// Stop current subscription pub async fn stop(&self) { let mut handle_guard = self.subscription_handle.lock().await; if let Some(handle) = handle_guard.take() { @@ -153,7 +153,7 @@ impl YellowstoneGrpc { } let mut metrics_handle = None; - // 启动自动性能监控(如果启用) + // Start automatic performance monitoring (if enabled) if self.config.enable_metrics { metrics_handle = MetricsManager::global().start_auto_monitoring().await; } @@ -165,13 +165,13 @@ impl YellowstoneGrpc { .subscription_manager .subscribe_with_account_request(account_filter, event_type_filter.as_ref()); - // 订阅事件 + // Subscribe to events let (subscribe_tx, mut stream, subscribe_request) = self .subscription_manager .subscribe_with_request(transactions, accounts, commitment, event_type_filter.as_ref()) .await?; - // 用 Arc> 包装 subscribe_tx 以支持多线程共享 + // Wrap subscribe_tx with Arc> to support multi-threaded sharing let subscribe_tx = Arc::new(Mutex::new(subscribe_tx)); *self.current_request.write().await = Some(subscribe_request); let (control_tx, mut control_rx) = mpsc::channel(100); @@ -238,7 +238,7 @@ impl YellowstoneGrpc { } } Some(UpdateOneof::Ping(_)) => { - // 只在需要时获取锁,并立即释放 + // Only acquire lock when needed and release immediately if let Ok(mut tx_guard) = subscribe_tx.try_lock() { let _ = tx_guard .send(SubscribeRequest { @@ -274,7 +274,7 @@ impl YellowstoneGrpc { } }); - // 保存订阅句柄 + // Save subscription handle let subscription_handle = SubscriptionHandle::new(stream_handle, None, metrics_handle); let mut handle_guard = self.subscription_handle.lock().await; *handle_guard = Some(subscription_handle); @@ -343,7 +343,7 @@ impl YellowstoneGrpc { } } -// 实现 Clone trait 以支持模块间共享 +// Implement Clone trait to support sharing between modules impl Clone for YellowstoneGrpc { fn clone(&self) -> Self { Self { @@ -351,7 +351,7 @@ impl Clone for YellowstoneGrpc { x_token: self.x_token.clone(), config: self.config.clone(), subscription_manager: self.subscription_manager.clone(), - subscription_handle: self.subscription_handle.clone(), // 共享同一个 Arc> + subscription_handle: self.subscription_handle.clone(), // Share the same Arc> active_subscription: self.active_subscription.clone(), control_tx: self.control_tx.clone(), event_type_filter: self.event_type_filter.clone(), From 58e4926cd819df8d45231657040cbf697cfb77f6 Mon Sep 17 00:00:00 2001 From: Estereg Date: Wed, 17 Dec 2025 20:45:57 -0300 Subject: [PATCH 3/9] fix(pumpswap): Align GlobalConfig and Pool structures with IDL - Remove non-existent fields from GlobalConfig: whitelist_pda, reserved_fee_recipient, mayhem_mode_enabled - Remove non-existent field from Pool: is_mayhem_mode - Adjust calculated sizes: GlobalConfig (345 bytes) and Pool (235 bytes) These fields belong to PumpFun, not PumpSwap, and were causing deserialization errors. --- src/streaming/event_parser/protocols/pumpswap/types.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/streaming/event_parser/protocols/pumpswap/types.rs b/src/streaming/event_parser/protocols/pumpswap/types.rs index d50aa43..afa99bc 100644 --- a/src/streaming/event_parser/protocols/pumpswap/types.rs +++ b/src/streaming/event_parser/protocols/pumpswap/types.rs @@ -20,12 +20,9 @@ pub struct GlobalConfig { pub protocol_fee_recipients: [Pubkey; 8], pub coin_creator_fee_basis_points: u64, pub admin_set_coin_creator_authority: Pubkey, - pub whitelist_pda: Pubkey, - pub reserved_fee_recipient: Pubkey, - pub mayhem_mode_enabled: bool, } -pub const GLOBAL_CONFIG_SIZE: usize = 32 + 8 + 8 + 1 + 32 * 8 + 8 + 32 + 32 + 32 + 1; +pub const GLOBAL_CONFIG_SIZE: usize = 32 + 8 + 8 + 1 + 32 * 8 + 8 + 32; pub fn global_config_decode(data: &[u8]) -> Option { if data.len() < GLOBAL_CONFIG_SIZE { @@ -70,10 +67,9 @@ pub struct Pool { pub pool_quote_token_account: Pubkey, pub lp_supply: u64, pub coin_creator: Pubkey, - pub is_mayhem_mode: bool, } -pub const POOL_SIZE: usize = 1 + 2 + 32 * 6 + 8 + 32 + 1; +pub const POOL_SIZE: usize = 1 + 2 + 32 * 6 + 8 + 32; pub fn pool_decode(data: &[u8]) -> Option { if data.len() < POOL_SIZE { From 95854f0491695f477b433781f691f60d9d413215 Mon Sep 17 00:00:00 2001 From: Estereg Date: Wed, 17 Dec 2025 21:16:28 -0300 Subject: [PATCH 4/9] fix(raydium-cpmm): Align account structures with IDL specification - Add missing creator_fee_rate field to AmmConfig - Update AmmConfig padding from [u64; 16] to [u64; 15] - Add missing creator fee fields to PoolState (creator_fee_on, enable_creator_fee, padding1, creator_fees_token0, creator_fees_token1) - Update PoolState padding from [u64; 31] to [u64; 28] - Recalculate AMM_CONFIG_SIZE to 236 bytes - Recalculate POOL_STATE_SIZE to 589 bytes All structures now match the IDL specification exactly. --- .../event_parser/protocols/raydium_cpmm/types.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/streaming/event_parser/protocols/raydium_cpmm/types.rs b/src/streaming/event_parser/protocols/raydium_cpmm/types.rs index 5b8e9fe..a046af6 100644 --- a/src/streaming/event_parser/protocols/raydium_cpmm/types.rs +++ b/src/streaming/event_parser/protocols/raydium_cpmm/types.rs @@ -24,10 +24,11 @@ pub struct AmmConfig { pub create_pool_fee: u64, pub protocol_owner: Pubkey, pub fund_owner: Pubkey, - pub padding: [u64; 16], + pub creator_fee_rate: u64, + pub padding: [u64; 15], } -pub const AMM_CONFIG_SIZE: usize = 228; +pub const AMM_CONFIG_SIZE: usize = 236; pub fn amm_config_decode(data: &[u8]) -> Option { if data.len() < AMM_CONFIG_SIZE { @@ -81,10 +82,15 @@ pub struct PoolState { pub fund_fees_token1: u64, pub open_time: u64, pub recent_epoch: u64, - pub padding: [u64; 31], + pub creator_fee_on: u8, + pub enable_creator_fee: bool, + pub padding1: [u8; 6], + pub creator_fees_token0: u64, + pub creator_fees_token1: u64, + pub padding: [u64; 28], } -pub const POOL_STATE_SIZE: usize = 629; +pub const POOL_STATE_SIZE: usize = 589; pub fn pool_state_decode(data: &[u8]) -> Option { if data.len() < POOL_STATE_SIZE { From a9516e8d08a5b4a754565a41002388574a154896 Mon Sep 17 00:00:00 2001 From: Estereg Date: Wed, 17 Dec 2025 22:22:44 -0300 Subject: [PATCH 5/9] fix(bonk): Align account structures with IDL specification - Add missing token_program_flag and amm_creator_fee_on fields to PoolState - Update PoolState padding from [u64; 8] to [u8; 62] - Add missing fields to PlatformConfig (cpswap_config, creator_fee_rate, transfer_fee_extension_auth) - Update PlatformConfig arrays from Vec to fixed-size arrays [u8; 64], [u8; 256], [u8; 256], [u8; 180] - Add BondingCurveParam and PlatformCurveParam structures - Add curve_params Vec field to PlatformConfig - Recalculate POOL_STATE_SIZE to 421 bytes - Recalculate GLOBAL_CONFIG_SIZE to 363 bytes - Recalculate PLATFORM_CONFIG_MIN_SIZE to 936 bytes All structures now match the IDL specification exactly. --- .../event_parser/protocols/bonk/types.rs | 49 ++++++++++++++----- 1 file changed, 38 insertions(+), 11 deletions(-) diff --git a/src/streaming/event_parser/protocols/bonk/types.rs b/src/streaming/event_parser/protocols/bonk/types.rs index a2ddbef..f74b9f3 100755 --- a/src/streaming/event_parser/protocols/bonk/types.rs +++ b/src/streaming/event_parser/protocols/bonk/types.rs @@ -120,10 +120,12 @@ pub struct PoolState { pub base_vault: Pubkey, pub quote_vault: Pubkey, pub creator: Pubkey, - pub padding: [u64; 8], + pub token_program_flag: u8, + pub amm_creator_fee_on: AmmFeeOn, + pub padding: [u8; 62], } -pub const POOL_STATE_SIZE: usize = 8 + 1 * 5 + 8 * 10 + 32 * 7 + 8 * 8 + 8 * 5; +pub const POOL_STATE_SIZE: usize = 8 + 1 * 5 + 8 * 10 + 8 * 5 + 32 * 7 + 1 + 1 + 62; pub fn pool_state_decode(data: &[u8]) -> Option { if data.len() < POOL_STATE_SIZE { @@ -206,6 +208,27 @@ pub fn global_config_parser( } } +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)] +pub struct BondingCurveParam { + pub migrate_type: u8, + pub migrate_cpmm_fee_on: u8, + pub supply: u64, + pub total_base_sell: u64, + pub total_quote_fund_raising: u64, + pub total_locked_amount: u64, + pub cliff_period: u64, + pub unlock_period: u64, +} + +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)] +pub struct PlatformCurveParam { + pub epoch: u64, + pub index: u8, + pub global_config: Pubkey, + pub bonding_curve_param: BondingCurveParam, + pub padding: [u64; 50], +} + #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)] pub struct PlatformConfig { pub epoch: u64, @@ -215,19 +238,23 @@ pub struct PlatformConfig { pub creator_scale: u64, pub burn_scale: u64, pub fee_rate: u64, - pub name: Vec, - pub web: Vec, - pub img: Vec, - pub padding: Vec, + pub name: [u8; 64], + pub web: [u8; 256], + pub img: [u8; 256], + pub cpswap_config: Pubkey, + pub creator_fee_rate: u64, + pub transfer_fee_extension_auth: Pubkey, + pub padding: [u8; 180], + pub curve_params: Vec, } -pub const PLATFORM_CONFIG_SIZE: usize = 8 + 32 * 2 + 8 * 4 + 8 * 64 + 8 * 256 + 8 * 256 + 8 * 256; +pub const PLATFORM_CONFIG_MIN_SIZE: usize = 936; pub fn platform_config_decode(data: &[u8]) -> Option { - if data.len() < PLATFORM_CONFIG_SIZE { + if data.len() < PLATFORM_CONFIG_MIN_SIZE { return None; } - borsh::from_slice::(&data[..PLATFORM_CONFIG_SIZE]).ok() + borsh::from_slice::(data).ok() } pub fn platform_config_parser( @@ -236,11 +263,11 @@ pub fn platform_config_parser( ) -> Option { metadata.event_type = EventType::AccountBonkPlatformConfig; - if account.data.len() < PLATFORM_CONFIG_SIZE + 8 { + if account.data.len() < PLATFORM_CONFIG_MIN_SIZE + 8 { return None; } if let Some(platform_config) = - platform_config_decode(&account.data[8..PLATFORM_CONFIG_SIZE + 8]) + platform_config_decode(&account.data[8..]) { Some(DexEvent::BonkPlatformConfigAccountEvent(BonkPlatformConfigAccountEvent { metadata, From 2f3634b2a154cd3807f5d595ad06d3192efba383 Mon Sep 17 00:00:00 2001 From: Estereg Date: Wed, 17 Dec 2025 22:53:52 -0300 Subject: [PATCH 6/9] refactor(bonk): Simplify account structs - Remove unnecessary Default implementations - Add BigArray support for large arrays - Translate comments to English - Improve code readability and maintainability - Ensure proper Borsh deserialization without Default values --- src/streaming/event_parser/protocols/bonk/events.rs | 12 ++++++------ src/streaming/event_parser/protocols/bonk/types.rs | 12 +++++++++--- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/streaming/event_parser/protocols/bonk/events.rs b/src/streaming/event_parser/protocols/bonk/events.rs index 8172a59..e6eb81d 100755 --- a/src/streaming/event_parser/protocols/bonk/events.rs +++ b/src/streaming/event_parser/protocols/bonk/events.rs @@ -227,8 +227,8 @@ pub struct BonkMigrateToCpswapEvent { pub remaining_accounts: Vec, } -/// 池状态 -#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +/// Pool state account event +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct BonkPoolStateAccountEvent { pub metadata: EventMetadata, pub pubkey: Pubkey, @@ -239,8 +239,8 @@ pub struct BonkPoolStateAccountEvent { pub pool_state: PoolState, } -/// 全局配置 -#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +/// Global config account event +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct BonkGlobalConfigAccountEvent { pub metadata: EventMetadata, pub pubkey: Pubkey, @@ -251,8 +251,8 @@ pub struct BonkGlobalConfigAccountEvent { pub global_config: GlobalConfig, } -/// 平台配置 -#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +/// Platform config account event +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct BonkPlatformConfigAccountEvent { pub metadata: EventMetadata, pub pubkey: Pubkey, diff --git a/src/streaming/event_parser/protocols/bonk/types.rs b/src/streaming/event_parser/protocols/bonk/types.rs index f74b9f3..f69d1cc 100755 --- a/src/streaming/event_parser/protocols/bonk/types.rs +++ b/src/streaming/event_parser/protocols/bonk/types.rs @@ -94,7 +94,7 @@ pub struct VestingSchedule { pub allocated_share_amount: u64, } -#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)] pub struct PoolState { pub epoch: u64, pub auth_bump: u8, @@ -122,6 +122,7 @@ pub struct PoolState { pub creator: Pubkey, pub token_program_flag: u8, pub amm_creator_fee_on: AmmFeeOn, + #[serde(with = "serde_big_array::BigArray")] pub padding: [u8; 62], } @@ -220,16 +221,17 @@ pub struct BondingCurveParam { pub unlock_period: u64, } -#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)] pub struct PlatformCurveParam { pub epoch: u64, pub index: u8, pub global_config: Pubkey, pub bonding_curve_param: BondingCurveParam, + #[serde(with = "serde_big_array::BigArray")] pub padding: [u64; 50], } -#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)] pub struct PlatformConfig { pub epoch: u64, pub platform_fee_wallet: Pubkey, @@ -238,12 +240,16 @@ pub struct PlatformConfig { pub creator_scale: u64, pub burn_scale: u64, pub fee_rate: u64, + #[serde(with = "serde_big_array::BigArray")] pub name: [u8; 64], + #[serde(with = "serde_big_array::BigArray")] pub web: [u8; 256], + #[serde(with = "serde_big_array::BigArray")] pub img: [u8; 256], pub cpswap_config: Pubkey, pub creator_fee_rate: u64, pub transfer_fee_extension_auth: Pubkey, + #[serde(with = "serde_big_array::BigArray")] pub padding: [u8; 180], pub curve_params: Vec, } From aab0a3a14311a881035858b419e5661c5cdc5e3b Mon Sep 17 00:00:00 2001 From: 0xfnzero <0xfnzero@users.noreply.github.com> Date: Fri, 17 Jul 2026 01:59:54 +0800 Subject: [PATCH 7/9] feat(pumpswap): forward virtual quote reserve upgrade --- Cargo.toml | 4 +- idl/pump_amm.json | 1122 +++++++++++++++++ .../event_parser/core/merger_event.rs | 35 + .../event_parser/protocols/pumpswap/events.rs | 10 + .../event_parser/protocols/pumpswap/types.rs | 11 +- .../parser_sdk_bridge/bonk_accounts.rs | 2 +- src/streaming/parser_sdk_bridge/mod.rs | 56 +- .../parser_sdk_bridge/pump_pumpswap.rs | 10 + 8 files changed, 1237 insertions(+), 13 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f4ba803..1d9bcdd 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "solana-streamer-sdk" -version = "1.5.16" +version = "2.0.0" edition = "2021" authors = ["William ", "sgxiang ", "wei <1415121722@qq.com>"] repository = "https://github.com/0xfnzero/solana-streamer" @@ -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.5.15", default-features = false } +sol-parser-sdk = { version = "0.6.0", git = "https://github.com/0xfnzero/sol-parser-sdk", branch = "dev", default-features = false } solana-sdk = "3.0.0" solana-client = "3.1.12" solana-transaction-status = "3.1.12" diff --git a/idl/pump_amm.json b/idl/pump_amm.json index 372cf2a..a654b6f 100644 --- a/idl/pump_amm.json +++ b/idl/pump_amm.json @@ -288,6 +288,213 @@ } ] }, + { + "name": "boost_buy_and_burn", + "discriminator": [ + 105, + 68, + 6, + 175, + 0, + 7, + 35, + 162 + ], + "accounts": [ + { + "name": "pool" + }, + { + "name": "authority", + "writable": true, + "signer": true + }, + { + "name": "global_config", + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 103, + 108, + 111, + 98, + 97, + 108, + 95, + 99, + 111, + 110, + 102, + 105, + 103 + ] + } + ] + } + }, + { + "name": "base_mint", + "writable": true, + "relations": [ + "pool" + ] + }, + { + "name": "quote_mint", + "relations": [ + "pool" + ] + }, + { + "name": "pool_base_token_account", + "writable": true, + "relations": [ + "pool" + ] + }, + { + "name": "pool_quote_token_account", + "writable": true, + "relations": [ + "pool" + ] + }, + { + "name": "boost_vault_authority", + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 98, + 111, + 111, + 115, + 116, + 95, + 118, + 97, + 117, + 108, + 116 + ] + }, + { + "kind": "account", + "path": "pool" + } + ] + } + }, + { + "name": "boost_vault", + "writable": true, + "pda": { + "seeds": [ + { + "kind": "account", + "path": "boost_vault_authority" + }, + { + "kind": "account", + "path": "quote_token_program" + }, + { + "kind": "account", + "path": "quote_mint" + } + ], + "program": { + "kind": "const", + "value": [ + 140, + 151, + 37, + 143, + 78, + 36, + 137, + 241, + 187, + 61, + 16, + 41, + 20, + 142, + 13, + 131, + 11, + 90, + 19, + 153, + 218, + 255, + 16, + 132, + 4, + 142, + 123, + 216, + 219, + 233, + 248, + 89 + ] + } + } + }, + { + "name": "base_token_program" + }, + { + "name": "quote_token_program" + }, + { + "name": "event_authority", + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 95, + 95, + 101, + 118, + 101, + 110, + 116, + 95, + 97, + 117, + 116, + 104, + 111, + 114, + 105, + 116, + 121 + ] + } + ] + } + }, + { + "name": "program" + } + ], + "args": [ + { + "name": "quote_amount_in", + "type": "u64" + }, + { + "name": "min_base_amount_burned", + "type": "u64" + } + ] + }, { "name": "buy", "docs": [ @@ -2587,6 +2794,208 @@ ], "args": [] }, + { + "name": "init_boost", + "discriminator": [ + 140, + 233, + 33, + 94, + 132, + 90, + 194, + 143 + ], + "accounts": [ + { + "name": "pool", + "writable": true + }, + { + "name": "global_config", + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 103, + 108, + 111, + 98, + 97, + 108, + 95, + 99, + 111, + 110, + 102, + 105, + 103 + ] + } + ] + } + }, + { + "name": "creator", + "writable": true, + "signer": true + }, + { + "name": "base_mint", + "relations": [ + "pool" + ] + }, + { + "name": "quote_mint", + "relations": [ + "pool" + ] + }, + { + "name": "pool_base_token_account", + "relations": [ + "pool" + ] + }, + { + "name": "pool_quote_token_account", + "writable": true, + "relations": [ + "pool" + ] + }, + { + "name": "boost_vault_authority", + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 98, + 111, + 111, + 115, + 116, + 95, + 118, + 97, + 117, + 108, + 116 + ] + }, + { + "kind": "account", + "path": "pool" + } + ] + } + }, + { + "name": "boost_vault", + "writable": true, + "pda": { + "seeds": [ + { + "kind": "account", + "path": "boost_vault_authority" + }, + { + "kind": "account", + "path": "quote_token_program" + }, + { + "kind": "account", + "path": "quote_mint" + } + ], + "program": { + "kind": "const", + "value": [ + 140, + 151, + 37, + 143, + 78, + 36, + 137, + 241, + 187, + 61, + 16, + 41, + 20, + 142, + 13, + 131, + 11, + 90, + 19, + 153, + 218, + 255, + 16, + 132, + 4, + 142, + 123, + 216, + 219, + 233, + 248, + 89 + ] + } + } + }, + { + "name": "quote_token_program" + }, + { + "name": "system_program", + "address": "11111111111111111111111111111111" + }, + { + "name": "associated_token_program", + "address": "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL" + }, + { + "name": "event_authority", + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 95, + 95, + 101, + 118, + 101, + 110, + 116, + 95, + 97, + 117, + 116, + 104, + 111, + 114, + 105, + 116, + 121 + ] + } + ] + } + }, + { + "name": "program" + } + ], + "args": [] + }, { "name": "init_user_volume_accumulator", "discriminator": [ @@ -3175,6 +3584,72 @@ } ] }, + { + "name": "set_boost_authority", + "discriminator": [ + 227, + 149, + 76, + 42, + 130, + 39, + 234, + 205 + ], + "accounts": [ + { + "name": "admin", + "signer": true, + "relations": [ + "global_config" + ] + }, + { + "name": "global_config", + "writable": true + }, + { + "name": "boost_authority" + }, + { + "name": "system_program", + "address": "11111111111111111111111111111111" + }, + { + "name": "event_authority", + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 95, + 95, + 101, + 118, + 101, + 110, + 116, + 95, + 97, + 117, + 116, + 104, + 111, + 114, + 105, + 116, + 121 + ] + } + ] + } + }, + { + "name": "program" + } + ], + "args": [] + }, { "name": "set_coin_creator", "docs": [ @@ -3610,6 +4085,38 @@ ], "args": [] }, + { + "name": "toggle_boost", + "discriminator": [ + 117, + 161, + 160, + 74, + 223, + 137, + 118, + 99 + ], + "accounts": [ + { + "name": "admin", + "signer": true, + "relations": [ + "global_config" + ] + }, + { + "name": "global_config", + "writable": true + } + ], + "args": [ + { + "name": "enabled", + "type": "bool" + } + ] + }, { "name": "toggle_cashback_enabled", "discriminator": [ @@ -3965,6 +4472,254 @@ ], "args": [] }, + { + "name": "transfer_creator_fees_to_pump_v2", + "discriminator": [ + 1, + 33, + 78, + 185, + 33, + 67, + 44, + 92 + ], + "accounts": [ + { + "name": "payer", + "writable": true, + "signer": true + }, + { + "name": "quote_mint" + }, + { + "name": "token_program" + }, + { + "name": "system_program", + "address": "11111111111111111111111111111111" + }, + { + "name": "associated_token_program", + "address": "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL" + }, + { + "name": "coin_creator" + }, + { + "name": "coin_creator_vault_authority", + "writable": true, + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 99, + 114, + 101, + 97, + 116, + 111, + 114, + 95, + 118, + 97, + 117, + 108, + 116 + ] + }, + { + "kind": "account", + "path": "coin_creator" + } + ] + } + }, + { + "name": "coin_creator_vault_ata", + "writable": true, + "pda": { + "seeds": [ + { + "kind": "account", + "path": "coin_creator_vault_authority" + }, + { + "kind": "account", + "path": "token_program" + }, + { + "kind": "account", + "path": "quote_mint" + } + ], + "program": { + "kind": "const", + "value": [ + 140, + 151, + 37, + 143, + 78, + 36, + 137, + 241, + 187, + 61, + 16, + 41, + 20, + 142, + 13, + 131, + 11, + 90, + 19, + 153, + 218, + 255, + 16, + 132, + 4, + 142, + 123, + 216, + 219, + 233, + 248, + 89 + ] + } + } + }, + { + "name": "pump_creator_vault", + "writable": true, + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 99, + 114, + 101, + 97, + 116, + 111, + 114, + 45, + 118, + 97, + 117, + 108, + 116 + ] + }, + { + "kind": "account", + "path": "coin_creator" + } + ], + "program": { + "kind": "const", + "value": [ + 1, + 86, + 224, + 246, + 147, + 102, + 90, + 207, + 68, + 219, + 21, + 104, + 191, + 23, + 91, + 170, + 81, + 137, + 203, + 151, + 245, + 210, + 255, + 59, + 101, + 93, + 43, + 182, + 253, + 109, + 24, + 176 + ] + } + } + }, + { + "name": "pump_creator_vault_ata", + "writable": true, + "pda": { + "seeds": [ + { + "kind": "account", + "path": "pump_creator_vault" + }, + { + "kind": "account", + "path": "token_program" + }, + { + "kind": "account", + "path": "quote_mint" + } + ], + "program": { + "kind": "account", + "path": "associated_token_program" + } + } + }, + { + "name": "event_authority", + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 95, + 95, + 101, + 118, + 101, + 110, + 116, + 95, + 97, + 117, + 116, + 104, + 111, + 114, + 105, + 116, + 121 + ] + } + ] + } + }, + { + "name": "program" + } + ], + "args": [] + }, { "name": "update_admin", "discriminator": [ @@ -4027,6 +4782,72 @@ ], "args": [] }, + { + "name": "update_buyback_config", + "discriminator": [ + 251, + 224, + 171, + 146, + 160, + 26, + 113, + 233 + ], + "accounts": [ + { + "name": "admin", + "signer": true, + "relations": [ + "global_config" + ] + }, + { + "name": "global_config", + "writable": true + }, + { + "name": "event_authority", + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 95, + 95, + 101, + 118, + 101, + 110, + 116, + 95, + 97, + 117, + 116, + 104, + 111, + 114, + 105, + 116, + 121 + ] + } + ] + } + }, + { + "name": "program" + } + ], + "args": [ + { + "name": "buyback_basis_points", + "type": { + "option": "u64" + } + } + ] + }, { "name": "update_fee_config", "discriminator": [ @@ -4358,6 +5179,19 @@ 222 ] }, + { + "name": "BoostBuyAndBurnEvent", + "discriminator": [ + 63, + 69, + 28, + 22, + 48, + 92, + 194, + 185 + ] + }, { "name": "BuyEvent", "discriminator": [ @@ -4488,6 +5322,19 @@ 124 ] }, + { + "name": "InitBoostEvent", + "discriminator": [ + 174, + 124, + 74, + 249, + 4, + 81, + 246, + 17 + ] + }, { "name": "InitUserVolumeAccumulatorEvent", "discriminator": [ @@ -4553,6 +5400,19 @@ 211 ] }, + { + "name": "SetBoostAuthorityEvent", + "discriminator": [ + 89, + 128, + 240, + 141, + 91, + 202, + 71, + 105 + ] + }, { "name": "SetMetaplexCoinCreatorEvent", "discriminator": [ @@ -4836,6 +5696,78 @@ { "code": 6052, "name": "TokensInVaultLessThanCashbackEarned" + }, + { + "code": 6053, + "name": "BuybackFeeRecipientNotAuthorized", + "msg": "Buyback fee recipient not authorized" + }, + { + "code": 6054, + "name": "AllBuybackFeeRecipientsShouldBeNonZero" + }, + { + "code": 6055, + "name": "NotUniqueBuybackFeeRecipients" + }, + { + "code": 6056, + "name": "BuybackBasisPointsOutOfRange", + "msg": "buyback_basis_points must be <= 10_000" + }, + { + "code": 6057, + "name": "WrongBuybackFeeRecipientsCount", + "msg": "buyback fee recipients require exactly 8 remaining accounts (or none)" + }, + { + "code": 6058, + "name": "BuybackFeeRecipientMissing" + }, + { + "code": 6059, + "name": "MissingCashbackAccounts", + "msg": "Cashback trade is missing the required remaining accounts" + }, + { + "code": 6060, + "name": "InvalidCashbackAccumulator", + "msg": "Cashback user_volume_accumulator account is invalid" + }, + { + "code": 6061, + "name": "InvalidCashbackAccumulatorAta", + "msg": "Cashback user_volume_accumulator ATA is missing or invalid" + }, + { + "code": 6062, + "name": "InvalidPoolV2", + "msg": "pool_v2 remaining account is missing or invalid" + }, + { + "code": 6063, + "name": "InsufficientRealQuoteReserves", + "msg": "BOOST: sell output exceeds the real quote vault. effective = real + virtual is pricing-only; payout is capped at real_vault, so quote min(out, real_vault)" + }, + { + "code": 6064, + "name": "BoostPoolLiquidityUnsupported", + "msg": "BOOST: deposit/withdraw don't apply to boost pools" + }, + { + "code": 6065, + "name": "PoolCannotBoost", + "msg": "BOOST: pool cannot be boosted (no virtual reserves)" + }, + { + "code": 6066, + "name": "BoostDisabled", + "msg": "BOOST: boost is disabled" + }, + { + "code": 6067, + "name": "SeedLockViolation", + "msg": "BOOST: lp_supply must never drop below the circulating LP mint supply" } ], "types": [ @@ -4951,6 +5883,62 @@ ] } }, + { + "name": "BoostBuyAndBurnEvent", + "type": { + "kind": "struct", + "fields": [ + { + "name": "timestamp", + "type": "i64" + }, + { + "name": "mint", + "type": "pubkey" + }, + { + "name": "bonding_curve", + "type": "pubkey" + }, + { + "name": "pool", + "type": "pubkey" + }, + { + "name": "authority", + "type": "pubkey" + }, + { + "name": "quote_amount_in_requested", + "type": "u64" + }, + { + "name": "quote_amount_in_used", + "type": "u64" + }, + { + "name": "base_amount_burned", + "type": "u64" + }, + { + "name": "virtual_quote_reserves", + "type": "i128" + }, + { + "name": "real_quote_reserves_after", + "type": "u64" + }, + { + "name": "base_reserves_after", + "type": "u64" + }, + { + "name": "boost_vault_remaining", + "type": "u64" + } + ] + } + }, { "name": "BuyEvent", "docs": [ @@ -5086,6 +6074,26 @@ { "name": "cashback", "type": "u64" + }, + { + "name": "buyback_fee_basis_points", + "type": "u64" + }, + { + "name": "buyback_fee", + "type": "u64" + }, + { + "name": "virtual_quote_reserves", + "type": "i128" + }, + { + "name": "can_boost", + "type": "bool" + }, + { + "name": "base_supply", + "type": "u64" } ] } @@ -5523,6 +6531,16 @@ } } } + }, + { + "name": "stable_fee_tiers", + "type": { + "vec": { + "defined": { + "name": "FeeTier" + } + } + } } ] } @@ -5646,6 +6664,27 @@ { "name": "is_cashback_enabled", "type": "bool" + }, + { + "name": "buyback_fee_recipients", + "type": { + "array": [ + "pubkey", + 8 + ] + } + }, + { + "name": "buyback_basis_points", + "type": "u64" + }, + { + "name": "boost_authority", + "type": "pubkey" + }, + { + "name": "boost_enabled", + "type": "bool" } ] } @@ -5692,6 +6731,38 @@ ] } }, + { + "name": "InitBoostEvent", + "type": { + "kind": "struct", + "fields": [ + { + "name": "timestamp", + "type": "i64" + }, + { + "name": "mint", + "type": "pubkey" + }, + { + "name": "bonding_curve", + "type": "pubkey" + }, + { + "name": "pool", + "type": "pubkey" + }, + { + "name": "virtual_quote_reserves", + "type": "i128" + }, + { + "name": "real_quote_reserves_after", + "type": "u64" + } + ] + } + }, { "name": "InitUserVolumeAccumulatorEvent", "type": { @@ -5808,6 +6879,13 @@ { "name": "is_cashback_coin", "type": "bool" + }, + { + "name": "virtual_quote_reserves", + "docs": [ + "For non-boost pools, value is 0, so the behavior is identical to legacy pools." + ], + "type": "i128" } ] } @@ -5941,6 +7019,26 @@ { "name": "cashback", "type": "u64" + }, + { + "name": "buyback_fee_basis_points", + "type": "u64" + }, + { + "name": "buyback_fee", + "type": "u64" + }, + { + "name": "virtual_quote_reserves", + "type": "i128" + }, + { + "name": "can_boost", + "type": "bool" + }, + { + "name": "base_supply", + "type": "u64" } ] } @@ -5973,6 +7071,30 @@ ] } }, + { + "name": "SetBoostAuthorityEvent", + "type": { + "kind": "struct", + "fields": [ + { + "name": "timestamp", + "type": "i64" + }, + { + "name": "admin", + "type": "pubkey" + }, + { + "name": "old_boost_authority", + "type": "pubkey" + }, + { + "name": "new_boost_authority", + "type": "pubkey" + } + ] + } + }, { "name": "SetMetaplexCoinCreatorEvent", "type": { diff --git a/src/streaming/event_parser/core/merger_event.rs b/src/streaming/event_parser/core/merger_event.rs index d473f86..4a847bc 100644 --- a/src/streaming/event_parser/core/merger_event.rs +++ b/src/streaming/event_parser/core/merger_event.rs @@ -342,6 +342,11 @@ pub fn merge(instruction_event: &mut DexEvent, cpi_log_event: DexEvent) { e.ix_name = cpie.ix_name.clone(); e.cashback_fee_basis_points = cpie.cashback_fee_basis_points; e.cashback = cpie.cashback; + e.buyback_fee_basis_points = cpie.buyback_fee_basis_points; + e.buyback_fee = cpie.buyback_fee; + e.virtual_quote_reserves = cpie.virtual_quote_reserves; + e.can_boost = cpie.can_boost; + e.base_supply = cpie.base_supply; e.is_pump_pool = cpie.is_pump_pool; fill_pubkey(&mut e.base_mint, cpie.base_mint); fill_pubkey(&mut e.quote_mint, cpie.quote_mint); @@ -387,6 +392,11 @@ pub fn merge(instruction_event: &mut DexEvent, cpi_log_event: DexEvent) { e.coin_creator_fee = cpie.coin_creator_fee; e.cashback_fee_basis_points = cpie.cashback_fee_basis_points; e.cashback = cpie.cashback; + e.buyback_fee_basis_points = cpie.buyback_fee_basis_points; + e.buyback_fee = cpie.buyback_fee; + e.virtual_quote_reserves = cpie.virtual_quote_reserves; + e.can_boost = cpie.can_boost; + e.base_supply = cpie.base_supply; e.is_pump_pool = cpie.is_pump_pool; fill_pubkey(&mut e.base_mint, cpie.base_mint); fill_pubkey(&mut e.quote_mint, cpie.quote_mint); @@ -858,6 +868,7 @@ mod tests { use crate::streaming::event_parser::protocols::pumpfun::events::{ PumpFeesShareholder, PumpFunTradeEvent, }; + use crate::streaming::event_parser::protocols::pumpswap::events::PumpSwapSellEvent; #[test] fn pumpfun_merge_keeps_instruction_context_and_copies_latest_trade_tail() { @@ -912,6 +923,30 @@ mod tests { } } + #[test] + fn pumpswap_merge_preserves_signed_virtual_quote_reserves() { + let mut instruction_event = DexEvent::PumpSwapSellEvent(PumpSwapSellEvent::default()); + let log_event = DexEvent::PumpSwapSellEvent(PumpSwapSellEvent { + virtual_quote_reserves: -500, + buyback_fee_basis_points: 11, + buyback_fee: 22, + can_boost: true, + base_supply: 33, + ..Default::default() + }); + + merge(&mut instruction_event, log_event); + + let DexEvent::PumpSwapSellEvent(event) = instruction_event else { + panic!("expected PumpSwapSellEvent"); + }; + assert_eq!(event.virtual_quote_reserves, -500); + assert_eq!(event.buyback_fee_basis_points, 11); + assert_eq!(event.buyback_fee, 22); + assert!(event.can_boost); + assert_eq!(event.base_supply, 33); + } + #[test] fn pumpfun_merge_replaces_sol_quote_sentinel_with_real_quote_mint() { let quote_mint = Pubkey::new_unique(); diff --git a/src/streaming/event_parser/protocols/pumpswap/events.rs b/src/streaming/event_parser/protocols/pumpswap/events.rs index 6764109..f3297a4 100755 --- a/src/streaming/event_parser/protocols/pumpswap/events.rs +++ b/src/streaming/event_parser/protocols/pumpswap/events.rs @@ -44,6 +44,11 @@ pub struct PumpSwapBuyEvent { pub ix_name: String, pub cashback_fee_basis_points: u64, pub cashback: u64, + pub buyback_fee_basis_points: u64, + pub buyback_fee: u64, + pub virtual_quote_reserves: i128, + pub can_boost: bool, + pub base_supply: u64, #[borsh(skip)] pub is_pump_pool: bool, #[borsh(skip)] @@ -105,6 +110,11 @@ pub struct PumpSwapSellEvent { pub coin_creator_fee: u64, pub cashback_fee_basis_points: u64, pub cashback: u64, + pub buyback_fee_basis_points: u64, + pub buyback_fee: u64, + pub virtual_quote_reserves: i128, + pub can_boost: bool, + pub base_supply: u64, #[borsh(skip)] pub is_pump_pool: bool, #[borsh(skip)] diff --git a/src/streaming/event_parser/protocols/pumpswap/types.rs b/src/streaming/event_parser/protocols/pumpswap/types.rs index e56a2a3..c1f7f3a 100644 --- a/src/streaming/event_parser/protocols/pumpswap/types.rs +++ b/src/streaming/event_parser/protocols/pumpswap/types.rs @@ -33,13 +33,12 @@ pub struct Pool { pub coin_creator: Pubkey, pub is_mayhem_mode: bool, pub is_cashback_coin: bool, - /// On-chain reserved tail (7 bytes); keep in sync with pump_amm pool account layout. - pub reserved: [u8; 7], + pub virtual_quote_reserves: i128, } -/// Legacy pool account body (before `is_cashback_coin` + reserved). -pub const POOL_BODY_LEGACY: usize = 1 + 2 + 32 * 6 + 8 + 32 + 1; -/// Current pool account body including flags and reserved. -pub const POOL_BODY: usize = POOL_BODY_LEGACY + 1 + 7; +/// Legacy allocated pool account body. +pub const POOL_BODY_LEGACY: usize = 244; +/// Current serialized pool account body including signed virtual quote reserves. +pub const POOL_BODY: usize = 253; pub const POOL_SIZE: usize = POOL_BODY; diff --git a/src/streaming/parser_sdk_bridge/bonk_accounts.rs b/src/streaming/parser_sdk_bridge/bonk_accounts.rs index 491eb5a..9db7cc5 100644 --- a/src/streaming/parser_sdk_bridge/bonk_accounts.rs +++ b/src/streaming/parser_sdk_bridge/bonk_accounts.rs @@ -185,7 +185,7 @@ pub(crate) fn pumpswap_pool_from_pb(p: sol_parser_sdk::core::events::PumpSwapPoo coin_creator: p.coin_creator, is_mayhem_mode: p.is_mayhem_mode, is_cashback_coin: p.is_cashback_coin, - reserved: [0u8; 7], + virtual_quote_reserves: p.virtual_quote_reserves, } } diff --git a/src/streaming/parser_sdk_bridge/mod.rs b/src/streaming/parser_sdk_bridge/mod.rs index e5f7a9f..61a5e0a 100644 --- a/src/streaming/parser_sdk_bridge/mod.rs +++ b/src/streaming/parser_sdk_bridge/mod.rs @@ -43,10 +43,10 @@ mod tests { use sol_parser_sdk::core::events::{ EventMetadata, MeteoraDlmmSwapEvent as PbDlmmSwap, OrcaWhirlpoolSwapEvent as PbOrcaSwap, PumpFunCreateV2TokenEvent as PbPumpCreateV2, PumpFunTradeEvent as PbPumpTrade, - PumpSwapCreatePoolEvent as PbPumpSwapCreatePool, PumpSwapPool as PbPumpSwapPool, - PumpSwapPoolAccountEvent as PbPumpSwapPoolAccount, - RaydiumLaunchlabTradeEvent as PbBonkTrade, TokenInfoEvent as PbTokenInfo, - TradeDirection as PbBonkDir, + PumpSwapBuyEvent as PbPumpSwapBuy, PumpSwapCreatePoolEvent as PbPumpSwapCreatePool, + PumpSwapPool as PbPumpSwapPool, PumpSwapPoolAccountEvent as PbPumpSwapPoolAccount, + PumpSwapSellEvent as PbPumpSwapSell, RaydiumLaunchlabTradeEvent as PbBonkTrade, + TokenInfoEvent as PbTokenInfo, TradeDirection as PbBonkDir, }; use sol_parser_sdk::DexEvent as PbDexEvent; use solana_sdk::{pubkey::Pubkey, signature::Signature}; @@ -242,6 +242,52 @@ mod tests { } } + #[test] + fn converts_pumpswap_buy_boost_tail() { + let event = PbPumpSwapBuy { + virtual_quote_reserves: -123, + buyback_fee_basis_points: 11, + buyback_fee: 22, + can_boost: true, + base_supply: 33, + ..Default::default() + }; + + let converted = + convert_parser_event(PbDexEvent::PumpSwapBuy(event), None, 999).expect("convert"); + let DexEvent::PumpSwapBuyEvent(converted) = converted else { + panic!("expected PumpSwapBuyEvent"); + }; + assert_eq!(converted.virtual_quote_reserves, -123); + assert_eq!(converted.buyback_fee_basis_points, 11); + assert_eq!(converted.buyback_fee, 22); + assert!(converted.can_boost); + assert_eq!(converted.base_supply, 33); + } + + #[test] + fn converts_pumpswap_sell_boost_tail() { + let event = PbPumpSwapSell { + virtual_quote_reserves: 123, + buyback_fee_basis_points: 44, + buyback_fee: 55, + can_boost: true, + base_supply: 66, + ..Default::default() + }; + + let converted = + convert_parser_event(PbDexEvent::PumpSwapSell(event), None, 999).expect("convert"); + let DexEvent::PumpSwapSellEvent(converted) = converted else { + panic!("expected PumpSwapSellEvent"); + }; + assert_eq!(converted.virtual_quote_reserves, 123); + assert_eq!(converted.buyback_fee_basis_points, 44); + assert_eq!(converted.buyback_fee, 55); + assert!(converted.can_boost); + assert_eq!(converted.base_supply, 66); + } + #[test] fn converts_pumpswap_pool_account_cashback_flag() { let pool_pubkey = Pubkey::new_unique(); @@ -272,6 +318,7 @@ mod tests { coin_creator: Pubkey::new_unique(), is_mayhem_mode: true, is_cashback_coin: true, + virtual_quote_reserves: -777, }, }; @@ -283,6 +330,7 @@ mod tests { assert_eq!(st.pubkey, pool_pubkey); assert!(st.pool.is_mayhem_mode); assert!(st.pool.is_cashback_coin); + assert_eq!(st.pool.virtual_quote_reserves, -777); } _ => panic!("expected PumpSwapPoolAccountEvent"), } diff --git a/src/streaming/parser_sdk_bridge/pump_pumpswap.rs b/src/streaming/parser_sdk_bridge/pump_pumpswap.rs index 26a80e2..0a79486 100644 --- a/src/streaming/parser_sdk_bridge/pump_pumpswap.rs +++ b/src/streaming/parser_sdk_bridge/pump_pumpswap.rs @@ -523,6 +523,11 @@ pub(crate) fn pumpswap_buy_full_from_parser( ix_name: b.ix_name, cashback_fee_basis_points: b.cashback_fee_basis_points, cashback: b.cashback, + buyback_fee_basis_points: b.buyback_fee_basis_points, + buyback_fee: b.buyback_fee, + virtual_quote_reserves: b.virtual_quote_reserves, + can_boost: b.can_boost, + base_supply: b.base_supply, is_pump_pool: b.is_pump_pool, base_mint: b.base_mint, quote_mint: b.quote_mint, @@ -569,6 +574,11 @@ pub(crate) fn pumpswap_sell_full_from_parser( coin_creator_fee: s.coin_creator_fee, cashback_fee_basis_points: s.cashback_fee_basis_points, cashback: s.cashback, + buyback_fee_basis_points: s.buyback_fee_basis_points, + buyback_fee: s.buyback_fee, + virtual_quote_reserves: s.virtual_quote_reserves, + can_boost: s.can_boost, + base_supply: s.base_supply, is_pump_pool: s.is_pump_pool, base_mint: s.base_mint, quote_mint: s.quote_mint, From 85c6cc901ad3f1bf8fe8010d79b92ecdd0be02b4 Mon Sep 17 00:00:00 2001 From: 0xfnzero <0xfnzero@users.noreply.github.com> Date: Fri, 17 Jul 2026 02:11:16 +0800 Subject: [PATCH 8/9] build: pin parser dev integration --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 1d9bcdd..e9b61b9 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -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.0", git = "https://github.com/0xfnzero/sol-parser-sdk", branch = "dev", default-features = false } +sol-parser-sdk = { version = "0.6.0", git = "https://github.com/0xfnzero/sol-parser-sdk", rev = "1002ffeeb3a65aff0a7a38bcc834ef01a9aebf1f", default-features = false } solana-sdk = "3.0.0" solana-client = "3.1.12" solana-transaction-status = "3.1.12" From f1c6aecb3d4a4ebb2cd3c9f6a58de20b019418e2 Mon Sep 17 00:00:00 2001 From: 0xfnzero <0xfnzero@users.noreply.github.com> Date: Fri, 17 Jul 2026 03:04:10 +0800 Subject: [PATCH 9/9] fix(pumpswap): preserve upgraded event fields --- Cargo.toml | 5 +- examples/pumpswap_with_metrics.rs | 29 +++--- .../event_parser/core/merger_event.rs | 99 +++++++++++++++---- .../event_parser/protocols/pumpswap/events.rs | 43 ++++++++ .../event_parser/protocols/pumpswap/types.rs | 1 + src/streaming/parser_sdk_bridge/mod.rs | 8 +- 6 files changed, 149 insertions(+), 36 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e9b61b9..0285d17 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -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.0", git = "https://github.com/0xfnzero/sol-parser-sdk", rev = "1002ffeeb3a65aff0a7a38bcc834ef01a9aebf1f", default-features = false } +sol-parser-sdk = { version = "0.6.0", git = "https://github.com/0xfnzero/sol-parser-sdk", rev = "995d88991b56234a23fc1d0911fdd33caa063c67", default-features = false } solana-sdk = "3.0.0" solana-client = "3.1.12" solana-transaction-status = "3.1.12" @@ -50,3 +50,6 @@ spl-token-2022 = { version = "10.0.0", default-features = false, features = ["no spl-token-group-interface = "=0.7.1" solana-commitment-config = { version = "3.1.1", features = ["serde"] } tonic-prost = "0.14.5" + +[dev-dependencies] +serde_json = "1.0.149" diff --git a/examples/pumpswap_with_metrics.rs b/examples/pumpswap_with_metrics.rs index 5f8c7cf..d0c09c6 100644 --- a/examples/pumpswap_with_metrics.rs +++ b/examples/pumpswap_with_metrics.rs @@ -3,10 +3,14 @@ //! Usage: cargo run --example pumpswap_with_metrics --release use solana_streamer_sdk::streaming::event_parser::protocols::pumpswap::parser::PUMPSWAP_PROGRAM_ID; -use solana_streamer_sdk::streaming::event_parser::{DexEvent, Protocol}; +use solana_streamer_sdk::streaming::event_parser::{ + common::filter::EventTypeFilter, common::EventType, DexEvent, Protocol, +}; use solana_streamer_sdk::streaming::grpc::ClientConfig; -use solana_streamer_sdk::streaming::yellowstone_grpc::{ - AccountFilter, TransactionFilter, YellowstoneGrpc, +use solana_streamer_sdk::streaming::yellowstone_grpc::{TransactionFilter, YellowstoneGrpc}; +use std::sync::{ + atomic::{AtomicU64, Ordering}, + Arc, }; #[tokio::main] @@ -29,22 +33,20 @@ async fn main() -> Result<(), Box> { account_exclude: vec![], account_required: vec![], }; - let account_filter = AccountFilter { - account: vec![], - owner: vec![PUMPSWAP_PROGRAM_ID.to_string()], - filters: vec![], - }; - - let callback = |event: DexEvent| { - println!("Event: {:?}", event.metadata().event_type); + let event_count = Arc::new(AtomicU64::new(0)); + let callback_count = event_count.clone(); + let callback = move |_event: DexEvent| { + callback_count.fetch_add(1, Ordering::Relaxed); }; + let event_filter = + EventTypeFilter::include_only(vec![EventType::PumpSwapBuy, EventType::PumpSwapSell]); grpc.subscribe_events_immediate( vec![Protocol::PumpSwap], None, vec![transaction_filter], - vec![account_filter], - None, + vec![], + Some(event_filter), None, callback, ) @@ -53,5 +55,6 @@ async fn main() -> Result<(), Box> { println!("Press Ctrl+C to stop...\n"); tokio::signal::ctrl_c().await?; grpc.stop().await; + println!("Processed {} PumpSwap trade events", event_count.load(Ordering::Relaxed)); Ok(()) } diff --git a/src/streaming/event_parser/core/merger_event.rs b/src/streaming/event_parser/core/merger_event.rs index 4a847bc..c88530c 100644 --- a/src/streaming/event_parser/core/merger_event.rs +++ b/src/streaming/event_parser/core/merger_event.rs @@ -42,6 +42,20 @@ fn fill_i64(to: &mut i64, from: i64) { } } +#[inline] +fn overwrite_u64_if_present(to: &mut u64, from: u64) { + if from != 0 { + *to = from; + } +} + +#[inline] +fn overwrite_i128_if_present(to: &mut i128, from: i128) { + if from != 0 { + *to = from; + } +} + #[inline] fn fill_string(to: &mut String, from: String) { if to.is_empty() && !from.is_empty() { @@ -338,16 +352,27 @@ pub fn merge(instruction_event: &mut DexEvent, cpi_log_event: DexEvent) { e.total_claimed_tokens = cpie.total_claimed_tokens; e.current_sol_volume = cpie.current_sol_volume; e.last_update_timestamp = cpie.last_update_timestamp; - e.min_base_amount_out = cpie.min_base_amount_out; - e.ix_name = cpie.ix_name.clone(); - e.cashback_fee_basis_points = cpie.cashback_fee_basis_points; - e.cashback = cpie.cashback; - e.buyback_fee_basis_points = cpie.buyback_fee_basis_points; - e.buyback_fee = cpie.buyback_fee; - e.virtual_quote_reserves = cpie.virtual_quote_reserves; - e.can_boost = cpie.can_boost; - e.base_supply = cpie.base_supply; - e.is_pump_pool = cpie.is_pump_pool; + overwrite_u64_if_present(&mut e.min_base_amount_out, cpie.min_base_amount_out); + if !cpie.ix_name.is_empty() { + e.ix_name = cpie.ix_name; + } + overwrite_u64_if_present( + &mut e.cashback_fee_basis_points, + cpie.cashback_fee_basis_points, + ); + overwrite_u64_if_present(&mut e.cashback, cpie.cashback); + overwrite_u64_if_present( + &mut e.buyback_fee_basis_points, + cpie.buyback_fee_basis_points, + ); + overwrite_u64_if_present(&mut e.buyback_fee, cpie.buyback_fee); + overwrite_i128_if_present( + &mut e.virtual_quote_reserves, + cpie.virtual_quote_reserves, + ); + e.can_boost |= cpie.can_boost; + overwrite_u64_if_present(&mut e.base_supply, cpie.base_supply); + e.is_pump_pool |= cpie.is_pump_pool; fill_pubkey(&mut e.base_mint, cpie.base_mint); fill_pubkey(&mut e.quote_mint, cpie.quote_mint); fill_pubkey(&mut e.pool_base_token_account, cpie.pool_base_token_account); @@ -390,14 +415,23 @@ pub fn merge(instruction_event: &mut DexEvent, cpi_log_event: DexEvent) { e.coin_creator = cpie.coin_creator; e.coin_creator_fee_basis_points = cpie.coin_creator_fee_basis_points; e.coin_creator_fee = cpie.coin_creator_fee; - e.cashback_fee_basis_points = cpie.cashback_fee_basis_points; - e.cashback = cpie.cashback; - e.buyback_fee_basis_points = cpie.buyback_fee_basis_points; - e.buyback_fee = cpie.buyback_fee; - e.virtual_quote_reserves = cpie.virtual_quote_reserves; - e.can_boost = cpie.can_boost; - e.base_supply = cpie.base_supply; - e.is_pump_pool = cpie.is_pump_pool; + overwrite_u64_if_present( + &mut e.cashback_fee_basis_points, + cpie.cashback_fee_basis_points, + ); + overwrite_u64_if_present(&mut e.cashback, cpie.cashback); + overwrite_u64_if_present( + &mut e.buyback_fee_basis_points, + cpie.buyback_fee_basis_points, + ); + overwrite_u64_if_present(&mut e.buyback_fee, cpie.buyback_fee); + overwrite_i128_if_present( + &mut e.virtual_quote_reserves, + cpie.virtual_quote_reserves, + ); + e.can_boost |= cpie.can_boost; + overwrite_u64_if_present(&mut e.base_supply, cpie.base_supply); + e.is_pump_pool |= cpie.is_pump_pool; fill_pubkey(&mut e.base_mint, cpie.base_mint); fill_pubkey(&mut e.quote_mint, cpie.quote_mint); fill_pubkey(&mut e.pool_base_token_account, cpie.pool_base_token_account); @@ -947,6 +981,35 @@ mod tests { assert_eq!(event.base_supply, 33); } + #[test] + fn pumpswap_merge_does_not_erase_present_tail_with_legacy_defaults() { + let mut instruction_event = DexEvent::PumpSwapSellEvent(PumpSwapSellEvent { + cashback_fee_basis_points: 10, + cashback: 20, + buyback_fee_basis_points: 30, + buyback_fee: 40, + virtual_quote_reserves: -500, + can_boost: true, + base_supply: 60, + is_pump_pool: true, + ..Default::default() + }); + + merge(&mut instruction_event, DexEvent::PumpSwapSellEvent(PumpSwapSellEvent::default())); + + let DexEvent::PumpSwapSellEvent(event) = instruction_event else { + panic!("expected PumpSwapSellEvent"); + }; + assert_eq!(event.cashback_fee_basis_points, 10); + assert_eq!(event.cashback, 20); + assert_eq!(event.buyback_fee_basis_points, 30); + assert_eq!(event.buyback_fee, 40); + assert_eq!(event.virtual_quote_reserves, -500); + assert!(event.can_boost); + assert_eq!(event.base_supply, 60); + assert!(event.is_pump_pool); + } + #[test] fn pumpfun_merge_replaces_sol_quote_sentinel_with_real_quote_mint() { let quote_mint = Pubkey::new_unique(); diff --git a/src/streaming/event_parser/protocols/pumpswap/events.rs b/src/streaming/event_parser/protocols/pumpswap/events.rs index f3297a4..31ca38f 100755 --- a/src/streaming/event_parser/protocols/pumpswap/events.rs +++ b/src/streaming/event_parser/protocols/pumpswap/events.rs @@ -44,10 +44,15 @@ pub struct PumpSwapBuyEvent { pub ix_name: String, pub cashback_fee_basis_points: u64, pub cashback: u64, + #[serde(default)] pub buyback_fee_basis_points: u64, + #[serde(default)] pub buyback_fee: u64, + #[serde(default)] pub virtual_quote_reserves: i128, + #[serde(default)] pub can_boost: bool, + #[serde(default)] pub base_supply: u64, #[borsh(skip)] pub is_pump_pool: bool, @@ -110,10 +115,15 @@ pub struct PumpSwapSellEvent { pub coin_creator_fee: u64, pub cashback_fee_basis_points: u64, pub cashback: u64, + #[serde(default)] pub buyback_fee_basis_points: u64, + #[serde(default)] pub buyback_fee: u64, + #[serde(default)] pub virtual_quote_reserves: i128, + #[serde(default)] pub can_boost: bool, + #[serde(default)] pub base_supply: u64, #[borsh(skip)] pub is_pump_pool: bool, @@ -251,6 +261,39 @@ pub struct PumpSwapWithdrawEvent { pub const PUMP_SWAP_WITHDRAW_EVENT_LOG_SIZE: usize = 248; +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn pumpswap_event_serde_preserves_signed_i128_extremes() { + for value in [i128::MIN, -1, i128::MAX] { + let event = PumpSwapBuyEvent { virtual_quote_reserves: value, ..Default::default() }; + let json = serde_json::to_string(&event).unwrap(); + let decoded: PumpSwapBuyEvent = serde_json::from_str(&json).unwrap(); + assert_eq!(decoded.virtual_quote_reserves, value); + } + } + + #[test] + fn pumpswap_event_serde_defaults_new_upgrade_tail() { + let mut value = serde_json::to_value(PumpSwapSellEvent::default()).unwrap(); + let object = value.as_object_mut().unwrap(); + object.remove("buyback_fee_basis_points"); + object.remove("buyback_fee"); + object.remove("virtual_quote_reserves"); + object.remove("can_boost"); + object.remove("base_supply"); + + let decoded: PumpSwapSellEvent = serde_json::from_value(value).unwrap(); + assert_eq!(decoded.buyback_fee_basis_points, 0); + assert_eq!(decoded.buyback_fee, 0); + assert_eq!(decoded.virtual_quote_reserves, 0); + assert!(!decoded.can_boost); + assert_eq!(decoded.base_supply, 0); + } +} + /// 全局配置 #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)] pub struct PumpSwapGlobalConfigAccountEvent { diff --git a/src/streaming/event_parser/protocols/pumpswap/types.rs b/src/streaming/event_parser/protocols/pumpswap/types.rs index c1f7f3a..7e86409 100644 --- a/src/streaming/event_parser/protocols/pumpswap/types.rs +++ b/src/streaming/event_parser/protocols/pumpswap/types.rs @@ -33,6 +33,7 @@ pub struct Pool { pub coin_creator: Pubkey, pub is_mayhem_mode: bool, pub is_cashback_coin: bool, + #[serde(default)] pub virtual_quote_reserves: i128, } diff --git a/src/streaming/parser_sdk_bridge/mod.rs b/src/streaming/parser_sdk_bridge/mod.rs index 61a5e0a..78c9ef6 100644 --- a/src/streaming/parser_sdk_bridge/mod.rs +++ b/src/streaming/parser_sdk_bridge/mod.rs @@ -245,7 +245,7 @@ mod tests { #[test] fn converts_pumpswap_buy_boost_tail() { let event = PbPumpSwapBuy { - virtual_quote_reserves: -123, + virtual_quote_reserves: i128::MIN, buyback_fee_basis_points: 11, buyback_fee: 22, can_boost: true, @@ -258,7 +258,7 @@ mod tests { let DexEvent::PumpSwapBuyEvent(converted) = converted else { panic!("expected PumpSwapBuyEvent"); }; - assert_eq!(converted.virtual_quote_reserves, -123); + assert_eq!(converted.virtual_quote_reserves, i128::MIN); assert_eq!(converted.buyback_fee_basis_points, 11); assert_eq!(converted.buyback_fee, 22); assert!(converted.can_boost); @@ -268,7 +268,7 @@ mod tests { #[test] fn converts_pumpswap_sell_boost_tail() { let event = PbPumpSwapSell { - virtual_quote_reserves: 123, + virtual_quote_reserves: i128::MAX, buyback_fee_basis_points: 44, buyback_fee: 55, can_boost: true, @@ -281,7 +281,7 @@ mod tests { let DexEvent::PumpSwapSellEvent(converted) = converted else { panic!("expected PumpSwapSellEvent"); }; - assert_eq!(converted.virtual_quote_reserves, 123); + assert_eq!(converted.virtual_quote_reserves, i128::MAX); assert_eq!(converted.buyback_fee_basis_points, 44); assert_eq!(converted.buyback_fee, 55); assert!(converted.can_boost);