diff --git a/Cargo.toml b/Cargo.toml index 12b0ec0..85c1e1f 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "solana-streamer-sdk" -version = "0.4.8" +version = "0.4.9" edition = "2021" authors = ["William ", "sgxiang ", "wei <1415121722@qq.com>"] repository = "https://github.com/0xfnzero/solana-streamer" diff --git a/README.md b/README.md index b890156..fa7c3c5 100755 --- a/README.md +++ b/README.md @@ -107,14 +107,14 @@ Add the dependency to your `Cargo.toml`: ```toml # Add to your Cargo.toml -solana-streamer-sdk = { path = "./solana-streamer", version = "0.4.8" } +solana-streamer-sdk = { path = "./solana-streamer", version = "0.4.9" } ``` ### Use crates.io ```toml # Add to your Cargo.toml -solana-streamer-sdk = "0.4.8" +solana-streamer-sdk = "0.4.9" ``` ## ⚙️ Configuration System diff --git a/README_CN.md b/README_CN.md index c1cf59a..035c6fb 100644 --- a/README_CN.md +++ b/README_CN.md @@ -107,14 +107,14 @@ git clone https://github.com/0xfnzero/solana-streamer ```toml # 添加到您的 Cargo.toml -solana-streamer-sdk = { path = "./solana-streamer", version = "0.4.8" } +solana-streamer-sdk = { path = "./solana-streamer", version = "0.4.9" } ``` ### 使用 crates.io ```toml # 添加到您的 Cargo.toml -solana-streamer-sdk = "0.4.8" +solana-streamer-sdk = "0.4.9" ``` ## ⚙️ 配置系统 diff --git a/examples/parse_tx_events.rs b/examples/parse_tx_events.rs index 5f54b8d..0330cb8 100644 --- a/examples/parse_tx_events.rs +++ b/examples/parse_tx_events.rs @@ -1,10 +1,9 @@ use anyhow::Result; use solana_sdk::commitment_config::CommitmentConfig; +use solana_streamer_sdk::streaming::event_parser::core::event_parser::EventParser; +use solana_streamer_sdk::streaming::event_parser::Protocol; use solana_streamer_sdk::streaming::event_parser::UnifiedEvent; -use solana_streamer_sdk::streaming::event_parser::{ - protocols::MutilEventParser, EventParser, Protocol, -}; use std::str::FromStr; use std::sync::Arc; @@ -99,7 +98,7 @@ async fn get_single_transaction_details(signature_str: &str) -> Result<()> { Protocol::RaydiumCpmm, Protocol::RaydiumAmmV4, ]; - let parser: Arc = Arc::new(MutilEventParser::new(protocols, None)); + let parser: Arc = Arc::new(EventParser::new(protocols, None)); parser .parse_encoded_confirmed_transaction_with_status_meta( signature, diff --git a/src/streaming/common/event_processor.rs b/src/streaming/common/event_processor.rs index dbe6517..5069f9c 100644 --- a/src/streaming/common/event_processor.rs +++ b/src/streaming/common/event_processor.rs @@ -13,10 +13,8 @@ use crate::streaming::event_parser::common::filter::EventTypeFilter; use crate::streaming::event_parser::core::account_event_parser::AccountEventParser; use crate::streaming::event_parser::core::common_event_parser::CommonEventParser; -use crate::streaming::event_parser::EventParser; -use crate::streaming::event_parser::{ - core::traits::UnifiedEvent, protocols::mutil::parser::MutilEventParser, Protocol, -}; +use crate::streaming::event_parser::core::event_parser::EventParser; +use crate::streaming::event_parser::{core::traits::UnifiedEvent, Protocol}; use crate::streaming::grpc::{BackpressureConfig, EventPretty}; use crate::streaming::shred::TransactionWithSlot; use once_cell::sync::OnceCell; @@ -25,7 +23,7 @@ use once_cell::sync::OnceCell; pub struct EventProcessor { pub(crate) metrics_manager: MetricsManager, pub(crate) config: ClientConfig, - pub(crate) parser_cache: OnceCell>, + pub(crate) parser_cache: OnceCell>, pub(crate) protocols: Vec, pub(crate) event_type_filter: Option, pub(crate) callback: Option) + Send + Sync>>, @@ -77,7 +75,7 @@ impl EventProcessor { let protocols_ref = &self.protocols; let event_type_filter_ref = self.event_type_filter.as_ref(); self.parser_cache.get_or_init(|| { - Arc::new(MutilEventParser::new(protocols_ref.clone(), event_type_filter_ref.cloned())) + Arc::new(EventParser::new(protocols_ref.clone(), event_type_filter_ref.cloned())) }); if matches!(self.backpressure_config.strategy, BackpressureStrategy::Block) { @@ -85,7 +83,7 @@ impl EventProcessor { } } - pub fn get_parser(&self) -> Arc { + pub fn get_parser(&self) -> Arc { self.parser_cache.get().unwrap().clone() } diff --git a/src/streaming/event_parser/common/high_performance_clock.rs b/src/streaming/event_parser/common/high_performance_clock.rs new file mode 100644 index 0000000..3faf27c --- /dev/null +++ b/src/streaming/event_parser/common/high_performance_clock.rs @@ -0,0 +1,130 @@ +use std::fmt::Debug; +use std::time::Instant; + +/// 高性能时钟管理器,减少系统调用开销并最小化延迟 +#[derive(Debug)] +pub struct HighPerformanceClock { + /// 基准时间点(程序启动时的单调时钟时间) + base_instant: Instant, + /// 基准时间点对应的UTC时间戳(微秒) + base_timestamp_us: i64, + /// 上次校准时间(用于检测是否需要重新校准) + last_calibration: Instant, + /// 校准间隔(秒) + calibration_interval_secs: u64, +} + +impl HighPerformanceClock { + /// 创建新的高性能时钟 + pub fn new() -> Self { + Self::new_with_calibration_interval(300) // 默认5分钟校准一次 + } + + /// 创建带自定义校准间隔的高性能时钟 + pub fn new_with_calibration_interval(calibration_interval_secs: u64) -> Self { + // 通过多次采样来减少初始化误差 + let mut best_offset = i64::MAX; + let mut best_instant = Instant::now(); + let mut best_timestamp = chrono::Utc::now().timestamp_micros(); + + // 进行3次采样,选择延迟最小的 + for _ in 0..3 { + let instant_before = Instant::now(); + let timestamp = chrono::Utc::now().timestamp_micros(); + let instant_after = Instant::now(); + + let sample_latency = instant_after.duration_since(instant_before).as_nanos() as i64; + + if sample_latency < best_offset { + best_offset = sample_latency; + best_instant = instant_before; + best_timestamp = timestamp; + } + } + + Self { + base_instant: best_instant, + base_timestamp_us: best_timestamp, + last_calibration: best_instant, + calibration_interval_secs, + } + } + + /// 获取当前时间戳(微秒),使用单调时钟计算,避免系统调用 + #[inline(always)] + pub fn now_micros(&self) -> i64 { + let elapsed = self.base_instant.elapsed(); + self.base_timestamp_us + elapsed.as_micros() as i64 + } + + /// 获取高精度当前时间戳(微秒),在必要时进行校准 + pub fn now_micros_with_calibration(&mut self) -> i64 { + // 检查是否需要重新校准 + if self.last_calibration.elapsed().as_secs() >= self.calibration_interval_secs { + self.recalibrate(); + } + self.now_micros() + } + + /// 重新校准时钟,减少累积漂移 + fn recalibrate(&mut self) { + let current_monotonic = Instant::now(); + let current_utc = chrono::Utc::now().timestamp_micros(); + + // 计算预期的UTC时间戳(基于单调时钟) + let expected_utc = self.base_timestamp_us + + current_monotonic.duration_since(self.base_instant).as_micros() as i64; + + // 计算漂移量 + let drift_us = current_utc - expected_utc; + + // 如果漂移超过1毫秒,进行校准 + if drift_us.abs() > 1000 { + self.base_instant = current_monotonic; + self.base_timestamp_us = current_utc; + } + + self.last_calibration = current_monotonic; + } + + /// 计算从指定时间戳到现在的消耗时间(微秒) + #[inline(always)] + pub fn elapsed_micros_since(&self, start_timestamp_us: i64) -> i64 { + self.now_micros() - start_timestamp_us + } + + /// 获取高精度纳秒时间戳 + #[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 + } + + /// 重置时钟(强制重新初始化) + pub fn reset(&mut self) { + *self = Self::new_with_calibration_interval(self.calibration_interval_secs); + } +} + +impl Default for HighPerformanceClock { + fn default() -> Self { + Self::new() + } +} + +/// 全局高性能时钟实例 +static HIGH_PERF_CLOCK: once_cell::sync::OnceCell = + once_cell::sync::OnceCell::new(); + +/// 获取全局高性能时钟实例(最简单的实现) +#[inline(always)] +pub fn get_high_perf_clock() -> i64 { + let clock = HIGH_PERF_CLOCK.get_or_init(HighPerformanceClock::new); + clock.now_micros() +} + +/// 计算从指定时间戳到现在的消耗时间(微秒) +#[inline(always)] +pub fn elapsed_micros_since(start_timestamp_us: i64) -> i64 { + get_high_perf_clock() - start_timestamp_us +} diff --git a/src/streaming/event_parser/common/mod.rs b/src/streaming/event_parser/common/mod.rs index 92424c6..479d978 100755 --- a/src/streaming/event_parser/common/mod.rs +++ b/src/streaming/event_parser/common/mod.rs @@ -1,6 +1,7 @@ pub mod types; pub mod utils; pub mod filter; +pub mod high_performance_clock; /// 自动生成UnifiedEvent trait实现的宏 #[macro_export] diff --git a/src/streaming/event_parser/common/types.rs b/src/streaming/event_parser/common/types.rs index d12e41c..0aabf08 100755 --- a/src/streaming/event_parser/common/types.rs +++ b/src/streaming/event_parser/common/types.rs @@ -24,7 +24,6 @@ use crate::{ // Object pool size configuration const EVENT_METADATA_POOL_SIZE: usize = 1000; -const TRANSFER_DATA_POOL_SIZE: usize = 2000; /// Event metadata object pool pub struct EventMetadataPool { diff --git a/src/streaming/event_parser/core/account_event_parser.rs b/src/streaming/event_parser/core/account_event_parser.rs index 8713059..d572420 100644 --- a/src/streaming/event_parser/core/account_event_parser.rs +++ b/src/streaming/event_parser/core/account_event_parser.rs @@ -1,8 +1,9 @@ use crate::impl_unified_event; use crate::streaming::common::SimdUtils; use crate::streaming::event_parser::common::filter::EventTypeFilter; +use crate::streaming::event_parser::common::high_performance_clock::elapsed_micros_since; use crate::streaming::event_parser::common::{EventMetadata, EventType, ProtocolType}; -use crate::streaming::event_parser::core::traits::{elapsed_micros_since, UnifiedEvent}; +use crate::streaming::event_parser::core::traits::UnifiedEvent; use crate::streaming::event_parser::protocols::bonk::parser::BONK_PROGRAM_ID; use crate::streaming::event_parser::protocols::pumpfun::parser::PUMPFUN_PROGRAM_ID; use crate::streaming::event_parser::protocols::pumpswap::parser::PUMPSWAP_PROGRAM_ID; diff --git a/src/streaming/event_parser/core/common_event_parser.rs b/src/streaming/event_parser/core/common_event_parser.rs index 56f0e8f..633a1b8 100644 --- a/src/streaming/event_parser/core/common_event_parser.rs +++ b/src/streaming/event_parser/core/common_event_parser.rs @@ -1,4 +1,5 @@ -use crate::streaming::event_parser::core::traits::{elapsed_micros_since, UnifiedEvent}; +use crate::streaming::event_parser::common::high_performance_clock::elapsed_micros_since; +use crate::streaming::event_parser::core::traits::UnifiedEvent; use crate::streaming::event_parser::protocols::block::block_meta_event::BlockMetaEvent; pub struct CommonEventParser {} @@ -10,10 +11,8 @@ impl CommonEventParser { block_time_ms: i64, recv_us: i64, ) -> Box { - let mut block_meta_event = - BlockMetaEvent::new(slot, block_hash, block_time_ms, recv_us); - block_meta_event - .set_handle_us(elapsed_micros_since(recv_us)); + let mut block_meta_event = BlockMetaEvent::new(slot, block_hash, block_time_ms, recv_us); + block_meta_event.set_handle_us(elapsed_micros_since(recv_us)); Box::new(block_meta_event) } } diff --git a/src/streaming/event_parser/core/event_parser.rs b/src/streaming/event_parser/core/event_parser.rs new file mode 100644 index 0000000..3ff1b73 --- /dev/null +++ b/src/streaming/event_parser/core/event_parser.rs @@ -0,0 +1,1134 @@ +use crate::streaming::{ + common::SimdUtils, + event_parser::{ + common::{ + filter::EventTypeFilter, + high_performance_clock::{elapsed_micros_since, get_high_perf_clock}, + parse_swap_data_from_next_grpc_instructions, parse_swap_data_from_next_instructions, + EventMetadata, EventType, ProtocolType, + }, + core::global_state::{ + add_bonk_dev_address, add_dev_address, is_bonk_dev_address_in_signature, + is_dev_address_in_signature, + }, + protocols::{ + bonk::{parser::BONK_PROGRAM_ID, BonkPoolCreateEvent, BonkTradeEvent}, + pumpfun::{parser::PUMPFUN_PROGRAM_ID, PumpFunCreateTokenEvent, PumpFunTradeEvent}, + pumpswap::{parser::PUMPSWAP_PROGRAM_ID, PumpSwapBuyEvent, PumpSwapSellEvent}, + raydium_amm_v4::parser::RAYDIUM_AMM_V4_PROGRAM_ID, + raydium_clmm::parser::RAYDIUM_CLMM_PROGRAM_ID, + raydium_cpmm::parser::RAYDIUM_CPMM_PROGRAM_ID, + }, + Protocol, UnifiedEvent, + }, +}; +use prost_types::Timestamp; +use solana_sdk::{ + bs58, instruction::CompiledInstruction, pubkey::Pubkey, signature::Signature, + transaction::VersionedTransaction, +}; +use solana_transaction_status::{ + EncodedConfirmedTransactionWithStatusMeta, InnerInstruction, InnerInstructions, UiInstruction, +}; +use std::{ + collections::HashMap, + sync::{Arc, LazyLock}, +}; +use yellowstone_grpc_proto::geyser::SubscribeUpdateTransactionInfo; + +/// 高性能账户公钥缓存,避免重复Vec分配 +#[derive(Debug)] +pub struct AccountPubkeyCache { + /// 预分配的账户公钥向量,避免每次重新分配 + cache: Vec, +} + +impl AccountPubkeyCache { + /// 创建新的账户公钥缓存 + pub fn new() -> Self { + Self { + cache: Vec::with_capacity(32), // 预分配32个位置,覆盖大多数交易 + } + } + + /// 从指令账户索引构建账户公钥向量,重用缓存内存 + #[inline] + pub fn build_account_pubkeys( + &mut self, + instruction_accounts: &[u8], + all_accounts: &[Pubkey], + ) -> &[Pubkey] { + self.cache.clear(); + + // 确保容量足够,避免动态扩容 + if self.cache.capacity() < instruction_accounts.len() { + self.cache.reserve(instruction_accounts.len() - self.cache.capacity()); + } + + // 快速填充账户公钥 + for &idx in instruction_accounts.iter() { + if (idx as usize) < all_accounts.len() { + self.cache.push(all_accounts[idx as usize]); + } + } + + &self.cache + } +} + +impl Default for AccountPubkeyCache { + fn default() -> Self { + Self::new() + } +} + +/// 内联指令事件解析器 +pub type InnerInstructionEventParser = + fn(data: &[u8], metadata: EventMetadata) -> Option>; + +/// 指令事件解析器 +pub type InstructionEventParser = + fn(data: &[u8], accounts: &[Pubkey], metadata: EventMetadata) -> Option>; + +/// 通用事件解析器配置 +#[derive(Debug, Clone)] +pub struct GenericEventParseConfig { + pub program_id: Pubkey, + pub protocol_type: ProtocolType, + pub inner_instruction_discriminator: &'static [u8], + pub instruction_discriminator: &'static [u8], + pub event_type: EventType, + pub inner_instruction_parser: Option, + pub instruction_parser: Option, + pub requires_inner_instruction: bool, +} + +pub static EVENT_PARSERS: LazyLock> = + LazyLock::new(|| { + // 预分配容量,避免动态扩容 + let mut parsers: HashMap = + HashMap::with_capacity(6); + parsers.insert( + Protocol::PumpSwap, + ( + PUMPSWAP_PROGRAM_ID, + crate::streaming::event_parser::protocols::pumpswap::parser::CONFIGS, + ), + ); + parsers.insert( + Protocol::PumpFun, + ( + PUMPFUN_PROGRAM_ID, + crate::streaming::event_parser::protocols::pumpfun::parser::CONFIGS, + ), + ); + parsers.insert( + Protocol::Bonk, + (BONK_PROGRAM_ID, crate::streaming::event_parser::protocols::bonk::parser::CONFIGS), + ); + parsers.insert( + Protocol::RaydiumCpmm, + ( + RAYDIUM_CPMM_PROGRAM_ID, + crate::streaming::event_parser::protocols::raydium_cpmm::parser::CONFIGS, + ), + ); + parsers.insert( + Protocol::RaydiumClmm, + ( + RAYDIUM_CLMM_PROGRAM_ID, + crate::streaming::event_parser::protocols::raydium_clmm::parser::CONFIGS, + ), + ); + parsers.insert( + Protocol::RaydiumAmmV4, + ( + RAYDIUM_AMM_V4_PROGRAM_ID, + crate::streaming::event_parser::protocols::raydium_amm_v4::parser::CONFIGS, + ), + ); + parsers + }); + +/// 通用事件解析器基类 +pub struct EventParser { + pub program_ids: Vec, + // pub inner_instruction_configs: HashMap, Vec>, + pub instruction_configs: HashMap, Vec>, + /// 账户公钥缓存,避免重复分配 + pub account_cache: parking_lot::Mutex, +} + +impl EventParser { + pub fn new(protocols: Vec, event_type_filter: Option) -> Self { + let mut instruction_configs = HashMap::with_capacity(protocols.len()); + let mut program_ids = Vec::with_capacity(protocols.len()); + // Configure all event types + for protocol in protocols { + let parse = EVENT_PARSERS.get(&protocol).unwrap(); + // Merge instruction_configs, append configurations to existing Vec + parse + .1 + .iter() + .filter(|config| { + event_type_filter + .as_ref() + .map(|filter| filter.include.contains(&config.event_type)) + .unwrap_or(true) + }) + .for_each(|config| { + instruction_configs + .entry(config.instruction_discriminator.to_vec()) + .or_insert_with(Vec::new) + .push(config.clone()); + }); + + // Append program_ids (this is already appending) + program_ids.push(parse.0); + } + let account_cache = parking_lot::Mutex::new(AccountPubkeyCache::new()); + + Self { program_ids, instruction_configs, account_cache } + } + + #[allow(clippy::too_many_arguments)] + async fn parse_instruction_events_from_grpc_transaction( + &self, + compiled_instructions: &[yellowstone_grpc_proto::prelude::CompiledInstruction], + signature: Signature, + slot: Option, + block_time: Option, + recv_us: i64, + accounts: &[Pubkey], + inner_instructions: &[yellowstone_grpc_proto::prelude::InnerInstructions], + bot_wallet: Option, + transaction_index: Option, + callback: Arc Fn(&'a Box) + Send + Sync>, + ) -> anyhow::Result<()> { + // 获取交易的指令和账户 + let mut accounts = accounts.to_vec(); + // 检查交易中是否包含程序 + let has_program = accounts.iter().any(|account| self.should_handle(account)); + if has_program { + // 解析每个指令 + for (index, instruction) in compiled_instructions.iter().enumerate() { + if let Some(program_id) = accounts.get(instruction.program_id_index as usize) { + if self.should_handle(program_id) { + let max_idx = instruction.accounts.iter().max().unwrap_or(&0); + // 补齐accounts(使用Pubkey::default()) + if *max_idx as usize > accounts.len() { + for _i in accounts.len()..*max_idx as usize { + accounts.push(Pubkey::default()); + } + } + let inner_instructions = inner_instructions + .iter() + .find(|inner_instruction| inner_instruction.index == index as u32); + self.parse_events_from_grpc_instruction( + instruction, + &accounts, + signature, + slot.unwrap_or(0), + block_time, + recv_us, + index as i64, + None, + bot_wallet, + transaction_index, + inner_instructions, + Arc::clone(&callback), + )?; + + // Immediately process inner instructions for correct ordering + if let Some(inner_instructions) = inner_instructions { + for (inner_index, inner_instruction) in + inner_instructions.instructions.iter().enumerate() + { + let inner_accounts = &inner_instruction.accounts; + let data = &inner_instruction.data; + let instruction = + yellowstone_grpc_proto::prelude::CompiledInstruction { + program_id_index: inner_instruction.program_id_index, + accounts: inner_accounts.to_vec(), + data: data.to_vec(), + }; + self.parse_events_from_grpc_instruction( + &instruction, + &accounts, + signature, + slot.unwrap_or(0), + block_time, + recv_us, + inner_instructions.index as i64, + Some(inner_index as i64), + bot_wallet, + transaction_index, + Some(&inner_instructions), + Arc::clone(&callback), + )?; + } + } + } + } + } + } + Ok(()) + } + + /// 从VersionedTransaction中解析指令事件的通用方法 + #[allow(clippy::too_many_arguments)] + async fn parse_instruction_events_from_versioned_transaction( + &self, + transaction: &VersionedTransaction, + signature: Signature, + slot: Option, + block_time: Option, + recv_us: i64, + accounts: &[Pubkey], + inner_instructions: &[InnerInstructions], + bot_wallet: Option, + transaction_index: Option, + callback: Arc Fn(&'a Box) + Send + Sync>, + ) -> anyhow::Result<()> { + // 获取交易的指令和账户 + let compiled_instructions = transaction.message.instructions(); + let mut accounts: Vec = accounts.to_vec(); + // 检查交易中是否包含程序 + let has_program = accounts.iter().any(|account| self.should_handle(account)); + if has_program { + // 解析每个指令 + for (index, instruction) in compiled_instructions.iter().enumerate() { + if let Some(program_id) = accounts.get(instruction.program_id_index as usize) { + if self.should_handle(program_id) { + let max_idx = instruction.accounts.iter().max().unwrap_or(&0); + // 补齐accounts(使用Pubkey::default()) + if *max_idx as usize > accounts.len() { + for _i in accounts.len()..*max_idx as usize { + accounts.push(Pubkey::default()); + } + } + let inner_instructions = inner_instructions + .iter() + .find(|inner_instruction| inner_instruction.index == index as u8); + self.parse_events_from_instruction( + instruction, + &accounts, + signature, + slot.unwrap_or(0), + block_time, + recv_us, + index as i64, + None, + bot_wallet, + transaction_index, + inner_instructions, + Arc::clone(&callback), + )?; + + // Immediately process inner instructions for correct ordering + if let Some(inner_instructions) = inner_instructions { + for (inner_index, inner_instruction) in + inner_instructions.instructions.iter().enumerate() + { + self.parse_events_from_instruction( + &inner_instruction.instruction, + &accounts, + signature, + slot.unwrap_or(0), + block_time, + recv_us, + index as i64, + Some(inner_index as i64), + bot_wallet, + transaction_index, + Some(&inner_instructions), + Arc::clone(&callback), + )?; + } + } + } + } + } + } + Ok(()) + } + + pub async fn parse_versioned_transaction_owned( + &self, + versioned_tx: VersionedTransaction, + signature: Signature, + slot: Option, + block_time: Option, + recv_us: i64, + bot_wallet: Option, + transaction_index: Option, + inner_instructions: &[InnerInstructions], + callback: Arc) + Send + Sync>, + ) -> anyhow::Result<()> { + // 创建适配器回调,将所有权回调转换为引用回调 + let adapter_callback = Arc::new(move |event: &Box| { + callback(event.clone_boxed()); + }); + self.parse_versioned_transaction( + &versioned_tx, + signature, + slot, + block_time, + recv_us, + bot_wallet, + transaction_index, + inner_instructions, + adapter_callback, + ) + .await?; + Ok(()) + } + + async fn parse_versioned_transaction( + &self, + versioned_tx: &VersionedTransaction, + signature: Signature, + slot: Option, + block_time: Option, + recv_us: i64, + bot_wallet: Option, + transaction_index: Option, + inner_instructions: &[InnerInstructions], + callback: Arc Fn(&'a Box) + Send + Sync>, + ) -> anyhow::Result<()> { + let accounts: Vec = versioned_tx.message.static_account_keys().to_vec(); + self.parse_instruction_events_from_versioned_transaction( + versioned_tx, + signature, + slot, + block_time, + recv_us, + &accounts, + inner_instructions, + bot_wallet, + transaction_index, + callback, + ) + .await?; + Ok(()) + } + + pub async fn parse_grpc_transaction_owned( + &self, + grpc_tx: SubscribeUpdateTransactionInfo, + signature: Signature, + slot: Option, + block_time: Option, + recv_us: i64, + bot_wallet: Option, + transaction_index: Option, + callback: Arc) + Send + Sync>, + ) -> anyhow::Result<()> { + // 创建适配器回调,将所有权回调转换为引用回调 + let adapter_callback = Arc::new(move |event: &Box| { + callback(event.clone_boxed()); + }); + // 调用原始方法 + self.parse_grpc_transaction( + grpc_tx, + signature, + slot, + block_time, + recv_us, + bot_wallet, + transaction_index, + adapter_callback, + ) + .await + } + + async fn parse_grpc_transaction( + &self, + grpc_tx: SubscribeUpdateTransactionInfo, + signature: Signature, + slot: Option, + block_time: Option, + recv_us: i64, + bot_wallet: Option, + transaction_index: Option, + callback: Arc Fn(&'a Box) + Send + Sync>, + ) -> anyhow::Result<()> { + if let Some(transition) = grpc_tx.transaction { + if let Some(message) = &transition.message { + let mut address_table_lookups: Vec> = vec![]; + let mut inner_instructions: Vec< + yellowstone_grpc_proto::solana::storage::confirmed_block::InnerInstructions, + > = vec![]; + + if let Some(meta) = grpc_tx.meta { + inner_instructions = meta.inner_instructions; + address_table_lookups.reserve( + meta.loaded_writable_addresses.len() + meta.loaded_writable_addresses.len(), + ); + let loaded_writable_addresses = meta.loaded_writable_addresses; + let loaded_readonly_addresses = meta.loaded_readonly_addresses; + address_table_lookups.extend( + loaded_writable_addresses.into_iter().chain(loaded_readonly_addresses), + ); + } + + let mut accounts_bytes: Vec> = + 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 + let accounts: Vec = accounts_bytes + .iter() + .filter_map(|account| { + if account.len() == 32 { + Some(Pubkey::try_from(account.as_slice()).unwrap_or_default()) + } else { + None + } + }) + .collect(); + // 使用 Arc 包装共享数据,避免不必要的克隆 + let accounts_arc = Arc::new(accounts); + let inner_instructions_arc = Arc::new(inner_instructions); + // 解析指令事件 + let instructions = &message.instructions; + self.parse_instruction_events_from_grpc_transaction( + &instructions, + signature, + slot, + block_time, + recv_us, + &accounts_arc, + &inner_instructions_arc, + bot_wallet, + transaction_index, + callback.clone(), + ) + .await?; + } + } + + Ok(()) + } + + pub async fn parse_encoded_confirmed_transaction_with_status_meta( + &self, + signature: Signature, + transaction: EncodedConfirmedTransactionWithStatusMeta, + callback: Arc Fn(&'a Box) + Send + Sync>, + ) -> anyhow::Result<()> { + let versioned_tx = match transaction.transaction.transaction.decode() { + Some(tx) => tx, + None => { + return Ok(()); + } + }; + let mut inner_instructions_vec: Vec = Vec::new(); + if let Some(meta) = &transaction.transaction.meta { + // 从meta中获取inner_instructions,处理OptionSerializer类型 + if let solana_transaction_status::option_serializer::OptionSerializer::Some( + ui_inner_insts, + ) = &meta.inner_instructions + { + // 将UiInnerInstructions转换为InnerInstructions + for ui_inner in ui_inner_insts { + let mut converted_instructions = Vec::new(); + + // 转换每个UiInstruction为InnerInstruction + for ui_instruction in &ui_inner.instructions { + if let UiInstruction::Compiled(ui_compiled) = ui_instruction { + // 解码base58编码的data + if let Ok(data) = bs58::decode(&ui_compiled.data).into_vec() { + // base64解码 + let compiled_instruction = CompiledInstruction { + program_id_index: ui_compiled.program_id_index, + accounts: ui_compiled.accounts.clone(), + data, + }; + + let inner_instruction = InnerInstruction { + instruction: compiled_instruction, + stack_height: ui_compiled.stack_height, + }; + + converted_instructions.push(inner_instruction); + } + } + } + + let inner_instructions = InnerInstructions { + index: ui_inner.index, + instructions: converted_instructions, + }; + + inner_instructions_vec.push(inner_instructions); + } + } + } + let inner_instructions: &[InnerInstructions] = &inner_instructions_vec; + + let meta = transaction.transaction.meta; + let mut address_table_lookups: Vec = vec![]; + if let Some(meta) = meta { + if let solana_transaction_status::option_serializer::OptionSerializer::Some( + loaded_addresses, + ) = &meta.loaded_addresses + { + address_table_lookups + .reserve(loaded_addresses.writable.len() + loaded_addresses.readonly.len()); + address_table_lookups.extend( + loaded_addresses + .writable + .iter() + .filter_map(|s| s.parse::().ok()) + .chain( + loaded_addresses + .readonly + .iter() + .filter_map(|s| s.parse::().ok()), + ), + ); + } + } + let mut accounts = Vec::with_capacity( + versioned_tx.message.static_account_keys().len() + address_table_lookups.len(), + ); + accounts.extend_from_slice(versioned_tx.message.static_account_keys()); + accounts.extend(address_table_lookups); + // 使用 Arc 包装共享数据,避免不必要的克隆 + let accounts_arc = Arc::new(accounts); + let inner_instructions_arc = Arc::new(inner_instructions); + + let slot = transaction.slot; + let block_time = transaction.block_time.map(|t| Timestamp { seconds: t as i64, nanos: 0 }); + let recv_us = get_high_perf_clock(); + let bot_wallet = None; + let transaction_index = None; + // 解析指令事件 + self.parse_instruction_events_from_versioned_transaction( + &versioned_tx, + signature, + Some(slot), + block_time, + recv_us, + &accounts_arc, + &inner_instructions_arc, + bot_wallet, + transaction_index, + callback.clone(), + ) + .await?; + + Ok(()) + } + + /// 通用的内联指令解析方法 + #[allow(clippy::too_many_arguments)] + fn parse_inner_instruction_event( + &self, + config: &GenericEventParseConfig, + data: &[u8], + signature: Signature, + slot: u64, + block_time: Option, + recv_us: i64, + outer_index: i64, + inner_index: Option, + transaction_index: Option, + ) -> Option> { + if let Some(parser) = config.inner_instruction_parser { + 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( + signature, + slot, + timestamp.seconds, + block_time_ms, + config.protocol_type.clone(), + config.event_type.clone(), + config.program_id, + outer_index, + inner_index, + recv_us, + transaction_index, + ); + parser(data, metadata) + } else { + None + } + } + + /// 通用的指令解析方法 + #[allow(clippy::too_many_arguments)] + fn parse_instruction_event( + &self, + config: &GenericEventParseConfig, + data: &[u8], + account_pubkeys: &[Pubkey], + signature: Signature, + slot: u64, + block_time: Option, + recv_us: i64, + outer_index: i64, + inner_index: Option, + transaction_index: Option, + ) -> Option> { + if let Some(parser) = config.instruction_parser { + 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( + signature, + slot, + timestamp.seconds, + block_time_ms, + config.protocol_type.clone(), + config.event_type.clone(), + config.program_id, + outer_index, + inner_index, + recv_us, + transaction_index, + ); + parser(data, account_pubkeys, metadata) + } else { + None + } + } + + /// 从内联指令中解析事件数据 + #[allow(clippy::too_many_arguments)] + fn parse_events_from_inner_instruction( + &self, + inner_instruction: &CompiledInstruction, + signature: Signature, + slot: u64, + block_time: Option, + recv_us: i64, + outer_index: i64, + inner_index: Option, + transaction_index: Option, + config: &GenericEventParseConfig, + ) -> Vec> { + // Use SIMD-optimized data validation with correct discriminator length + let discriminator_len = config.inner_instruction_discriminator.len(); + if !SimdUtils::validate_instruction_data_simd( + &inner_instruction.data, + 16, + discriminator_len, + ) { + return Vec::new(); + } + + // Use SIMD-optimized discriminator matching + if !SimdUtils::fast_discriminator_match( + &inner_instruction.data, + config.inner_instruction_discriminator, + ) { + return Vec::new(); + } + + let data = &inner_instruction.data[16..]; + let mut events = Vec::new(); + if let Some(event) = self.parse_inner_instruction_event( + config, + data, + signature, + slot, + block_time, + recv_us, + outer_index, + inner_index, + transaction_index, + ) { + events.push(event); + } + events + } + + /// 从内联指令中解析事件数据 + #[allow(clippy::too_many_arguments)] + fn parse_events_from_grpc_inner_instruction( + &self, + inner_instruction: &yellowstone_grpc_proto::prelude::InnerInstruction, + signature: Signature, + slot: u64, + block_time: Option, + recv_us: i64, + outer_index: i64, + inner_index: Option, + transaction_index: Option, + config: &GenericEventParseConfig, + ) -> Vec> { + // Use SIMD-optimized data validation with correct discriminator length + let discriminator_len = config.inner_instruction_discriminator.len(); + if !SimdUtils::validate_instruction_data_simd( + &inner_instruction.data, + 16, + discriminator_len, + ) { + return Vec::new(); + } + + // Use SIMD-optimized discriminator matching + if !SimdUtils::fast_discriminator_match( + &inner_instruction.data, + config.inner_instruction_discriminator, + ) { + return Vec::new(); + } + + let data = &inner_instruction.data[16..]; + let mut events = Vec::new(); + if let Some(event) = self.parse_inner_instruction_event( + config, + data, + signature, + slot, + block_time, + recv_us, + outer_index, + inner_index, + transaction_index, + ) { + events.push(event); + } + events + } + + /// 从指令中解析事件 + #[allow(clippy::too_many_arguments)] + fn parse_events_from_instruction( + &self, + instruction: &CompiledInstruction, + accounts: &[Pubkey], + signature: Signature, + slot: u64, + block_time: Option, + recv_us: i64, + outer_index: i64, + inner_index: Option, + bot_wallet: Option, + transaction_index: Option, + inner_instructions: Option<&InnerInstructions>, + callback: Arc Fn(&'a Box) + Send + Sync>, + ) -> anyhow::Result<()> { + let program_id = accounts[instruction.program_id_index as usize]; + if !self.should_handle(&program_id) { + return Ok(()); + } + // 一维化并行处理:将所有 (discriminator, config) 组合展开并行处理 + let all_processing_params: Vec<_> = self + .instruction_configs + .iter() + .filter(|(disc, _)| { + // Use SIMD-optimized data validation and discriminator matching + SimdUtils::validate_instruction_data_simd(&instruction.data, disc.len(), disc.len()) + && SimdUtils::fast_discriminator_match(&instruction.data, disc) + }) + .flat_map(|(disc, configs)| { + configs + .iter() + .filter(|config| config.program_id == program_id) + .map(move |config| (disc, config)) + }) + .collect(); + + // Use SIMD-optimized account indices validation (只需检查一次) + if !SimdUtils::validate_account_indices_simd(&instruction.accounts, accounts.len()) { + return Ok(()); + } + + // 使用缓存构建账户公钥列表,避免重复分配 (只需构建一次) + let account_pubkeys = { + let mut cache_guard = self.account_cache.lock(); + cache_guard.build_account_pubkeys(&instruction.accounts, accounts).to_vec() + }; + + // 并行处理所有 (discriminator, config) 组合 + let all_results: Vec<_> = all_processing_params + .iter() + .filter_map(|(disc, config)| { + let data = &instruction.data[disc.len()..]; + self.parse_instruction_event( + config, + data, + &account_pubkeys, + signature, + slot, + block_time, + recv_us, + outer_index, + inner_index, + transaction_index, + ) + .map(|event| ((*disc).clone(), (*config).clone(), event)) + }) + .collect(); + + for (_disc, config, mut event) in all_results { + // 阻塞处理:原有的同步逻辑 + let mut inner_instruction_event: Option> = None; + if inner_instructions.is_some() { + let inner_instructions_ref = inner_instructions.unwrap(); + + // 并行执行两个任务 + let (inner_event_result, swap_data_result) = std::thread::scope(|s| { + let inner_event_handle = s.spawn(|| { + for inner_instruction in inner_instructions_ref.instructions.iter() { + let result = self.parse_events_from_inner_instruction( + &inner_instruction.instruction, + signature, + slot, + block_time, + recv_us, + outer_index, + inner_index, + transaction_index, + &config, + ); + if result.len() > 0 { + return Some(result[0].clone()); + } + } + None + }); + + let swap_data_handle = s.spawn(|| { + if !event.swap_data_is_parsed() { + parse_swap_data_from_next_instructions( + &*event, + inner_instructions_ref, + inner_index.unwrap_or(-1_i64) as i8, + &accounts, + ) + } else { + None + } + }); + + // 等待两个任务完成 + (inner_event_handle.join().unwrap(), swap_data_handle.join().unwrap()) + }); + + inner_instruction_event = inner_event_result; + if let Some(swap_data) = swap_data_result { + event.set_swap_data(swap_data); + } + } + + // Skip events that require inner instruction data but don't have it + if config.requires_inner_instruction && inner_instruction_event.is_none() { + continue; + } + + // 合并事件 + if let Some(inner_instruction_event) = inner_instruction_event { + event.merge(&*inner_instruction_event); + } + // 设置处理时间(使用高性能时钟) + event.set_handle_us(elapsed_micros_since(recv_us)); + event = process_event(event, bot_wallet); + callback(&event); + } + Ok(()) + } + + /// 从指令中解析事件 + /// TODO: - wait refactor + #[allow(clippy::too_many_arguments)] + fn parse_events_from_grpc_instruction( + &self, + instruction: &yellowstone_grpc_proto::prelude::CompiledInstruction, + accounts: &[Pubkey], + signature: Signature, + slot: u64, + block_time: Option, + recv_us: i64, + outer_index: i64, + inner_index: Option, + bot_wallet: Option, + transaction_index: Option, + inner_instructions: Option<&yellowstone_grpc_proto::prelude::InnerInstructions>, + callback: Arc Fn(&'a Box) + Send + Sync>, + ) -> anyhow::Result<()> { + let program_id = accounts[instruction.program_id_index as usize]; + if !self.should_handle(&program_id) { + return Ok(()); + } + // 一维化并行处理:将所有 (discriminator, config) 组合展开并行处理 + let all_processing_params: Vec<_> = self + .instruction_configs + .iter() + .filter(|(disc, _)| { + // Use SIMD-optimized data validation and discriminator matching + SimdUtils::validate_instruction_data_simd(&instruction.data, disc.len(), disc.len()) + && SimdUtils::fast_discriminator_match(&instruction.data, disc) + }) + .flat_map(|(disc, configs)| { + configs + .iter() + .filter(|config| config.program_id == program_id) + .map(move |config| (disc, config)) + }) + .collect(); + + // Use SIMD-optimized account indices validation (只需检查一次) + if !SimdUtils::validate_account_indices_simd(&instruction.accounts, accounts.len()) { + return Ok(()); + } + + // 使用缓存构建账户公钥列表,避免重复分配 (只需构建一次) + let account_pubkeys = { + let mut cache_guard = self.account_cache.lock(); + cache_guard.build_account_pubkeys(&instruction.accounts, accounts).to_vec() + }; + + // 并行处理所有 (discriminator, config) 组合 + let all_results: Vec<_> = all_processing_params + .iter() + .filter_map(|(disc, config)| { + let data = &instruction.data[disc.len()..]; + self.parse_instruction_event( + config, + data, + &account_pubkeys, + signature, + slot, + block_time, + recv_us, + outer_index, + inner_index, + transaction_index, + ) + .map(|event| ((*disc).clone(), (*config).clone(), event)) + }) + .collect(); + + for (_disc, config, mut event) in all_results { + // 阻塞处理:原有的同步逻辑 + let mut inner_instruction_event: Option> = None; + if inner_instructions.is_some() { + let inner_instructions_ref = inner_instructions.unwrap(); + + // 并行执行两个任务 + let (inner_event_result, swap_data_result) = std::thread::scope(|s| { + let inner_event_handle = s.spawn(|| { + for inner_instruction in inner_instructions_ref.instructions.iter() { + let result = self.parse_events_from_grpc_inner_instruction( + &inner_instruction, + signature, + slot, + block_time, + recv_us, + outer_index, + inner_index, + transaction_index, + &config, + ); + if result.len() > 0 { + return Some(result[0].clone()); + } + } + None + }); + + let swap_data_handle = s.spawn(|| { + if !event.swap_data_is_parsed() { + parse_swap_data_from_next_grpc_instructions( + &*event, + inner_instructions_ref, + inner_index.unwrap_or(-1_i64) as i8, + &accounts, + ) + } else { + None + } + }); + + // 等待两个任务完成 + (inner_event_handle.join().unwrap(), swap_data_handle.join().unwrap()) + }); + + inner_instruction_event = inner_event_result; + if let Some(swap_data) = swap_data_result { + event.set_swap_data(swap_data); + } + } + + // Skip events that require inner instruction data but don't have it + if config.requires_inner_instruction && inner_instruction_event.is_none() { + continue; + } + + // 合并事件 + if let Some(inner_instruction_event) = inner_instruction_event { + event.merge(&*inner_instruction_event); + } + // 设置处理时间(使用高性能时钟) + event.set_handle_us(elapsed_micros_since(recv_us)); + event = process_event(event, bot_wallet); + callback(&event); + } + Ok(()) + } + + fn should_handle(&self, program_id: &Pubkey) -> bool { + self.program_ids.contains(program_id) + } + + // fn supported_program_ids(&self) -> Vec { + // self.program_ids.clone() + // } +} + +fn process_event( + mut event: Box, + bot_wallet: Option, +) -> Box { + let signature = *event.signature(); // Copy the signature to avoid borrowing issues + if let Some(token_info) = event.as_any().downcast_ref::() { + add_dev_address(&signature, token_info.user); + if token_info.creator != Pubkey::default() && token_info.creator != token_info.user { + add_dev_address(&signature, token_info.creator); + } + } else if let Some(trade_info) = event.as_any_mut().downcast_mut::() { + if is_dev_address_in_signature(&signature, &trade_info.user) + || is_dev_address_in_signature(&signature, &trade_info.creator) + { + trade_info.is_dev_create_token_trade = true; + } else if Some(trade_info.user) == bot_wallet { + trade_info.is_bot = true; + } else { + trade_info.is_dev_create_token_trade = false; + } + if trade_info.metadata.swap_data.is_some() { + trade_info.metadata.swap_data.as_mut().unwrap().from_amount = + if trade_info.is_buy { trade_info.sol_amount } else { trade_info.token_amount }; + trade_info.metadata.swap_data.as_mut().unwrap().to_amount = + if trade_info.is_buy { trade_info.token_amount } else { trade_info.sol_amount }; + } + } else if let Some(trade_info) = event.as_any_mut().downcast_mut::() { + if trade_info.metadata.swap_data.is_some() { + trade_info.metadata.swap_data.as_mut().unwrap().from_amount = + trade_info.user_quote_amount_in; + trade_info.metadata.swap_data.as_mut().unwrap().to_amount = trade_info.base_amount_out; + } + } else if let Some(trade_info) = event.as_any_mut().downcast_mut::() { + if trade_info.metadata.swap_data.is_some() { + trade_info.metadata.swap_data.as_mut().unwrap().from_amount = trade_info.base_amount_in; + trade_info.metadata.swap_data.as_mut().unwrap().to_amount = + trade_info.user_quote_amount_out; + } + } else if let Some(pool_info) = event.as_any().downcast_ref::() { + add_bonk_dev_address(&signature, pool_info.creator); + } else if let Some(trade_info) = event.as_any_mut().downcast_mut::() { + if is_bonk_dev_address_in_signature(&signature, &trade_info.payer) { + trade_info.is_dev_create_token_trade = true; + } else if Some(trade_info.payer) == bot_wallet { + trade_info.is_bot = true; + } else { + trade_info.is_dev_create_token_trade = false; + } + } + event +} diff --git a/src/streaming/event_parser/core/macros.rs b/src/streaming/event_parser/core/macros.rs deleted file mode 100644 index 0216bb7..0000000 --- a/src/streaming/event_parser/core/macros.rs +++ /dev/null @@ -1,133 +0,0 @@ -/// Macro to generate boilerplate EventParser implementation for protocol parsers -/// -/// This macro eliminates the repetitive code where each parser simply delegates -/// all EventParser trait methods to its inner GenericEventParser. -/// -/// Usage: -/// ```rust -/// impl_event_parser_delegate!(MyEventParser); -/// ``` -/// -/// This will generate the complete EventParser implementation that delegates -/// all methods to `self.inner`. -#[macro_export] -macro_rules! impl_event_parser_delegate { - ($parser_type:ty) => { - #[async_trait::async_trait] - impl $crate::streaming::event_parser::core::traits::EventParser for $parser_type { - fn instruction_configs( - &self, - ) -> std::collections::HashMap< - Vec, - Vec<$crate::streaming::event_parser::core::traits::GenericEventParseConfig>, - > { - self.inner.instruction_configs() - } - - fn parse_events_from_inner_instruction( - &self, - inner_instruction: &solana_sdk::instruction::CompiledInstruction, - signature: solana_sdk::signature::Signature, - slot: u64, - block_time: Option, - recv_us: i64, - outer_index: i64, - inner_index: Option, - transaction_index: Option, - config: &GenericEventParseConfig, - ) -> Vec> { - self.inner.parse_events_from_inner_instruction( - inner_instruction, - signature, - slot, - block_time, - recv_us, - outer_index, - inner_index, - transaction_index, - config, - ) - } - - fn parse_events_from_grpc_inner_instruction( - &self, - inner_instruction: &yellowstone_grpc_proto::prelude::InnerInstruction, - signature: solana_sdk::signature::Signature, - slot: u64, - block_time: Option, - recv_us: i64, - outer_index: i64, - inner_index: Option, - transaction_index: Option, - config: &GenericEventParseConfig, - ) -> Vec> { - self.inner.parse_events_from_grpc_inner_instruction(inner_instruction, signature, slot, block_time, recv_us, outer_index, inner_index, transaction_index, config) - } - - fn parse_events_from_instruction( - &self, - instruction: &solana_sdk::instruction::CompiledInstruction, - accounts: &[solana_sdk::pubkey::Pubkey], - signature: solana_sdk::signature::Signature, - slot: u64, - block_time: Option, - recv_us: i64, - outer_index: i64, - inner_index: Option, - bot_wallet: Option, - transaction_index: Option, - inner_instructions: Option<&solana_transaction_status::InnerInstructions>, - callback: std::sync::Arc< - dyn for<'a> Fn(&'a Box) - + Send - + Sync, - >, - ) -> anyhow::Result<()> { - self.inner.parse_events_from_instruction( - instruction, - accounts, - signature, - slot, - block_time, - recv_us, - outer_index, - inner_index, - bot_wallet, - transaction_index, - inner_instructions, - callback, - ) - } - - fn parse_events_from_grpc_instruction( - &self, - instruction: &yellowstone_grpc_proto::prelude::CompiledInstruction, - accounts: &[solana_sdk::pubkey::Pubkey], - signature: solana_sdk::signature::Signature, - slot: u64, - block_time: Option, - recv_us: i64, - outer_index: i64, - inner_index: Option, - bot_wallet: Option, - transaction_index: Option, - inner_instructions: Option<&yellowstone_grpc_proto::prelude::InnerInstructions>, - callback: std::sync::Arc< - dyn for<'a> Fn(&'a Box) - + Send - + Sync, - >, - ) -> anyhow::Result<()> { - self.inner.parse_events_from_grpc_instruction(instruction, accounts, signature, slot, block_time, recv_us, outer_index, inner_index, bot_wallet, transaction_index, inner_instructions, callback) - } - - fn should_handle(&self, program_id: &solana_sdk::pubkey::Pubkey) -> bool { - self.inner.should_handle(program_id) - } - - fn supported_program_ids(&self) -> Vec { - self.inner.supported_program_ids() - } - } - }; -} diff --git a/src/streaming/event_parser/core/mod.rs b/src/streaming/event_parser/core/mod.rs index 2e9f50e..39fda22 100755 --- a/src/streaming/event_parser/core/mod.rs +++ b/src/streaming/event_parser/core/mod.rs @@ -1,6 +1,7 @@ -pub mod common_event_parser; -pub mod traits; pub mod account_event_parser; -pub mod macros; +pub mod common_event_parser; pub mod global_state; -pub use traits::{EventParser, UnifiedEvent}; +pub mod traits; +pub use traits::UnifiedEvent; + +pub mod event_parser; diff --git a/src/streaming/event_parser/core/traits.rs b/src/streaming/event_parser/core/traits.rs index 5ef7c95..850e1f3 100755 --- a/src/streaming/event_parser/core/traits.rs +++ b/src/streaming/event_parser/core/traits.rs @@ -1,229 +1,7 @@ -use prost_types::Timestamp; -use solana_sdk::bs58; +use crate::streaming::event_parser::common::EventType; +use crate::streaming::event_parser::common::SwapData; use solana_sdk::signature::Signature; -use solana_sdk::{ - instruction::CompiledInstruction, pubkey::Pubkey, transaction::VersionedTransaction, -}; -use solana_transaction_status::{ - EncodedConfirmedTransactionWithStatusMeta, InnerInstruction, InnerInstructions, - TransactionWithStatusMeta, UiInstruction, -}; -use std::borrow::Cow; -use std::collections::HashMap; use std::fmt::Debug; -use std::sync::Arc; -use std::time::Instant; -use yellowstone_grpc_proto::geyser::SubscribeUpdateTransactionInfo; - -use super::global_state::{ - add_bonk_dev_address, add_dev_address, - is_bonk_dev_address_in_signature, is_dev_address_in_signature, -}; - -use crate::streaming::common::simd_utils::SimdUtils; -use crate::streaming::event_parser::common::{ - parse_swap_data_from_next_grpc_instructions, parse_swap_data_from_next_instructions, SwapData, -}; -use crate::streaming::event_parser::protocols::pumpswap::{PumpSwapBuyEvent, PumpSwapSellEvent}; -use crate::streaming::event_parser::{ - common::{EventMetadata, EventType, ProtocolType}, - protocols::{ - bonk::{BonkPoolCreateEvent, BonkTradeEvent}, - pumpfun::{PumpFunCreateTokenEvent, PumpFunTradeEvent}, - }, -}; - -/// 高性能时钟管理器,减少系统调用开销并最小化延迟 -#[derive(Debug)] -pub struct HighPerformanceClock { - /// 基准时间点(程序启动时的单调时钟时间) - base_instant: Instant, - /// 基准时间点对应的UTC时间戳(微秒) - base_timestamp_us: i64, - /// 上次校准时间(用于检测是否需要重新校准) - last_calibration: Instant, - /// 校准间隔(秒) - calibration_interval_secs: u64, -} - -impl HighPerformanceClock { - /// 创建新的高性能时钟 - pub fn new() -> Self { - Self::new_with_calibration_interval(300) // 默认5分钟校准一次 - } - - /// 创建带自定义校准间隔的高性能时钟 - pub fn new_with_calibration_interval(calibration_interval_secs: u64) -> Self { - // 通过多次采样来减少初始化误差 - let mut best_offset = i64::MAX; - let mut best_instant = Instant::now(); - let mut best_timestamp = chrono::Utc::now().timestamp_micros(); - - // 进行3次采样,选择延迟最小的 - for _ in 0..3 { - let instant_before = Instant::now(); - let timestamp = chrono::Utc::now().timestamp_micros(); - let instant_after = Instant::now(); - - let sample_latency = instant_after.duration_since(instant_before).as_nanos() as i64; - - if sample_latency < best_offset { - best_offset = sample_latency; - best_instant = instant_before; - best_timestamp = timestamp; - } - } - - Self { - base_instant: best_instant, - base_timestamp_us: best_timestamp, - last_calibration: best_instant, - calibration_interval_secs, - } - } - - /// 获取当前时间戳(微秒),使用单调时钟计算,避免系统调用 - #[inline(always)] - pub fn now_micros(&self) -> i64 { - let elapsed = self.base_instant.elapsed(); - self.base_timestamp_us + elapsed.as_micros() as i64 - } - - /// 获取高精度当前时间戳(微秒),在必要时进行校准 - pub fn now_micros_with_calibration(&mut self) -> i64 { - // 检查是否需要重新校准 - if self.last_calibration.elapsed().as_secs() >= self.calibration_interval_secs { - self.recalibrate(); - } - self.now_micros() - } - - /// 重新校准时钟,减少累积漂移 - fn recalibrate(&mut self) { - let current_monotonic = Instant::now(); - let current_utc = chrono::Utc::now().timestamp_micros(); - - // 计算预期的UTC时间戳(基于单调时钟) - let expected_utc = self.base_timestamp_us - + current_monotonic.duration_since(self.base_instant).as_micros() as i64; - - // 计算漂移量 - let drift_us = current_utc - expected_utc; - - // 如果漂移超过1毫秒,进行校准 - if drift_us.abs() > 1000 { - self.base_instant = current_monotonic; - self.base_timestamp_us = current_utc; - } - - self.last_calibration = current_monotonic; - } - - /// 计算从指定时间戳到现在的消耗时间(微秒) - #[inline(always)] - pub fn elapsed_micros_since(&self, start_timestamp_us: i64) -> i64 { - self.now_micros() - start_timestamp_us - } - - /// 获取高精度纳秒时间戳 - #[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 - } - - /// 重置时钟(强制重新初始化) - pub fn reset(&mut self) { - *self = Self::new_with_calibration_interval(self.calibration_interval_secs); - } -} - -impl Default for HighPerformanceClock { - fn default() -> Self { - Self::new() - } -} - -/// 全局高性能时钟实例 -static HIGH_PERF_CLOCK: once_cell::sync::OnceCell = - once_cell::sync::OnceCell::new(); - -/// 获取全局高性能时钟实例(最简单的实现) -#[inline(always)] -pub fn get_high_perf_clock() -> i64 { - let clock = HIGH_PERF_CLOCK.get_or_init(HighPerformanceClock::new); - clock.now_micros() -} - -/// 计算从指定时间戳到现在的消耗时间(微秒) -#[inline(always)] -pub fn elapsed_micros_since(start_timestamp_us: i64) -> i64 { - get_high_perf_clock() - start_timestamp_us -} - -/// 轻量级事件包装器,避免频繁的Box分配 -#[derive(Debug)] -pub struct EventWrapper { - pub event: T, -} - -impl EventWrapper { - #[inline] - pub fn new(event: T) -> Self { - Self { event } - } - - #[inline] - pub fn into_boxed(self) -> Box { - Box::new(self.event) - } -} - -/// 高性能账户公钥缓存,避免重复Vec分配 -#[derive(Debug)] -pub struct AccountPubkeyCache { - /// 预分配的账户公钥向量,避免每次重新分配 - cache: Vec, -} - -impl AccountPubkeyCache { - /// 创建新的账户公钥缓存 - pub fn new() -> Self { - Self { - cache: Vec::with_capacity(32), // 预分配32个位置,覆盖大多数交易 - } - } - - /// 从指令账户索引构建账户公钥向量,重用缓存内存 - #[inline] - pub fn build_account_pubkeys( - &mut self, - instruction_accounts: &[u8], - all_accounts: &[Pubkey], - ) -> &[Pubkey] { - self.cache.clear(); - - // 确保容量足够,避免动态扩容 - if self.cache.capacity() < instruction_accounts.len() { - self.cache.reserve(instruction_accounts.len() - self.cache.capacity()); - } - - // 快速填充账户公钥 - for &idx in instruction_accounts.iter() { - if (idx as usize) < all_accounts.len() { - self.cache.push(all_accounts[idx as usize]); - } - } - - &self.cache - } -} - -impl Default for AccountPubkeyCache { - fn default() -> Self { - Self::new() - } -} /// Unified Event Interface - All protocol events must implement this trait pub trait UnifiedEvent: Debug + Send + Sync { @@ -273,1165 +51,9 @@ pub trait UnifiedEvent: Debug + Send + Sync { fn transaction_index(&self) -> Option; } -/// 事件解析器trait - 定义了事件解析的核心方法 -#[async_trait::async_trait] -pub trait EventParser: Send + Sync { - /// 获取指令解析配置 - fn instruction_configs(&self) -> HashMap, Vec>; - /// 从内联指令中解析事件数据 - #[allow(clippy::too_many_arguments)] - fn parse_events_from_inner_instruction( - &self, - inner_instruction: &CompiledInstruction, - signature: Signature, - slot: u64, - block_time: Option, - recv_us: i64, - outer_index: i64, - inner_index: Option, - transaction_index: Option, - config: &GenericEventParseConfig, - ) -> Vec>; - - /// 从内联指令中解析事件数据 - #[allow(clippy::too_many_arguments)] - fn parse_events_from_grpc_inner_instruction( - &self, - inner_instruction: &yellowstone_grpc_proto::prelude::InnerInstruction, - signature: Signature, - slot: u64, - block_time: Option, - recv_us: i64, - outer_index: i64, - inner_index: Option, - transaction_index: Option, - config: &GenericEventParseConfig, - ) -> Vec>; - - /// 从指令中解析事件数据 - #[allow(clippy::too_many_arguments)] - fn parse_events_from_instruction( - &self, - instruction: &CompiledInstruction, - accounts: &[Pubkey], - signature: Signature, - slot: u64, - block_time: Option, - recv_us: i64, - outer_index: i64, - inner_index: Option, - bot_wallet: Option, - transaction_index: Option, - inner_instructions: Option<&InnerInstructions>, - callback: Arc Fn(&'a Box) + Send + Sync>, - ) -> anyhow::Result<()>; - - /// 从指令中解析事件数据 - /// TODO: - wait refactor - #[allow(clippy::too_many_arguments)] - fn parse_events_from_grpc_instruction( - &self, - instruction: &yellowstone_grpc_proto::prelude::CompiledInstruction, - accounts: &[Pubkey], - signature: Signature, - slot: u64, - block_time: Option, - recv_us: i64, - outer_index: i64, - inner_index: Option, - bot_wallet: Option, - transaction_index: Option, - inner_instructions: Option<&yellowstone_grpc_proto::prelude::InnerInstructions>, - callback: Arc Fn(&'a Box) + Send + Sync>, - ) -> anyhow::Result<()>; - - #[allow(clippy::too_many_arguments)] - async fn parse_instruction_events_from_grpc_transaction( - &self, - compiled_instructions: &[yellowstone_grpc_proto::prelude::CompiledInstruction], - signature: Signature, - slot: Option, - block_time: Option, - recv_us: i64, - accounts: &[Pubkey], - inner_instructions: &[yellowstone_grpc_proto::prelude::InnerInstructions], - bot_wallet: Option, - transaction_index: Option, - callback: Arc Fn(&'a Box) + Send + Sync>, - ) -> anyhow::Result<()> { - // 获取交易的指令和账户 - let mut accounts = accounts.to_vec(); - // 检查交易中是否包含程序 - let has_program = accounts.iter().any(|account| self.should_handle(account)); - if has_program { - // 解析每个指令 - for (index, instruction) in compiled_instructions.iter().enumerate() { - if let Some(program_id) = accounts.get(instruction.program_id_index as usize) { - if self.should_handle(program_id) { - let max_idx = instruction.accounts.iter().max().unwrap_or(&0); - // 补齐accounts(使用Pubkey::default()) - if *max_idx as usize > accounts.len() { - for _i in accounts.len()..*max_idx as usize { - accounts.push(Pubkey::default()); - } - } - let inner_instructions = inner_instructions - .iter() - .find(|inner_instruction| inner_instruction.index == index as u32); - self.parse_grpc_instruction( - instruction, - &accounts, - signature, - slot, - block_time, - recv_us, - index as i64, - None, - bot_wallet, - transaction_index, - inner_instructions, - Arc::clone(&callback), - ) - .await?; - - // Immediately process inner instructions for correct ordering - if let Some(inner_instructions) = inner_instructions { - for (inner_index, inner_instruction) in inner_instructions.instructions.iter().enumerate() { - let inner_accounts = &inner_instruction.accounts; - let data = &inner_instruction.data; - let instruction = yellowstone_grpc_proto::prelude::CompiledInstruction { - program_id_index: inner_instruction.program_id_index, - accounts: inner_accounts.to_vec(), - data: data.to_vec(), - }; - self.parse_grpc_instruction( - &instruction, - &accounts, - signature, - slot, - block_time, - recv_us, - inner_instructions.index as i64, - Some(inner_index as i64), - bot_wallet, - transaction_index, - Some(&inner_instructions), - Arc::clone(&callback), - ) - .await?; - } - } - } - } - } - } - Ok(()) - } - - /// 从VersionedTransaction中解析指令事件的通用方法 - #[allow(clippy::too_many_arguments)] - async fn parse_instruction_events_from_versioned_transaction( - &self, - transaction: &VersionedTransaction, - signature: Signature, - slot: Option, - block_time: Option, - recv_us: i64, - accounts: &[Pubkey], - inner_instructions: &[InnerInstructions], - bot_wallet: Option, - transaction_index: Option, - callback: Arc Fn(&'a Box) + Send + Sync>, - ) -> anyhow::Result<()> { - // 获取交易的指令和账户 - let compiled_instructions = transaction.message.instructions(); - let mut accounts: Vec = accounts.to_vec(); - // 检查交易中是否包含程序 - let has_program = accounts.iter().any(|account| self.should_handle(account)); - if has_program { - // 解析每个指令 - for (index, instruction) in compiled_instructions.iter().enumerate() { - if let Some(program_id) = accounts.get(instruction.program_id_index as usize) { - if self.should_handle(program_id) { - let max_idx = instruction.accounts.iter().max().unwrap_or(&0); - // 补齐accounts(使用Pubkey::default()) - if *max_idx as usize > accounts.len() { - for _i in accounts.len()..*max_idx as usize { - accounts.push(Pubkey::default()); - } - } - let inner_instructions = inner_instructions - .iter() - .find(|inner_instruction| inner_instruction.index == index as u8); - self.parse_instruction( - instruction, - &accounts, - signature, - slot, - block_time, - recv_us, - index as i64, - None, - bot_wallet, - transaction_index, - inner_instructions, - Arc::clone(&callback), - ) - .await?; - - // Immediately process inner instructions for correct ordering - if let Some(inner_instructions) = inner_instructions { - for (inner_index, inner_instruction) in inner_instructions.instructions.iter().enumerate() { - self.parse_instruction( - &inner_instruction.instruction, - &accounts, - signature, - slot, - block_time, - recv_us, - index as i64, - Some(inner_index as i64), - bot_wallet, - transaction_index, - Some(&inner_instructions), - Arc::clone(&callback), - ) - .await?; - } - } - } - } - } - } - Ok(()) - } - - async fn parse_versioned_transaction_owned( - &self, - versioned_tx: VersionedTransaction, - signature: Signature, - slot: Option, - block_time: Option, - recv_us: i64, - bot_wallet: Option, - transaction_index: Option, - inner_instructions: &[InnerInstructions], - callback: Arc) + Send + Sync>, - ) -> anyhow::Result<()> { - // 创建适配器回调,将所有权回调转换为引用回调 - let adapter_callback = Arc::new(move |event: &Box| { - callback(event.clone_boxed()); - }); - self.parse_versioned_transaction( - &versioned_tx, - signature, - slot, - block_time, - recv_us, - bot_wallet, - transaction_index, - inner_instructions, - adapter_callback, - ) - .await?; - Ok(()) - } - - async fn parse_versioned_transaction( - &self, - versioned_tx: &VersionedTransaction, - signature: Signature, - slot: Option, - block_time: Option, - recv_us: i64, - bot_wallet: Option, - transaction_index: Option, - inner_instructions: &[InnerInstructions], - callback: Arc Fn(&'a Box) + Send + Sync>, - ) -> anyhow::Result<()> { - let accounts: Vec = versioned_tx.message.static_account_keys().to_vec(); - self.parse_instruction_events_from_versioned_transaction( - versioned_tx, - signature, - slot, - block_time, - recv_us, - &accounts, - inner_instructions, - bot_wallet, - transaction_index, - callback, - ) - .await?; - Ok(()) - } - - async fn parse_grpc_transaction_owned( - &self, - grpc_tx: SubscribeUpdateTransactionInfo, - signature: Signature, - slot: Option, - block_time: Option, - recv_us: i64, - bot_wallet: Option, - transaction_index: Option, - callback: Arc) + Send + Sync>, - ) -> anyhow::Result<()> { - // 创建适配器回调,将所有权回调转换为引用回调 - let adapter_callback = Arc::new(move |event: &Box| { - callback(event.clone_boxed()); - }); - // 调用原始方法 - self.parse_grpc_transaction( - grpc_tx, - signature, - slot, - block_time, - recv_us, - bot_wallet, - transaction_index, - adapter_callback, - ) - .await - } - - async fn parse_grpc_transaction( - &self, - grpc_tx: SubscribeUpdateTransactionInfo, - signature: Signature, - slot: Option, - block_time: Option, - recv_us: i64, - bot_wallet: Option, - transaction_index: Option, - callback: Arc Fn(&'a Box) + Send + Sync>, - ) -> anyhow::Result<()> { - if let Some(transition) = grpc_tx.transaction { - if let Some(message) = &transition.message { - let mut address_table_lookups: Vec> = vec![]; - let mut inner_instructions: Vec< - yellowstone_grpc_proto::solana::storage::confirmed_block::InnerInstructions, - > = vec![]; - - if let Some(meta) = grpc_tx.meta { - inner_instructions = meta.inner_instructions; - address_table_lookups.reserve( - meta.loaded_writable_addresses.len() + meta.loaded_writable_addresses.len(), - ); - let loaded_writable_addresses = meta.loaded_writable_addresses; - let loaded_readonly_addresses = meta.loaded_readonly_addresses; - address_table_lookups.extend( - loaded_writable_addresses.into_iter().chain(loaded_readonly_addresses), - ); - } - - let mut accounts_bytes: Vec> = - 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 - let accounts: Vec = accounts_bytes - .iter() - .filter_map(|account| { - if account.len() == 32 { - Some(Pubkey::try_from(account.as_slice()).unwrap_or_default()) - } else { - None - } - }) - .collect(); - // 使用 Arc 包装共享数据,避免不必要的克隆 - let accounts_arc = Arc::new(accounts); - let inner_instructions_arc = Arc::new(inner_instructions); - // 解析指令事件 - let instructions = &message.instructions; - self.parse_instruction_events_from_grpc_transaction( - &instructions, - signature, - slot, - block_time, - recv_us, - &accounts_arc, - &inner_instructions_arc, - bot_wallet, - transaction_index, - callback.clone(), - ) - .await?; - } - } - - Ok(()) - } - - async fn parse_encoded_confirmed_transaction_with_status_meta( - &self, - signature: Signature, - transaction: EncodedConfirmedTransactionWithStatusMeta, - callback: Arc Fn(&'a Box) + Send + Sync>, - ) -> anyhow::Result<()> { - let versioned_tx = match transaction.transaction.transaction.decode() { - Some(tx) => tx, - None => { - return Ok(()); - } - }; - let mut inner_instructions_vec: Vec = Vec::new(); - if let Some(meta) = &transaction.transaction.meta { - // 从meta中获取inner_instructions,处理OptionSerializer类型 - if let solana_transaction_status::option_serializer::OptionSerializer::Some( - ui_inner_insts, - ) = &meta.inner_instructions - { - // 将UiInnerInstructions转换为InnerInstructions - for ui_inner in ui_inner_insts { - let mut converted_instructions = Vec::new(); - - // 转换每个UiInstruction为InnerInstruction - for ui_instruction in &ui_inner.instructions { - if let UiInstruction::Compiled(ui_compiled) = ui_instruction { - // 解码base58编码的data - if let Ok(data) = bs58::decode(&ui_compiled.data).into_vec() { - // base64解码 - let compiled_instruction = CompiledInstruction { - program_id_index: ui_compiled.program_id_index, - accounts: ui_compiled.accounts.clone(), - data, - }; - - let inner_instruction = InnerInstruction { - instruction: compiled_instruction, - stack_height: ui_compiled.stack_height, - }; - - converted_instructions.push(inner_instruction); - } - } - } - - let inner_instructions = InnerInstructions { - index: ui_inner.index, - instructions: converted_instructions, - }; - - inner_instructions_vec.push(inner_instructions); - } - } - } - let inner_instructions: &[InnerInstructions] = &inner_instructions_vec; - - let meta = transaction.transaction.meta; - let mut address_table_lookups: Vec = vec![]; - if let Some(meta) = meta { - if let solana_transaction_status::option_serializer::OptionSerializer::Some( - loaded_addresses, - ) = &meta.loaded_addresses - { - address_table_lookups - .reserve(loaded_addresses.writable.len() + loaded_addresses.readonly.len()); - address_table_lookups.extend( - loaded_addresses - .writable - .iter() - .filter_map(|s| s.parse::().ok()) - .chain( - loaded_addresses - .readonly - .iter() - .filter_map(|s| s.parse::().ok()), - ), - ); - } - } - let mut accounts = Vec::with_capacity( - versioned_tx.message.static_account_keys().len() + address_table_lookups.len(), - ); - accounts.extend_from_slice(versioned_tx.message.static_account_keys()); - accounts.extend(address_table_lookups); - // 使用 Arc 包装共享数据,避免不必要的克隆 - let accounts_arc = Arc::new(accounts); - let inner_instructions_arc = Arc::new(inner_instructions); - - let slot = transaction.slot; - let block_time = transaction.block_time.map(|t| Timestamp { seconds: t as i64, nanos: 0 }); - let recv_us = get_high_perf_clock(); - let bot_wallet = None; - let transaction_index = None; - // 解析指令事件 - self.parse_instruction_events_from_versioned_transaction( - &versioned_tx, - signature, - Some(slot), - block_time, - recv_us, - &accounts_arc, - &inner_instructions_arc, - bot_wallet, - transaction_index, - callback.clone(), - ) - .await?; - - Ok(()) - } - - async fn parse_inner_instruction( - &self, - instruction: &CompiledInstruction, - signature: Signature, - slot: Option, - block_time: Option, - recv_us: i64, - outer_index: i64, - inner_index: Option, - transaction_index: Option, - config: &GenericEventParseConfig, - ) -> anyhow::Result>> { - let slot = slot.unwrap_or(0); - let events = self.parse_events_from_inner_instruction( - instruction, - signature, - slot, - block_time, - recv_us, - outer_index, - inner_index, - transaction_index, - config, - ); - Ok(events) - } - - #[allow(clippy::too_many_arguments)] - async fn parse_grpc_instruction( - &self, - instruction: &yellowstone_grpc_proto::prelude::CompiledInstruction, - accounts: &[Pubkey], - signature: Signature, - slot: Option, - block_time: Option, - recv_us: i64, - outer_index: i64, - inner_index: Option, - bot_wallet: Option, - transaction_index: Option, - inner_instructions: Option<&yellowstone_grpc_proto::prelude::InnerInstructions>, - callback: Arc Fn(&'a Box) + Send + Sync>, - ) -> anyhow::Result<()> { - let slot = slot.unwrap_or(0); - self.parse_events_from_grpc_instruction( - instruction, - accounts, - signature, - slot, - block_time, - recv_us, - outer_index, - inner_index, - bot_wallet, - transaction_index, - inner_instructions, - callback, - ) - } - - #[allow(clippy::too_many_arguments)] - async fn parse_instruction( - &self, - instruction: &CompiledInstruction, - accounts: &[Pubkey], - signature: Signature, - slot: Option, - block_time: Option, - recv_us: i64, - outer_index: i64, - inner_index: Option, - bot_wallet: Option, - transaction_index: Option, - inner_instructions: Option<&InnerInstructions>, - callback: Arc Fn(&'a Box) + Send + Sync>, - ) -> anyhow::Result<()> { - let slot = slot.unwrap_or(0); - self.parse_events_from_instruction( - instruction, - accounts, - signature, - slot, - block_time, - recv_us, - outer_index, - inner_index, - bot_wallet, - transaction_index, - inner_instructions, - callback, - ) - } - - /// 检查是否应该处理此程序ID - fn should_handle(&self, program_id: &Pubkey) -> bool; - - /// 获取支持的程序ID列表 - fn supported_program_ids(&self) -> Vec; -} - // 为Box实现Clone impl Clone for Box { fn clone(&self) -> Self { self.clone_boxed() } } - -/// 通用事件解析器配置 -#[derive(Debug, Clone)] -pub struct GenericEventParseConfig { - pub program_id: Pubkey, - pub protocol_type: ProtocolType, - pub inner_instruction_discriminator: &'static [u8], - pub instruction_discriminator: &'static [u8], - pub event_type: EventType, - pub inner_instruction_parser: Option, - pub instruction_parser: Option, - pub requires_inner_instruction: bool, -} - -/// 内联指令事件解析器 -pub type InnerInstructionEventParser = - fn(data: &[u8], metadata: EventMetadata) -> Option>; - -/// 指令事件解析器 -pub type InstructionEventParser = - fn(data: &[u8], accounts: &[Pubkey], metadata: EventMetadata) -> Option>; - -/// 通用事件解析器基类 -pub struct GenericEventParser { - pub program_ids: Vec, - // pub inner_instruction_configs: HashMap, Vec>, - pub instruction_configs: HashMap, Vec>, - /// 账户公钥缓存,避免重复分配 - pub account_cache: parking_lot::Mutex, -} - -impl GenericEventParser { - /// 创建新的通用事件解析器 - pub fn new(program_ids: Vec, configs: Vec) -> Self { - // 预分配容量,避免动态扩容 - let mut instruction_configs = HashMap::with_capacity(configs.len()); - - for config in configs { - instruction_configs - .entry(config.instruction_discriminator.to_vec()) - .or_insert_with(Vec::new) - .push(config.clone()); - } - - // 初始化账户缓存 - let account_cache = parking_lot::Mutex::new(AccountPubkeyCache::new()); - - Self { program_ids, instruction_configs, account_cache } - } - - /// 通用的内联指令解析方法 - #[allow(clippy::too_many_arguments)] - fn parse_inner_instruction_event( - &self, - config: &GenericEventParseConfig, - data: &[u8], - signature: Signature, - slot: u64, - block_time: Option, - recv_us: i64, - outer_index: i64, - inner_index: Option, - transaction_index: Option, - ) -> Option> { - if let Some(parser) = config.inner_instruction_parser { - 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( - signature, - slot, - timestamp.seconds, - block_time_ms, - config.protocol_type.clone(), - config.event_type.clone(), - config.program_id, - outer_index, - inner_index, - recv_us, - transaction_index, - ); - parser(data, metadata) - } else { - None - } - } - - /// 通用的指令解析方法 - #[allow(clippy::too_many_arguments)] - fn parse_instruction_event( - &self, - config: &GenericEventParseConfig, - data: &[u8], - account_pubkeys: &[Pubkey], - signature: Signature, - slot: u64, - block_time: Option, - recv_us: i64, - outer_index: i64, - inner_index: Option, - transaction_index: Option, - ) -> Option> { - if let Some(parser) = config.instruction_parser { - 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( - signature, - slot, - timestamp.seconds, - block_time_ms, - config.protocol_type.clone(), - config.event_type.clone(), - config.program_id, - outer_index, - inner_index, - recv_us, - transaction_index, - ); - parser(data, account_pubkeys, metadata) - } else { - None - } - } -} - -#[async_trait::async_trait] -impl EventParser for GenericEventParser { - fn instruction_configs(&self) -> HashMap, Vec> { - self.instruction_configs.clone() - } - /// 从内联指令中解析事件数据 - #[allow(clippy::too_many_arguments)] - fn parse_events_from_inner_instruction( - &self, - inner_instruction: &CompiledInstruction, - signature: Signature, - slot: u64, - block_time: Option, - recv_us: i64, - outer_index: i64, - inner_index: Option, - transaction_index: Option, - config: &GenericEventParseConfig, - ) -> Vec> { - // Use SIMD-optimized data validation with correct discriminator length - let discriminator_len = config.inner_instruction_discriminator.len(); - if !SimdUtils::validate_instruction_data_simd(&inner_instruction.data, 16, discriminator_len) { - return Vec::new(); - } - - // Use SIMD-optimized discriminator matching - if !SimdUtils::fast_discriminator_match(&inner_instruction.data, config.inner_instruction_discriminator) { - return Vec::new(); - } - - let data = &inner_instruction.data[16..]; - let mut events = Vec::new(); - if let Some(event) = self.parse_inner_instruction_event( - config, - data, - signature, - slot, - block_time, - recv_us, - outer_index, - inner_index, - transaction_index, - ) { - events.push(event); - } - events - } - - /// 从内联指令中解析事件数据 - #[allow(clippy::too_many_arguments)] - fn parse_events_from_grpc_inner_instruction( - &self, - inner_instruction: &yellowstone_grpc_proto::prelude::InnerInstruction, - signature: Signature, - slot: u64, - block_time: Option, - recv_us: i64, - outer_index: i64, - inner_index: Option, - transaction_index: Option, - config: &GenericEventParseConfig, - ) -> Vec> { - // Use SIMD-optimized data validation with correct discriminator length - let discriminator_len = config.inner_instruction_discriminator.len(); - if !SimdUtils::validate_instruction_data_simd(&inner_instruction.data, 16, discriminator_len) { - return Vec::new(); - } - - // Use SIMD-optimized discriminator matching - if !SimdUtils::fast_discriminator_match(&inner_instruction.data, config.inner_instruction_discriminator) { - return Vec::new(); - } - - let data = &inner_instruction.data[16..]; - let mut events = Vec::new(); - if let Some(event) = self.parse_inner_instruction_event( - config, - data, - signature, - slot, - block_time, - recv_us, - outer_index, - inner_index, - transaction_index, - ) { - events.push(event); - } - events - } - - /// 从指令中解析事件 - #[allow(clippy::too_many_arguments)] - fn parse_events_from_instruction( - &self, - instruction: &CompiledInstruction, - accounts: &[Pubkey], - signature: Signature, - slot: u64, - block_time: Option, - recv_us: i64, - outer_index: i64, - inner_index: Option, - bot_wallet: Option, - transaction_index: Option, - inner_instructions: Option<&InnerInstructions>, - callback: Arc Fn(&'a Box) + Send + Sync>, - ) -> anyhow::Result<()> { - let program_id = accounts[instruction.program_id_index as usize]; - if !self.should_handle(&program_id) { - return Ok(()); - } - // 一维化并行处理:将所有 (discriminator, config) 组合展开并行处理 - let all_processing_params: Vec<_> = self - .instruction_configs - .iter() - .filter(|(disc, _)| { - // Use SIMD-optimized data validation and discriminator matching - SimdUtils::validate_instruction_data_simd(&instruction.data, disc.len(), disc.len()) - && SimdUtils::fast_discriminator_match(&instruction.data, disc) - }) - .flat_map(|(disc, configs)| { - configs - .iter() - .filter(|config| config.program_id == program_id) - .map(move |config| (disc, config)) - }) - .collect(); - - // Use SIMD-optimized account indices validation (只需检查一次) - if !SimdUtils::validate_account_indices_simd(&instruction.accounts, accounts.len()) { - return Ok(()); - } - - // 使用缓存构建账户公钥列表,避免重复分配 (只需构建一次) - let account_pubkeys = { - let mut cache_guard = self.account_cache.lock(); - cache_guard.build_account_pubkeys(&instruction.accounts, accounts).to_vec() - }; - - // 并行处理所有 (discriminator, config) 组合 - let all_results: Vec<_> = all_processing_params - .iter() - .filter_map(|(disc, config)| { - let data = &instruction.data[disc.len()..]; - self.parse_instruction_event( - config, - data, - &account_pubkeys, - signature, - slot, - block_time, - recv_us, - outer_index, - inner_index, - transaction_index, - ) - .map(|event| ((*disc).clone(), (*config).clone(), event)) - }) - .collect(); - - for (_disc, config, mut event) in all_results { - // 阻塞处理:原有的同步逻辑 - let mut inner_instruction_event: Option> = None; - if inner_instructions.is_some() { - let inner_instructions_ref = inner_instructions.unwrap(); - - // 并行执行两个任务 - let (inner_event_result, swap_data_result) = std::thread::scope(|s| { - let inner_event_handle = s.spawn(|| { - for inner_instruction in inner_instructions_ref.instructions.iter() { - let result = self.parse_events_from_inner_instruction( - &inner_instruction.instruction, - signature, - slot, - block_time, - recv_us, - outer_index, - inner_index, - transaction_index, - &config, - ); - if result.len() > 0 { - return Some(result[0].clone()); - } - } - None - }); - - let swap_data_handle = s.spawn(|| { - if !event.swap_data_is_parsed() { - parse_swap_data_from_next_instructions( - &*event, - inner_instructions_ref, - inner_index.unwrap_or(-1_i64) as i8, - &accounts, - ) - } else { - None - } - }); - - // 等待两个任务完成 - (inner_event_handle.join().unwrap(), swap_data_handle.join().unwrap()) - }); - - inner_instruction_event = inner_event_result; - if let Some(swap_data) = swap_data_result { - event.set_swap_data(swap_data); - } - } - - // Skip events that require inner instruction data but don't have it - if config.requires_inner_instruction && inner_instruction_event.is_none() { - continue; - } - - // 合并事件 - if let Some(inner_instruction_event) = inner_instruction_event { - event.merge(&*inner_instruction_event); - } - // 设置处理时间(使用高性能时钟) - event.set_handle_us(elapsed_micros_since( - recv_us, - )); - event = process_event(event, bot_wallet); - callback(&event); - } - Ok(()) - } - - /// 从指令中解析事件 - /// TODO: - wait refactor - #[allow(clippy::too_many_arguments)] - fn parse_events_from_grpc_instruction( - &self, - instruction: &yellowstone_grpc_proto::prelude::CompiledInstruction, - accounts: &[Pubkey], - signature: Signature, - slot: u64, - block_time: Option, - recv_us: i64, - outer_index: i64, - inner_index: Option, - bot_wallet: Option, - transaction_index: Option, - inner_instructions: Option<&yellowstone_grpc_proto::prelude::InnerInstructions>, - callback: Arc Fn(&'a Box) + Send + Sync>, - ) -> anyhow::Result<()> { - let program_id = accounts[instruction.program_id_index as usize]; - if !self.should_handle(&program_id) { - return Ok(()); - } - // 一维化并行处理:将所有 (discriminator, config) 组合展开并行处理 - let all_processing_params: Vec<_> = self - .instruction_configs - .iter() - .filter(|(disc, _)| { - // Use SIMD-optimized data validation and discriminator matching - SimdUtils::validate_instruction_data_simd(&instruction.data, disc.len(), disc.len()) - && SimdUtils::fast_discriminator_match(&instruction.data, disc) - }) - .flat_map(|(disc, configs)| { - configs - .iter() - .filter(|config| config.program_id == program_id) - .map(move |config| (disc, config)) - }) - .collect(); - - // Use SIMD-optimized account indices validation (只需检查一次) - if !SimdUtils::validate_account_indices_simd(&instruction.accounts, accounts.len()) { - return Ok(()); - } - - // 使用缓存构建账户公钥列表,避免重复分配 (只需构建一次) - let account_pubkeys = { - let mut cache_guard = self.account_cache.lock(); - cache_guard.build_account_pubkeys(&instruction.accounts, accounts).to_vec() - }; - - // 并行处理所有 (discriminator, config) 组合 - let all_results: Vec<_> = all_processing_params - .iter() - .filter_map(|(disc, config)| { - let data = &instruction.data[disc.len()..]; - self.parse_instruction_event( - config, - data, - &account_pubkeys, - signature, - slot, - block_time, - recv_us, - outer_index, - inner_index, - transaction_index, - ) - .map(|event| ((*disc).clone(), (*config).clone(), event)) - }) - .collect(); - - for (_disc, config, mut event) in all_results { - // 阻塞处理:原有的同步逻辑 - let mut inner_instruction_event: Option> = None; - if inner_instructions.is_some() { - let inner_instructions_ref = inner_instructions.unwrap(); - - // 并行执行两个任务 - let (inner_event_result, swap_data_result) = std::thread::scope(|s| { - let inner_event_handle = s.spawn(|| { - for inner_instruction in inner_instructions_ref.instructions.iter() { - let result = self.parse_events_from_grpc_inner_instruction( - &inner_instruction, - signature, - slot, - block_time, - recv_us, - outer_index, - inner_index, - transaction_index, - &config, - ); - if result.len() > 0 { - return Some(result[0].clone()); - } - } - None - }); - - let swap_data_handle = s.spawn(|| { - if !event.swap_data_is_parsed() { - parse_swap_data_from_next_grpc_instructions( - &*event, - inner_instructions_ref, - inner_index.unwrap_or(-1_i64) as i8, - &accounts, - ) - } else { - None - } - }); - - // 等待两个任务完成 - (inner_event_handle.join().unwrap(), swap_data_handle.join().unwrap()) - }); - - inner_instruction_event = inner_event_result; - if let Some(swap_data) = swap_data_result { - event.set_swap_data(swap_data); - } - } - - // Skip events that require inner instruction data but don't have it - if config.requires_inner_instruction && inner_instruction_event.is_none() { - continue; - } - - // 合并事件 - if let Some(inner_instruction_event) = inner_instruction_event { - event.merge(&*inner_instruction_event); - } - // 设置处理时间(使用高性能时钟) - event.set_handle_us(elapsed_micros_since( - recv_us, - )); - event = process_event(event, bot_wallet); - callback(&event); - } - Ok(()) - } - - fn should_handle(&self, program_id: &Pubkey) -> bool { - self.program_ids.contains(program_id) - } - - fn supported_program_ids(&self) -> Vec { - self.program_ids.clone() - } -} - -fn process_event( - mut event: Box, - bot_wallet: Option, -) -> Box { - let signature = *event.signature(); // Copy the signature to avoid borrowing issues - if let Some(token_info) = event.as_any().downcast_ref::() { - add_dev_address(&signature, token_info.user); - if token_info.creator != Pubkey::default() && token_info.creator != token_info.user { - add_dev_address(&signature, token_info.creator); - } - } else if let Some(trade_info) = event.as_any_mut().downcast_mut::() { - if is_dev_address_in_signature(&signature, &trade_info.user) || is_dev_address_in_signature(&signature, &trade_info.creator) { - trade_info.is_dev_create_token_trade = true; - } else if Some(trade_info.user) == bot_wallet { - trade_info.is_bot = true; - } else { - trade_info.is_dev_create_token_trade = false; - } - if trade_info.metadata.swap_data.is_some() { - trade_info.metadata.swap_data.as_mut().unwrap().from_amount = - if trade_info.is_buy { trade_info.sol_amount } else { trade_info.token_amount }; - trade_info.metadata.swap_data.as_mut().unwrap().to_amount = - if trade_info.is_buy { trade_info.token_amount } else { trade_info.sol_amount }; - } - } else if let Some(trade_info) = event.as_any_mut().downcast_mut::() { - if trade_info.metadata.swap_data.is_some() { - trade_info.metadata.swap_data.as_mut().unwrap().from_amount = - trade_info.user_quote_amount_in; - trade_info.metadata.swap_data.as_mut().unwrap().to_amount = trade_info.base_amount_out; - } - } else if let Some(trade_info) = event.as_any_mut().downcast_mut::() { - if trade_info.metadata.swap_data.is_some() { - trade_info.metadata.swap_data.as_mut().unwrap().from_amount = trade_info.base_amount_in; - trade_info.metadata.swap_data.as_mut().unwrap().to_amount = - trade_info.user_quote_amount_out; - } - } else if let Some(pool_info) = event.as_any().downcast_ref::() { - add_bonk_dev_address(&signature, pool_info.creator); - } else if let Some(trade_info) = event.as_any_mut().downcast_mut::() { - if is_bonk_dev_address_in_signature(&signature, &trade_info.payer) { - trade_info.is_dev_create_token_trade = true; - } else if Some(trade_info.payer) == bot_wallet { - trade_info.is_bot = true; - } else { - trade_info.is_dev_create_token_trade = false; - } - } - event -} diff --git a/src/streaming/event_parser/factory.rs b/src/streaming/event_parser/factory.rs deleted file mode 100755 index 48de9c5..0000000 --- a/src/streaming/event_parser/factory.rs +++ /dev/null @@ -1,109 +0,0 @@ -use anyhow::{anyhow, Result}; -use solana_sdk::pubkey::Pubkey; -use std::{collections::HashMap, sync::{Arc, LazyLock}}; - -use crate::streaming::event_parser::protocols::{ - bonk::parser::BONK_PROGRAM_ID, pumpfun::parser::PUMPFUN_PROGRAM_ID, pumpswap::parser::PUMPSWAP_PROGRAM_ID, raydium_amm_v4::parser::RAYDIUM_AMM_V4_PROGRAM_ID, raydium_clmm::parser::RAYDIUM_CLMM_PROGRAM_ID, raydium_cpmm::parser::RAYDIUM_CPMM_PROGRAM_ID, BonkEventParser, RaydiumAmmV4EventParser, RaydiumClmmEventParser, RaydiumCpmmEventParser -}; - -use super::{ - core::traits::EventParser, - protocols::{pumpfun::PumpFunEventParser, pumpswap::PumpSwapEventParser}, -}; - -/// 支持的协议 -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum Protocol { - PumpSwap, - PumpFun, - Bonk, - RaydiumCpmm, - RaydiumClmm, - RaydiumAmmV4, -} - -impl Protocol { - pub fn get_program_id(&self) -> Vec { - match self { - Protocol::PumpSwap => vec![PUMPSWAP_PROGRAM_ID], - Protocol::PumpFun => vec![PUMPFUN_PROGRAM_ID], - Protocol::Bonk => vec![BONK_PROGRAM_ID], - Protocol::RaydiumCpmm => vec![RAYDIUM_CPMM_PROGRAM_ID], - Protocol::RaydiumClmm => vec![RAYDIUM_CLMM_PROGRAM_ID], - Protocol::RaydiumAmmV4 => vec![RAYDIUM_AMM_V4_PROGRAM_ID], - } - } -} - -impl std::fmt::Display for Protocol { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Protocol::PumpSwap => write!(f, "PumpSwap"), - Protocol::PumpFun => write!(f, "PumpFun"), - Protocol::Bonk => write!(f, "Bonk"), - Protocol::RaydiumCpmm => write!(f, "RaydiumCpmm"), - Protocol::RaydiumClmm => write!(f, "RaydiumClmm"), - Protocol::RaydiumAmmV4 => write!(f, "RaydiumAmmV4"), - } - } -} - -impl std::str::FromStr for Protocol { - type Err = anyhow::Error; - - fn from_str(s: &str) -> Result { - match s.to_lowercase().as_str() { - "pumpswap" => Ok(Protocol::PumpSwap), - "pumpfun" => Ok(Protocol::PumpFun), - "bonk" => Ok(Protocol::Bonk), - "raydiumcpmm" => Ok(Protocol::RaydiumCpmm), - "raydiumclmm" => Ok(Protocol::RaydiumClmm), - "raydiumammv4" => Ok(Protocol::RaydiumAmmV4), - _ => Err(anyhow!("Unsupported protocol: {}", s)), - } - } -} - -static EVENT_PARSERS: LazyLock>> = LazyLock::new(|| { - // 预分配容量,避免动态扩容 - let mut parsers: HashMap> = HashMap::with_capacity(6); - parsers.insert(Protocol::PumpSwap, Arc::new(PumpSwapEventParser::new())); - parsers.insert(Protocol::PumpFun, Arc::new(PumpFunEventParser::new())); - parsers.insert(Protocol::Bonk, Arc::new(BonkEventParser::new())); - parsers.insert(Protocol::RaydiumCpmm, Arc::new(RaydiumCpmmEventParser::new())); - parsers.insert(Protocol::RaydiumClmm, Arc::new(RaydiumClmmEventParser::new())); - parsers.insert(Protocol::RaydiumAmmV4, Arc::new(RaydiumAmmV4EventParser::new())); - parsers -}); - - -/// 事件解析器工厂 - 用于创建不同协议的事件解析器 -pub struct EventParserFactory; - -impl EventParserFactory { - - /// 创建指定协议的事件解析器 - pub fn create_parser(protocol: Protocol) -> Arc { - EVENT_PARSERS.get(&protocol).cloned().unwrap_or_else(|| { - panic!("Parser for protocol {protocol} not found"); - }) - } - - /// 创建所有协议的事件解析器 - pub fn create_all_parsers() -> Vec> { - Self::supported_protocols() - .into_iter() - .map(Self::create_parser) - .collect() - } - - /// 获取所有支持的协议 - pub fn supported_protocols() -> Vec { - vec![Protocol::PumpSwap] - } - - /// 检查协议是否支持 - pub fn is_supported(protocol: &Protocol) -> bool { - Self::supported_protocols().contains(protocol) - } -} diff --git a/src/streaming/event_parser/mod.rs b/src/streaming/event_parser/mod.rs index 99d71eb..cb5a70f 100755 --- a/src/streaming/event_parser/mod.rs +++ b/src/streaming/event_parser/mod.rs @@ -1,17 +1,16 @@ pub mod common; pub mod core; -pub mod factory; pub mod protocols; -pub use core::traits::{EventParser, UnifiedEvent}; -pub use factory::{EventParserFactory, Protocol}; +pub use core::traits::UnifiedEvent; +pub use protocols::types::Protocol; /// 宏:简化 downcast_ref 模式匹配 -/// +/// /// # 使用示例 /// ``` /// use sol_trade_sdk::event_parser::match_event; -/// +/// /// match_event!(event, { /// PumpSwapCreatePoolEvent => |typed_event| { /// println!("CreatePool event: {:?}", typed_event); diff --git a/src/streaming/event_parser/protocols/bonk/mod.rs b/src/streaming/event_parser/protocols/bonk/mod.rs index 8c7a629..9a2a832 100755 --- a/src/streaming/event_parser/protocols/bonk/mod.rs +++ b/src/streaming/event_parser/protocols/bonk/mod.rs @@ -3,5 +3,4 @@ pub mod parser; pub mod types; pub use events::*; -pub use parser::BonkEventParser; -pub use types::*; \ No newline at end of file +pub use types::*; diff --git a/src/streaming/event_parser/protocols/bonk/parser.rs b/src/streaming/event_parser/protocols/bonk/parser.rs index ac2eac0..f64203c 100755 --- a/src/streaming/event_parser/protocols/bonk/parser.rs +++ b/src/streaming/event_parser/protocols/bonk/parser.rs @@ -1,635 +1,611 @@ use solana_sdk::pubkey::Pubkey; -use crate::{ - impl_event_parser_delegate, - streaming::event_parser::{ - common::{utils::*, EventMetadata, EventType, ProtocolType}, - core::traits::{GenericEventParseConfig, GenericEventParser, UnifiedEvent}, - protocols::bonk::{ - bonk_pool_create_event_log_decode, bonk_trade_event_log_decode, discriminators, - AmmFeeOn, BonkMigrateToAmmEvent, BonkMigrateToCpswapEvent, BonkPoolCreateEvent, - BonkTradeEvent, ConstantCurve, CurveParams, FixedCurve, LinearCurve, MintParams, - TradeDirection, VestingParams, - }, +use crate::streaming::event_parser::{ + common::{utils::*, EventMetadata, EventType, ProtocolType}, + core::event_parser::GenericEventParseConfig, + protocols::bonk::{ + bonk_pool_create_event_log_decode, bonk_trade_event_log_decode, discriminators, AmmFeeOn, + BonkMigrateToAmmEvent, BonkMigrateToCpswapEvent, BonkPoolCreateEvent, BonkTradeEvent, + ConstantCurve, CurveParams, FixedCurve, LinearCurve, MintParams, TradeDirection, + VestingParams, }, + UnifiedEvent, }; /// Bonk Program ID pub const BONK_PROGRAM_ID: Pubkey = solana_sdk::pubkey!("LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj"); -/// Bonk Event Parser -pub struct BonkEventParser { - inner: GenericEventParser, -} - -impl Default for BonkEventParser { - fn default() -> Self { - Self::new() +// Configure all event types +pub const CONFIGS: &[GenericEventParseConfig] = &[ + GenericEventParseConfig { + program_id: BONK_PROGRAM_ID, + protocol_type: ProtocolType::Bonk, + inner_instruction_discriminator: discriminators::TRADE_EVENT, + instruction_discriminator: discriminators::BUY_EXACT_IN, + event_type: EventType::BonkBuyExactIn, + inner_instruction_parser: Some(parse_trade_inner_instruction), + instruction_parser: Some(parse_buy_exact_in_instruction), + requires_inner_instruction: false, + }, + GenericEventParseConfig { + program_id: BONK_PROGRAM_ID, + protocol_type: ProtocolType::Bonk, + inner_instruction_discriminator: discriminators::TRADE_EVENT, + instruction_discriminator: discriminators::BUY_EXACT_OUT, + event_type: EventType::BonkBuyExactOut, + inner_instruction_parser: Some(parse_trade_inner_instruction), + instruction_parser: Some(parse_buy_exact_out_instruction), + requires_inner_instruction: false, + }, + GenericEventParseConfig { + program_id: BONK_PROGRAM_ID, + protocol_type: ProtocolType::Bonk, + inner_instruction_discriminator: discriminators::TRADE_EVENT, + instruction_discriminator: discriminators::SELL_EXACT_IN, + event_type: EventType::BonkSellExactIn, + inner_instruction_parser: Some(parse_trade_inner_instruction), + instruction_parser: Some(parse_sell_exact_in_instruction), + requires_inner_instruction: false, + }, + GenericEventParseConfig { + program_id: BONK_PROGRAM_ID, + protocol_type: ProtocolType::Bonk, + inner_instruction_discriminator: discriminators::TRADE_EVENT, + instruction_discriminator: discriminators::SELL_EXACT_OUT, + event_type: EventType::BonkSellExactOut, + inner_instruction_parser: Some(parse_trade_inner_instruction), + instruction_parser: Some(parse_sell_exact_out_instruction), + requires_inner_instruction: false, + }, + GenericEventParseConfig { + program_id: BONK_PROGRAM_ID, + protocol_type: ProtocolType::Bonk, + inner_instruction_discriminator: discriminators::POOL_CREATE_EVENT, + instruction_discriminator: discriminators::INITIALIZE, + event_type: EventType::BonkInitialize, + inner_instruction_parser: Some(parse_pool_create_inner_instruction), + instruction_parser: Some(parse_initialize_instruction), + requires_inner_instruction: false, + }, + GenericEventParseConfig { + program_id: BONK_PROGRAM_ID, + protocol_type: ProtocolType::Bonk, + inner_instruction_discriminator: discriminators::POOL_CREATE_EVENT, + instruction_discriminator: discriminators::INITIALIZE_V2, + event_type: EventType::BonkInitializeV2, + inner_instruction_parser: Some(parse_pool_create_inner_instruction), + instruction_parser: Some(parse_initialize_v2_instruction), + requires_inner_instruction: false, + }, + GenericEventParseConfig { + program_id: BONK_PROGRAM_ID, + protocol_type: ProtocolType::Bonk, + inner_instruction_discriminator: discriminators::POOL_CREATE_EVENT, + instruction_discriminator: discriminators::INITIALIZE_WITH_TOKEN_2022, + event_type: EventType::BonkInitializeWithToken2022, + inner_instruction_parser: Some(parse_pool_create_inner_instruction), + instruction_parser: Some(parse_initialize_with_token_2022_instruction), + requires_inner_instruction: false, + }, + GenericEventParseConfig { + program_id: BONK_PROGRAM_ID, + protocol_type: ProtocolType::Bonk, + inner_instruction_discriminator: &[], + instruction_discriminator: discriminators::MIGRATE_TO_AMM, + event_type: EventType::BonkMigrateToAmm, + inner_instruction_parser: None, + instruction_parser: Some(parse_migrate_to_amm_instruction), + requires_inner_instruction: false, + }, + GenericEventParseConfig { + program_id: BONK_PROGRAM_ID, + protocol_type: ProtocolType::Bonk, + inner_instruction_discriminator: &[], + instruction_discriminator: discriminators::MIGRATE_TO_CP_SWAP, + event_type: EventType::BonkMigrateToCpswap, + inner_instruction_parser: None, + instruction_parser: Some(parse_migrate_to_cpswap_instruction), + requires_inner_instruction: false, + }, +]; +/// Parse pool creation event +fn parse_pool_create_inner_instruction( + data: &[u8], + metadata: EventMetadata, +) -> Option> { + if let Some(event) = bonk_pool_create_event_log_decode(data) { + Some(Box::new(BonkPoolCreateEvent { metadata, ..event })) + } else { + None } } -impl BonkEventParser { - pub fn new() -> Self { - // Configure all event types - let configs = vec![ - GenericEventParseConfig { - program_id: BONK_PROGRAM_ID, - protocol_type: ProtocolType::Bonk, - inner_instruction_discriminator: discriminators::TRADE_EVENT, - instruction_discriminator: discriminators::BUY_EXACT_IN, - event_type: EventType::BonkBuyExactIn, - inner_instruction_parser: Some(Self::parse_trade_inner_instruction), - instruction_parser: Some(Self::parse_buy_exact_in_instruction), - requires_inner_instruction: false, - }, - GenericEventParseConfig { - program_id: BONK_PROGRAM_ID, - protocol_type: ProtocolType::Bonk, - inner_instruction_discriminator: discriminators::TRADE_EVENT, - instruction_discriminator: discriminators::BUY_EXACT_OUT, - event_type: EventType::BonkBuyExactOut, - inner_instruction_parser: Some(Self::parse_trade_inner_instruction), - instruction_parser: Some(Self::parse_buy_exact_out_instruction), - requires_inner_instruction: false, - }, - GenericEventParseConfig { - program_id: BONK_PROGRAM_ID, - protocol_type: ProtocolType::Bonk, - inner_instruction_discriminator: discriminators::TRADE_EVENT, - instruction_discriminator: discriminators::SELL_EXACT_IN, - event_type: EventType::BonkSellExactIn, - inner_instruction_parser: Some(Self::parse_trade_inner_instruction), - instruction_parser: Some(Self::parse_sell_exact_in_instruction), - requires_inner_instruction: false, - }, - GenericEventParseConfig { - program_id: BONK_PROGRAM_ID, - protocol_type: ProtocolType::Bonk, - inner_instruction_discriminator: discriminators::TRADE_EVENT, - instruction_discriminator: discriminators::SELL_EXACT_OUT, - event_type: EventType::BonkSellExactOut, - inner_instruction_parser: Some(Self::parse_trade_inner_instruction), - instruction_parser: Some(Self::parse_sell_exact_out_instruction), - requires_inner_instruction: false, - }, - GenericEventParseConfig { - program_id: BONK_PROGRAM_ID, - protocol_type: ProtocolType::Bonk, - inner_instruction_discriminator: discriminators::POOL_CREATE_EVENT, - instruction_discriminator: discriminators::INITIALIZE, - event_type: EventType::BonkInitialize, - inner_instruction_parser: Some(Self::parse_pool_create_inner_instruction), - instruction_parser: Some(Self::parse_initialize_instruction), - requires_inner_instruction: false, - }, - GenericEventParseConfig { - program_id: BONK_PROGRAM_ID, - protocol_type: ProtocolType::Bonk, - inner_instruction_discriminator: discriminators::POOL_CREATE_EVENT, - instruction_discriminator: discriminators::INITIALIZE_V2, - event_type: EventType::BonkInitializeV2, - inner_instruction_parser: Some(Self::parse_pool_create_inner_instruction), - instruction_parser: Some(Self::parse_initialize_v2_instruction), - requires_inner_instruction: false, - }, - GenericEventParseConfig { - program_id: BONK_PROGRAM_ID, - protocol_type: ProtocolType::Bonk, - inner_instruction_discriminator: discriminators::POOL_CREATE_EVENT, - instruction_discriminator: discriminators::INITIALIZE_WITH_TOKEN_2022, - event_type: EventType::BonkInitializeWithToken2022, - inner_instruction_parser: Some(Self::parse_pool_create_inner_instruction), - instruction_parser: Some(Self::parse_initialize_with_token_2022_instruction), - requires_inner_instruction: false, - }, - GenericEventParseConfig { - program_id: BONK_PROGRAM_ID, - protocol_type: ProtocolType::Bonk, - inner_instruction_discriminator: &[], - instruction_discriminator: discriminators::MIGRATE_TO_AMM, - event_type: EventType::BonkMigrateToAmm, - inner_instruction_parser: None, - instruction_parser: Some(Self::parse_migrate_to_amm_instruction), - requires_inner_instruction: false, - }, - GenericEventParseConfig { - program_id: BONK_PROGRAM_ID, - protocol_type: ProtocolType::Bonk, - inner_instruction_discriminator: &[], - instruction_discriminator: discriminators::MIGRATE_TO_CP_SWAP, - event_type: EventType::BonkMigrateToCpswap, - inner_instruction_parser: None, - instruction_parser: Some(Self::parse_migrate_to_cpswap_instruction), - requires_inner_instruction: false, - }, - ]; - - let inner = GenericEventParser::new(vec![BONK_PROGRAM_ID], configs); - - Self { inner } - } - - /// Parse pool creation event - fn parse_pool_create_inner_instruction( - data: &[u8], - metadata: EventMetadata, - ) -> Option> { - if let Some(event) = bonk_pool_create_event_log_decode(data) { - Some(Box::new(BonkPoolCreateEvent { metadata, ..event })) - } else { - None - } - } - - /// Parse trade event - fn parse_trade_inner_instruction( - data: &[u8], - metadata: EventMetadata, - ) -> Option> { - if let Some(event) = bonk_trade_event_log_decode(data) { - if metadata.event_type == EventType::BonkBuyExactIn - || metadata.event_type == EventType::BonkBuyExactOut - { - if event.trade_direction != TradeDirection::Buy { - return None; - } - } else if (metadata.event_type == EventType::BonkSellExactIn - || metadata.event_type == EventType::BonkSellExactOut) - && event.trade_direction != TradeDirection::Sell - { +/// Parse trade event +fn parse_trade_inner_instruction( + data: &[u8], + metadata: EventMetadata, +) -> Option> { + if let Some(event) = bonk_trade_event_log_decode(data) { + if metadata.event_type == EventType::BonkBuyExactIn + || metadata.event_type == EventType::BonkBuyExactOut + { + if event.trade_direction != TradeDirection::Buy { return None; } - Some(Box::new(BonkTradeEvent { metadata, ..event })) - } else { - None - } - } - - /// Parse buy instruction event - fn parse_buy_exact_in_instruction( - data: &[u8], - accounts: &[Pubkey], - metadata: EventMetadata, - ) -> Option> { - if data.len() < 16 || accounts.len() < 18 { + } else if (metadata.event_type == EventType::BonkSellExactIn + || metadata.event_type == EventType::BonkSellExactOut) + && event.trade_direction != TradeDirection::Sell + { return None; } - - let amount_in = read_u64_le(data, 0)?; - let minimum_amount_out = read_u64_le(data, 8)?; - let share_fee_rate = read_u64_le(data, 16)?; - - Some(Box::new(BonkTradeEvent { - metadata, - amount_in, - minimum_amount_out, - share_fee_rate, - payer: accounts[0], - global_config: accounts[2], - platform_config: accounts[3], - pool_state: accounts[4], - user_base_token: accounts[5], - user_quote_token: accounts[6], - base_vault: accounts[7], - quote_vault: accounts[8], - base_token_mint: accounts[9], - quote_token_mint: accounts[10], - base_token_program: accounts[11], - quote_token_program: accounts[12], - system_program: accounts[15], - platform_associated_account: accounts[16], - creator_associated_account: accounts[17], - trade_direction: TradeDirection::Buy, - ..Default::default() - })) - } - - fn parse_buy_exact_out_instruction( - data: &[u8], - accounts: &[Pubkey], - metadata: EventMetadata, - ) -> Option> { - if data.len() < 16 || accounts.len() < 18 { - return None; - } - - let amount_out = read_u64_le(data, 0)?; - let maximum_amount_in = read_u64_le(data, 8)?; - let share_fee_rate = read_u64_le(data, 16)?; - - Some(Box::new(BonkTradeEvent { - metadata, - amount_out, - maximum_amount_in, - share_fee_rate, - payer: accounts[0], - global_config: accounts[2], - platform_config: accounts[3], - pool_state: accounts[4], - user_base_token: accounts[5], - user_quote_token: accounts[6], - base_vault: accounts[7], - quote_vault: accounts[8], - base_token_mint: accounts[9], - quote_token_mint: accounts[10], - base_token_program: accounts[11], - quote_token_program: accounts[12], - system_program: accounts[15], - platform_associated_account: accounts[16], - creator_associated_account: accounts[17], - trade_direction: TradeDirection::Buy, - ..Default::default() - })) - } - - fn parse_sell_exact_in_instruction( - data: &[u8], - accounts: &[Pubkey], - metadata: EventMetadata, - ) -> Option> { - if data.len() < 16 || accounts.len() < 18 { - return None; - } - - let amount_in = read_u64_le(data, 0)?; - let minimum_amount_out = read_u64_le(data, 8)?; - let share_fee_rate = read_u64_le(data, 16)?; - - Some(Box::new(BonkTradeEvent { - metadata, - amount_in, - minimum_amount_out, - share_fee_rate, - payer: accounts[0], - global_config: accounts[2], - platform_config: accounts[3], - pool_state: accounts[4], - user_base_token: accounts[5], - user_quote_token: accounts[6], - base_vault: accounts[7], - quote_vault: accounts[8], - base_token_mint: accounts[9], - quote_token_mint: accounts[10], - base_token_program: accounts[11], - quote_token_program: accounts[12], - system_program: accounts[15], - platform_associated_account: accounts[16], - creator_associated_account: accounts[17], - trade_direction: TradeDirection::Sell, - ..Default::default() - })) - } - - fn parse_sell_exact_out_instruction( - data: &[u8], - accounts: &[Pubkey], - metadata: EventMetadata, - ) -> Option> { - if data.len() < 16 || accounts.len() < 18 { - return None; - } - - let amount_out = read_u64_le(data, 0)?; - let maximum_amount_in = read_u64_le(data, 8)?; - let share_fee_rate = read_u64_le(data, 16)?; - - Some(Box::new(BonkTradeEvent { - metadata, - amount_out, - maximum_amount_in, - share_fee_rate, - payer: accounts[0], - global_config: accounts[2], - platform_config: accounts[3], - pool_state: accounts[4], - user_base_token: accounts[5], - user_quote_token: accounts[6], - base_vault: accounts[7], - quote_vault: accounts[8], - base_token_mint: accounts[9], - quote_token_mint: accounts[10], - base_token_program: accounts[11], - quote_token_program: accounts[12], - system_program: accounts[15], - platform_associated_account: accounts[16], - creator_associated_account: accounts[17], - trade_direction: TradeDirection::Sell, - ..Default::default() - })) - } - - /// Parse initialize event - fn parse_initialize_instruction( - data: &[u8], - accounts: &[Pubkey], - metadata: EventMetadata, - ) -> Option> { - if data.len() < 24 { - return None; - } - - let mut offset = 0; - let base_mint_param = Self::parse_mint_params(data, &mut offset)?; - let curve_param = Self::parse_curve_params(data, &mut offset)?; - let vesting_param = Self::parse_vesting_params(data, &mut offset)?; - - Some(Box::new(BonkPoolCreateEvent { - metadata, - payer: accounts[0], - creator: accounts[1], - global_config: accounts[2], - platform_config: accounts[3], - pool_state: accounts[5], - base_mint: accounts[6], - quote_mint: accounts[7], - base_vault: accounts[8], - quote_vault: accounts[9], - base_mint_param, - curve_param, - vesting_param, - ..Default::default() - })) - } - - /// Parse initialize event - fn parse_initialize_v2_instruction( - data: &[u8], - accounts: &[Pubkey], - metadata: EventMetadata, - ) -> Option> { - if data.len() < 24 { - return None; - } - - let mut offset = 0; - let base_mint_param = Self::parse_mint_params(data, &mut offset)?; - let curve_param = Self::parse_curve_params(data, &mut offset)?; - let vesting_param = Self::parse_vesting_params(data, &mut offset)?; - let amm_fee_on = data[offset]; - - Some(Box::new(BonkPoolCreateEvent { - metadata, - payer: accounts[0], - creator: accounts[1], - global_config: accounts[2], - platform_config: accounts[3], - pool_state: accounts[5], - base_mint: accounts[6], - quote_mint: accounts[7], - base_vault: accounts[8], - quote_vault: accounts[9], - base_mint_param, - curve_param, - vesting_param, - amm_fee_on: if amm_fee_on == 0 { - Some(AmmFeeOn::QuoteToken) - } else { - Some(AmmFeeOn::BothToken) - }, - ..Default::default() - })) - } - - /// Parse initialize event - fn parse_initialize_with_token_2022_instruction( - data: &[u8], - accounts: &[Pubkey], - metadata: EventMetadata, - ) -> Option> { - if data.len() < 24 { - return None; - } - - let mut offset = 0; - let base_mint_param = Self::parse_mint_params(data, &mut offset)?; - let curve_param = Self::parse_curve_params(data, &mut offset)?; - let vesting_param = Self::parse_vesting_params(data, &mut offset)?; - let amm_fee_on = data[offset]; - - Some(Box::new(BonkPoolCreateEvent { - metadata, - payer: accounts[0], - creator: accounts[1], - global_config: accounts[2], - platform_config: accounts[3], - pool_state: accounts[5], - base_mint: accounts[6], - quote_mint: accounts[7], - base_vault: accounts[8], - quote_vault: accounts[9], - base_mint_param, - curve_param, - vesting_param, - amm_fee_on: if amm_fee_on == 0 { - Some(AmmFeeOn::QuoteToken) - } else { - Some(AmmFeeOn::BothToken) - }, - ..Default::default() - })) - } - - /// Parse MintParams structure - fn parse_mint_params(data: &[u8], offset: &mut usize) -> Option { - // Read decimals (1 byte) - let decimals = read_u8(data, *offset)?; - *offset += 1; - - // Read name string length and content - let name_len = read_u32_le(data, *offset)? as usize; - *offset += 4; - if data.len() < *offset + name_len { - return None; - } - let name = String::from_utf8(data[*offset..*offset + name_len].to_vec()).ok()?; - *offset += name_len; - - // Read symbol string length and content - let symbol_len = read_u32_le(data, *offset)? as usize; - *offset += 4; - if data.len() < *offset + symbol_len { - return None; - } - let symbol = String::from_utf8(data[*offset..*offset + symbol_len].to_vec()).ok()?; - *offset += symbol_len; - - // Read uri string length and content - let uri_len = read_u32_le(data, *offset)? as usize; - *offset += 4; - if data.len() < *offset + uri_len { - return None; - } - let uri = String::from_utf8(data[*offset..*offset + uri_len].to_vec()).ok()?; - *offset += uri_len; - - Some(MintParams { decimals, name, symbol, uri }) - } - - /// Parse CurveParams structure - fn parse_curve_params(data: &[u8], offset: &mut usize) -> Option { - // Read curve type identifier (1 byte) - let curve_type = read_u8(data, *offset)?; - *offset += 1; - - match curve_type { - 0 => { - // Constant curve - let supply = read_u64_le(data, *offset)?; - *offset += 8; - let total_base_sell = read_u64_le(data, *offset)?; - *offset += 8; - let total_quote_fund_raising = read_u64_le(data, *offset)?; - *offset += 8; - let migrate_type = read_u8(data, *offset)?; - *offset += 1; - - Some(CurveParams::Constant { - data: ConstantCurve { - supply, - total_base_sell, - total_quote_fund_raising, - migrate_type, - }, - }) - } - 1 => { - // Fixed curve - let supply = read_u64_le(data, *offset)?; - *offset += 8; - let total_quote_fund_raising = read_u64_le(data, *offset)?; - *offset += 8; - let migrate_type = read_u8(data, *offset)?; - *offset += 1; - - Some(CurveParams::Fixed { - data: FixedCurve { supply, total_quote_fund_raising, migrate_type }, - }) - } - 2 => { - // Linear curve - let supply = read_u64_le(data, *offset)?; - *offset += 8; - let total_quote_fund_raising = read_u64_le(data, *offset)?; - *offset += 8; - let migrate_type = read_u8(data, *offset)?; - *offset += 1; - - Some(CurveParams::Linear { - data: LinearCurve { supply, total_quote_fund_raising, migrate_type }, - }) - } - _ => None, - } - } - - /// Parse VestingParams structure - fn parse_vesting_params(data: &[u8], offset: &mut usize) -> Option { - let total_locked_amount = read_u64_le(data, *offset)?; - *offset += 8; - let cliff_period = read_u64_le(data, *offset)?; - *offset += 8; - let unlock_period = read_u64_le(data, *offset)?; - *offset += 8; - - Some(VestingParams { total_locked_amount, cliff_period, unlock_period }) - } - - /// Parse migrate to AMM event - fn parse_migrate_to_amm_instruction( - data: &[u8], - accounts: &[Pubkey], - metadata: EventMetadata, - ) -> Option> { - if data.len() < 16 { - return None; - } - - let base_lot_size = u64::from_le_bytes(data[0..8].try_into().unwrap()); - let quote_lot_size = u64::from_le_bytes(data[8..16].try_into().unwrap()); - let market_vault_signer_nonce = data[16]; - - Some(Box::new(BonkMigrateToAmmEvent { - metadata, - base_lot_size, - quote_lot_size, - market_vault_signer_nonce, - payer: accounts[0], - base_mint: accounts[1], - quote_mint: accounts[2], - openbook_program: accounts[3], - market: accounts[4], - request_queue: accounts[5], - event_queue: accounts[6], - bids: accounts[7], - asks: accounts[8], - market_vault_signer: accounts[9], - market_base_vault: accounts[10], - market_quote_vault: accounts[11], - amm_program: accounts[12], - amm_pool: accounts[13], - amm_authority: accounts[14], - amm_open_orders: accounts[15], - amm_lp_mint: accounts[16], - amm_base_vault: accounts[17], - amm_quote_vault: accounts[18], - amm_target_orders: accounts[19], - amm_config: accounts[20], - amm_create_fee_destination: accounts[21], - authority: accounts[22], - pool_state: accounts[23], - global_config: accounts[24], - base_vault: accounts[25], - quote_vault: accounts[26], - pool_lp_token: accounts[27], - spl_token_program: accounts[28], - associated_token_program: accounts[29], - system_program: accounts[30], - rent_program: accounts[31], - ..Default::default() - })) - } - - /// Parse migrate to CP Swap event - fn parse_migrate_to_cpswap_instruction( - _data: &[u8], - accounts: &[Pubkey], - metadata: EventMetadata, - ) -> Option> { - Some(Box::new(BonkMigrateToCpswapEvent { - metadata, - payer: accounts[0], - base_mint: accounts[1], - quote_mint: accounts[2], - platform_config: accounts[3], - cpswap_program: accounts[4], - cpswap_pool: accounts[5], - cpswap_authority: accounts[6], - cpswap_lp_mint: accounts[7], - cpswap_base_vault: accounts[8], - cpswap_quote_vault: accounts[9], - cpswap_config: accounts[10], - cpswap_create_pool_fee: accounts[11], - cpswap_observation: accounts[12], - lock_program: accounts[13], - lock_authority: accounts[14], - lock_lp_vault: accounts[15], - authority: accounts[16], - pool_state: accounts[17], - global_config: accounts[18], - base_vault: accounts[19], - quote_vault: accounts[20], - pool_lp_token: accounts[21], - base_token_program: accounts[22], - quote_token_program: accounts[23], - associated_token_program: accounts[24], - system_program: accounts[25], - rent_program: accounts[26], - metadata_program: accounts[27], - remaining_accounts: accounts[28..].to_vec(), - ..Default::default() - })) + Some(Box::new(BonkTradeEvent { metadata, ..event })) + } else { + None } } -impl_event_parser_delegate!(BonkEventParser); +/// Parse buy instruction event +fn parse_buy_exact_in_instruction( + data: &[u8], + accounts: &[Pubkey], + metadata: EventMetadata, +) -> Option> { + if data.len() < 16 || accounts.len() < 18 { + return None; + } + + let amount_in = read_u64_le(data, 0)?; + let minimum_amount_out = read_u64_le(data, 8)?; + let share_fee_rate = read_u64_le(data, 16)?; + + Some(Box::new(BonkTradeEvent { + metadata, + amount_in, + minimum_amount_out, + share_fee_rate, + payer: accounts[0], + global_config: accounts[2], + platform_config: accounts[3], + pool_state: accounts[4], + user_base_token: accounts[5], + user_quote_token: accounts[6], + base_vault: accounts[7], + quote_vault: accounts[8], + base_token_mint: accounts[9], + quote_token_mint: accounts[10], + base_token_program: accounts[11], + quote_token_program: accounts[12], + system_program: accounts[15], + platform_associated_account: accounts[16], + creator_associated_account: accounts[17], + trade_direction: TradeDirection::Buy, + ..Default::default() + })) +} + +fn parse_buy_exact_out_instruction( + data: &[u8], + accounts: &[Pubkey], + metadata: EventMetadata, +) -> Option> { + if data.len() < 16 || accounts.len() < 18 { + return None; + } + + let amount_out = read_u64_le(data, 0)?; + let maximum_amount_in = read_u64_le(data, 8)?; + let share_fee_rate = read_u64_le(data, 16)?; + + Some(Box::new(BonkTradeEvent { + metadata, + amount_out, + maximum_amount_in, + share_fee_rate, + payer: accounts[0], + global_config: accounts[2], + platform_config: accounts[3], + pool_state: accounts[4], + user_base_token: accounts[5], + user_quote_token: accounts[6], + base_vault: accounts[7], + quote_vault: accounts[8], + base_token_mint: accounts[9], + quote_token_mint: accounts[10], + base_token_program: accounts[11], + quote_token_program: accounts[12], + system_program: accounts[15], + platform_associated_account: accounts[16], + creator_associated_account: accounts[17], + trade_direction: TradeDirection::Buy, + ..Default::default() + })) +} + +fn parse_sell_exact_in_instruction( + data: &[u8], + accounts: &[Pubkey], + metadata: EventMetadata, +) -> Option> { + if data.len() < 16 || accounts.len() < 18 { + return None; + } + + let amount_in = read_u64_le(data, 0)?; + let minimum_amount_out = read_u64_le(data, 8)?; + let share_fee_rate = read_u64_le(data, 16)?; + + Some(Box::new(BonkTradeEvent { + metadata, + amount_in, + minimum_amount_out, + share_fee_rate, + payer: accounts[0], + global_config: accounts[2], + platform_config: accounts[3], + pool_state: accounts[4], + user_base_token: accounts[5], + user_quote_token: accounts[6], + base_vault: accounts[7], + quote_vault: accounts[8], + base_token_mint: accounts[9], + quote_token_mint: accounts[10], + base_token_program: accounts[11], + quote_token_program: accounts[12], + system_program: accounts[15], + platform_associated_account: accounts[16], + creator_associated_account: accounts[17], + trade_direction: TradeDirection::Sell, + ..Default::default() + })) +} + +fn parse_sell_exact_out_instruction( + data: &[u8], + accounts: &[Pubkey], + metadata: EventMetadata, +) -> Option> { + if data.len() < 16 || accounts.len() < 18 { + return None; + } + + let amount_out = read_u64_le(data, 0)?; + let maximum_amount_in = read_u64_le(data, 8)?; + let share_fee_rate = read_u64_le(data, 16)?; + + Some(Box::new(BonkTradeEvent { + metadata, + amount_out, + maximum_amount_in, + share_fee_rate, + payer: accounts[0], + global_config: accounts[2], + platform_config: accounts[3], + pool_state: accounts[4], + user_base_token: accounts[5], + user_quote_token: accounts[6], + base_vault: accounts[7], + quote_vault: accounts[8], + base_token_mint: accounts[9], + quote_token_mint: accounts[10], + base_token_program: accounts[11], + quote_token_program: accounts[12], + system_program: accounts[15], + platform_associated_account: accounts[16], + creator_associated_account: accounts[17], + trade_direction: TradeDirection::Sell, + ..Default::default() + })) +} + +/// Parse initialize event +fn parse_initialize_instruction( + data: &[u8], + accounts: &[Pubkey], + metadata: EventMetadata, +) -> Option> { + if data.len() < 24 { + return None; + } + + let mut offset = 0; + let base_mint_param = parse_mint_params(data, &mut offset)?; + let curve_param = parse_curve_params(data, &mut offset)?; + let vesting_param = parse_vesting_params(data, &mut offset)?; + + Some(Box::new(BonkPoolCreateEvent { + metadata, + payer: accounts[0], + creator: accounts[1], + global_config: accounts[2], + platform_config: accounts[3], + pool_state: accounts[5], + base_mint: accounts[6], + quote_mint: accounts[7], + base_vault: accounts[8], + quote_vault: accounts[9], + base_mint_param, + curve_param, + vesting_param, + ..Default::default() + })) +} + +/// Parse initialize event +fn parse_initialize_v2_instruction( + data: &[u8], + accounts: &[Pubkey], + metadata: EventMetadata, +) -> Option> { + if data.len() < 24 { + return None; + } + + let mut offset = 0; + let base_mint_param = parse_mint_params(data, &mut offset)?; + let curve_param = parse_curve_params(data, &mut offset)?; + let vesting_param = parse_vesting_params(data, &mut offset)?; + let amm_fee_on = data[offset]; + + Some(Box::new(BonkPoolCreateEvent { + metadata, + payer: accounts[0], + creator: accounts[1], + global_config: accounts[2], + platform_config: accounts[3], + pool_state: accounts[5], + base_mint: accounts[6], + quote_mint: accounts[7], + base_vault: accounts[8], + quote_vault: accounts[9], + base_mint_param, + curve_param, + vesting_param, + amm_fee_on: if amm_fee_on == 0 { + Some(AmmFeeOn::QuoteToken) + } else { + Some(AmmFeeOn::BothToken) + }, + ..Default::default() + })) +} + +/// Parse initialize event +fn parse_initialize_with_token_2022_instruction( + data: &[u8], + accounts: &[Pubkey], + metadata: EventMetadata, +) -> Option> { + if data.len() < 24 { + return None; + } + + let mut offset = 0; + let base_mint_param = parse_mint_params(data, &mut offset)?; + let curve_param = parse_curve_params(data, &mut offset)?; + let vesting_param = parse_vesting_params(data, &mut offset)?; + let amm_fee_on = data[offset]; + + Some(Box::new(BonkPoolCreateEvent { + metadata, + payer: accounts[0], + creator: accounts[1], + global_config: accounts[2], + platform_config: accounts[3], + pool_state: accounts[5], + base_mint: accounts[6], + quote_mint: accounts[7], + base_vault: accounts[8], + quote_vault: accounts[9], + base_mint_param, + curve_param, + vesting_param, + amm_fee_on: if amm_fee_on == 0 { + Some(AmmFeeOn::QuoteToken) + } else { + Some(AmmFeeOn::BothToken) + }, + ..Default::default() + })) +} + +/// Parse MintParams structure +fn parse_mint_params(data: &[u8], offset: &mut usize) -> Option { + // Read decimals (1 byte) + let decimals = read_u8(data, *offset)?; + *offset += 1; + + // Read name string length and content + let name_len = read_u32_le(data, *offset)? as usize; + *offset += 4; + if data.len() < *offset + name_len { + return None; + } + let name = String::from_utf8(data[*offset..*offset + name_len].to_vec()).ok()?; + *offset += name_len; + + // Read symbol string length and content + let symbol_len = read_u32_le(data, *offset)? as usize; + *offset += 4; + if data.len() < *offset + symbol_len { + return None; + } + let symbol = String::from_utf8(data[*offset..*offset + symbol_len].to_vec()).ok()?; + *offset += symbol_len; + + // Read uri string length and content + let uri_len = read_u32_le(data, *offset)? as usize; + *offset += 4; + if data.len() < *offset + uri_len { + return None; + } + let uri = String::from_utf8(data[*offset..*offset + uri_len].to_vec()).ok()?; + *offset += uri_len; + + Some(MintParams { decimals, name, symbol, uri }) +} + +/// Parse CurveParams structure +fn parse_curve_params(data: &[u8], offset: &mut usize) -> Option { + // Read curve type identifier (1 byte) + let curve_type = read_u8(data, *offset)?; + *offset += 1; + + match curve_type { + 0 => { + // Constant curve + let supply = read_u64_le(data, *offset)?; + *offset += 8; + let total_base_sell = read_u64_le(data, *offset)?; + *offset += 8; + let total_quote_fund_raising = read_u64_le(data, *offset)?; + *offset += 8; + let migrate_type = read_u8(data, *offset)?; + *offset += 1; + + Some(CurveParams::Constant { + data: ConstantCurve { + supply, + total_base_sell, + total_quote_fund_raising, + migrate_type, + }, + }) + } + 1 => { + // Fixed curve + let supply = read_u64_le(data, *offset)?; + *offset += 8; + let total_quote_fund_raising = read_u64_le(data, *offset)?; + *offset += 8; + let migrate_type = read_u8(data, *offset)?; + *offset += 1; + + Some(CurveParams::Fixed { + data: FixedCurve { supply, total_quote_fund_raising, migrate_type }, + }) + } + 2 => { + // Linear curve + let supply = read_u64_le(data, *offset)?; + *offset += 8; + let total_quote_fund_raising = read_u64_le(data, *offset)?; + *offset += 8; + let migrate_type = read_u8(data, *offset)?; + *offset += 1; + + Some(CurveParams::Linear { + data: LinearCurve { supply, total_quote_fund_raising, migrate_type }, + }) + } + _ => None, + } +} + +/// Parse VestingParams structure +fn parse_vesting_params(data: &[u8], offset: &mut usize) -> Option { + let total_locked_amount = read_u64_le(data, *offset)?; + *offset += 8; + let cliff_period = read_u64_le(data, *offset)?; + *offset += 8; + let unlock_period = read_u64_le(data, *offset)?; + *offset += 8; + + Some(VestingParams { total_locked_amount, cliff_period, unlock_period }) +} + +/// Parse migrate to AMM event +fn parse_migrate_to_amm_instruction( + data: &[u8], + accounts: &[Pubkey], + metadata: EventMetadata, +) -> Option> { + if data.len() < 16 { + return None; + } + + let base_lot_size = u64::from_le_bytes(data[0..8].try_into().unwrap()); + let quote_lot_size = u64::from_le_bytes(data[8..16].try_into().unwrap()); + let market_vault_signer_nonce = data[16]; + + Some(Box::new(BonkMigrateToAmmEvent { + metadata, + base_lot_size, + quote_lot_size, + market_vault_signer_nonce, + payer: accounts[0], + base_mint: accounts[1], + quote_mint: accounts[2], + openbook_program: accounts[3], + market: accounts[4], + request_queue: accounts[5], + event_queue: accounts[6], + bids: accounts[7], + asks: accounts[8], + market_vault_signer: accounts[9], + market_base_vault: accounts[10], + market_quote_vault: accounts[11], + amm_program: accounts[12], + amm_pool: accounts[13], + amm_authority: accounts[14], + amm_open_orders: accounts[15], + amm_lp_mint: accounts[16], + amm_base_vault: accounts[17], + amm_quote_vault: accounts[18], + amm_target_orders: accounts[19], + amm_config: accounts[20], + amm_create_fee_destination: accounts[21], + authority: accounts[22], + pool_state: accounts[23], + global_config: accounts[24], + base_vault: accounts[25], + quote_vault: accounts[26], + pool_lp_token: accounts[27], + spl_token_program: accounts[28], + associated_token_program: accounts[29], + system_program: accounts[30], + rent_program: accounts[31], + ..Default::default() + })) +} + +/// Parse migrate to CP Swap event +fn parse_migrate_to_cpswap_instruction( + _data: &[u8], + accounts: &[Pubkey], + metadata: EventMetadata, +) -> Option> { + Some(Box::new(BonkMigrateToCpswapEvent { + metadata, + payer: accounts[0], + base_mint: accounts[1], + quote_mint: accounts[2], + platform_config: accounts[3], + cpswap_program: accounts[4], + cpswap_pool: accounts[5], + cpswap_authority: accounts[6], + cpswap_lp_mint: accounts[7], + cpswap_base_vault: accounts[8], + cpswap_quote_vault: accounts[9], + cpswap_config: accounts[10], + cpswap_create_pool_fee: accounts[11], + cpswap_observation: accounts[12], + lock_program: accounts[13], + lock_authority: accounts[14], + lock_lp_vault: accounts[15], + authority: accounts[16], + pool_state: accounts[17], + global_config: accounts[18], + base_vault: accounts[19], + quote_vault: accounts[20], + pool_lp_token: accounts[21], + base_token_program: accounts[22], + quote_token_program: accounts[23], + associated_token_program: accounts[24], + system_program: accounts[25], + rent_program: accounts[26], + metadata_program: accounts[27], + remaining_accounts: accounts[28..].to_vec(), + ..Default::default() + })) +} diff --git a/src/streaming/event_parser/protocols/mod.rs b/src/streaming/event_parser/protocols/mod.rs index a082b31..71e3c64 100755 --- a/src/streaming/event_parser/protocols/mod.rs +++ b/src/streaming/event_parser/protocols/mod.rs @@ -1,17 +1,10 @@ +pub mod block; +pub mod bonk; pub mod pumpfun; pub mod pumpswap; -pub mod bonk; -pub mod raydium_cpmm; -pub mod raydium_clmm; pub mod raydium_amm_v4; -pub mod block; -pub mod mutil; - -pub use pumpfun::PumpFunEventParser; -pub use pumpswap::PumpSwapEventParser; -pub use bonk::BonkEventParser; -pub use raydium_cpmm::RaydiumCpmmEventParser; -pub use raydium_clmm::RaydiumClmmEventParser; -pub use raydium_amm_v4::RaydiumAmmV4EventParser; +pub mod raydium_clmm; +pub mod raydium_cpmm; +pub mod types; pub use block::block_meta_event::BlockMetaEvent; -pub use mutil::MutilEventParser; \ No newline at end of file +pub use types::Protocol; diff --git a/src/streaming/event_parser/protocols/mutil/mod.rs b/src/streaming/event_parser/protocols/mutil/mod.rs deleted file mode 100644 index 852450f..0000000 --- a/src/streaming/event_parser/protocols/mutil/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod parser; - -pub use parser::MutilEventParser; \ No newline at end of file diff --git a/src/streaming/event_parser/protocols/mutil/parser.rs b/src/streaming/event_parser/protocols/mutil/parser.rs deleted file mode 100755 index e64edfd..0000000 --- a/src/streaming/event_parser/protocols/mutil/parser.rs +++ /dev/null @@ -1,46 +0,0 @@ -use crate::{ - impl_event_parser_delegate, - streaming::event_parser::{ - common::filter::EventTypeFilter, - core::traits::{GenericEventParseConfig, GenericEventParser}, - EventParserFactory, Protocol, - }, -}; - -pub struct MutilEventParser { - inner: GenericEventParser, -} - -impl MutilEventParser { - pub fn new(protocols: Vec, event_type_filter: Option) -> Self { - let mut inner = GenericEventParser::new(vec![], vec![]); - // Configure all event types - for protocol in protocols { - let parse = EventParserFactory::create_parser(protocol); - - // Merge instruction_configs, append configurations to existing Vec - for (key, configs) in parse.instruction_configs() { - let filtered_configs: Vec = configs - .into_iter() - .filter(|config| { - event_type_filter - .as_ref() - .map(|filter| filter.include.contains(&config.event_type)) - .unwrap_or(true) - }) - .collect(); - inner - .instruction_configs - .entry(key) - .or_insert_with(Vec::new) - .extend(filtered_configs); - } - - // Append program_ids (this is already appending) - inner.program_ids.extend(parse.supported_program_ids().clone()); - } - Self { inner } - } -} - -impl_event_parser_delegate!(MutilEventParser); diff --git a/src/streaming/event_parser/protocols/pumpfun/mod.rs b/src/streaming/event_parser/protocols/pumpfun/mod.rs index fb51532..86156fa 100755 --- a/src/streaming/event_parser/protocols/pumpfun/mod.rs +++ b/src/streaming/event_parser/protocols/pumpfun/mod.rs @@ -3,4 +3,3 @@ pub mod parser; pub mod types; pub use events::*; -pub use parser::PumpFunEventParser; \ No newline at end of file diff --git a/src/streaming/event_parser/protocols/pumpfun/parser.rs b/src/streaming/event_parser/protocols/pumpfun/parser.rs index 6d79b74..58e1a0f 100755 --- a/src/streaming/event_parser/protocols/pumpfun/parser.rs +++ b/src/streaming/event_parser/protocols/pumpfun/parser.rs @@ -1,270 +1,246 @@ -use solana_sdk::pubkey::Pubkey; - -use crate::{ - impl_event_parser_delegate, - streaming::event_parser::{ - common::{EventMetadata, EventType, ProtocolType}, - core::traits::{GenericEventParseConfig, GenericEventParser, UnifiedEvent}, - protocols::pumpfun::{ - discriminators, pumpfun_create_token_event_log_decode, - pumpfun_migrate_event_log_decode, pumpfun_trade_event_log_decode, - PumpFunCreateTokenEvent, PumpFunMigrateEvent, PumpFunTradeEvent, - }, +use crate::streaming::event_parser::{ + common::{EventMetadata, EventType, ProtocolType}, + core::event_parser::GenericEventParseConfig, + protocols::pumpfun::{ + discriminators, pumpfun_create_token_event_log_decode, pumpfun_migrate_event_log_decode, + pumpfun_trade_event_log_decode, PumpFunCreateTokenEvent, PumpFunMigrateEvent, + PumpFunTradeEvent, }, + UnifiedEvent, }; +use solana_sdk::pubkey::Pubkey; /// PumpFun程序ID pub const PUMPFUN_PROGRAM_ID: Pubkey = solana_sdk::pubkey!("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"); -/// PumpFun事件解析器 -pub struct PumpFunEventParser { - inner: GenericEventParser, -} +// 配置所有事件类型 +pub const CONFIGS: &[GenericEventParseConfig] = &[ + GenericEventParseConfig { + program_id: PUMPFUN_PROGRAM_ID, + protocol_type: ProtocolType::PumpFun, + inner_instruction_discriminator: discriminators::CREATE_TOKEN_EVENT, + instruction_discriminator: discriminators::CREATE_TOKEN_IX, + event_type: EventType::PumpFunCreateToken, + inner_instruction_parser: Some(parse_create_token_inner_instruction), + instruction_parser: Some(parse_create_token_instruction), + requires_inner_instruction: false, + }, + GenericEventParseConfig { + program_id: PUMPFUN_PROGRAM_ID, + protocol_type: ProtocolType::PumpFun, + inner_instruction_discriminator: discriminators::TRADE_EVENT, + instruction_discriminator: discriminators::BUY_IX, + event_type: EventType::PumpFunBuy, + inner_instruction_parser: Some(parse_trade_inner_instruction), + instruction_parser: Some(parse_buy_instruction), + requires_inner_instruction: false, + }, + GenericEventParseConfig { + program_id: PUMPFUN_PROGRAM_ID, + protocol_type: ProtocolType::PumpFun, + inner_instruction_discriminator: discriminators::TRADE_EVENT, + instruction_discriminator: discriminators::SELL_IX, + event_type: EventType::PumpFunSell, + inner_instruction_parser: Some(parse_trade_inner_instruction), + instruction_parser: Some(parse_sell_instruction), + requires_inner_instruction: false, + }, + GenericEventParseConfig { + program_id: PUMPFUN_PROGRAM_ID, + protocol_type: ProtocolType::PumpFun, + inner_instruction_discriminator: discriminators::COMPLETE_PUMP_AMM_MIGRATION_EVENT, + instruction_discriminator: discriminators::MIGRATE_IX, + event_type: EventType::PumpFunMigrate, + inner_instruction_parser: Some(parse_migrate_inner_instruction), + instruction_parser: Some(parse_migrate_instruction), + // Failed migrations lack inner instruction data (typically "Bonding curve already migrated") + requires_inner_instruction: true, + }, +]; -impl Default for PumpFunEventParser { - fn default() -> Self { - Self::new() +/// 解析迁移事件 +fn parse_migrate_inner_instruction( + data: &[u8], + metadata: EventMetadata, +) -> Option> { + if let Some(event) = pumpfun_migrate_event_log_decode(data) { + Some(Box::new(PumpFunMigrateEvent { metadata, ..event })) + } else { + None } } -impl PumpFunEventParser { - pub fn new() -> Self { - // 配置所有事件类型 - let configs = vec![ - GenericEventParseConfig { - program_id: PUMPFUN_PROGRAM_ID, - protocol_type: ProtocolType::PumpFun, - inner_instruction_discriminator: discriminators::CREATE_TOKEN_EVENT, - instruction_discriminator: discriminators::CREATE_TOKEN_IX, - event_type: EventType::PumpFunCreateToken, - inner_instruction_parser: Some(Self::parse_create_token_inner_instruction), - instruction_parser: Some(Self::parse_create_token_instruction), - requires_inner_instruction: false, - }, - GenericEventParseConfig { - program_id: PUMPFUN_PROGRAM_ID, - protocol_type: ProtocolType::PumpFun, - inner_instruction_discriminator: discriminators::TRADE_EVENT, - instruction_discriminator: discriminators::BUY_IX, - event_type: EventType::PumpFunBuy, - inner_instruction_parser: Some(Self::parse_trade_inner_instruction), - instruction_parser: Some(Self::parse_buy_instruction), - requires_inner_instruction: false, - }, - GenericEventParseConfig { - program_id: PUMPFUN_PROGRAM_ID, - protocol_type: ProtocolType::PumpFun, - inner_instruction_discriminator: discriminators::TRADE_EVENT, - instruction_discriminator: discriminators::SELL_IX, - event_type: EventType::PumpFunSell, - inner_instruction_parser: Some(Self::parse_trade_inner_instruction), - instruction_parser: Some(Self::parse_sell_instruction), - requires_inner_instruction: false, - }, - GenericEventParseConfig { - program_id: PUMPFUN_PROGRAM_ID, - protocol_type: ProtocolType::PumpFun, - inner_instruction_discriminator: discriminators::COMPLETE_PUMP_AMM_MIGRATION_EVENT, - instruction_discriminator: discriminators::MIGRATE_IX, - event_type: EventType::PumpFunMigrate, - inner_instruction_parser: Some(Self::parse_migrate_inner_instruction), - instruction_parser: Some(Self::parse_migrate_instruction), - // Failed migrations lack inner instruction data (typically "Bonding curve already migrated") - requires_inner_instruction: true, - }, - ]; - - let inner = GenericEventParser::new(vec![PUMPFUN_PROGRAM_ID], configs); - - Self { inner } - } - - /// 解析迁移事件 - fn parse_migrate_inner_instruction( - data: &[u8], - metadata: EventMetadata, - ) -> Option> { - if let Some(event) = pumpfun_migrate_event_log_decode(data) { - Some(Box::new(PumpFunMigrateEvent { metadata, ..event })) - } else { - None - } - } - - /// 解析创建代币日志事件 - fn parse_create_token_inner_instruction( - data: &[u8], - metadata: EventMetadata, - ) -> Option> { - if let Some(event) = pumpfun_create_token_event_log_decode(data) { - Some(Box::new(PumpFunCreateTokenEvent { metadata, ..event })) - } else { - None - } - } - - /// 解析交易事件 - fn parse_trade_inner_instruction( - data: &[u8], - metadata: EventMetadata, - ) -> Option> { - if let Some(event) = pumpfun_trade_event_log_decode(data) { - Some(Box::new(PumpFunTradeEvent { metadata, ..event })) - } else { - None - } - } - - /// 解析创建代币指令事件 - fn parse_create_token_instruction( - data: &[u8], - accounts: &[Pubkey], - metadata: EventMetadata, - ) -> Option> { - if data.len() < 16 || accounts.len() < 11 { - return None; - } - let mut offset = 0; - let name_len = u32::from_le_bytes(data[offset..offset + 4].try_into().unwrap()) as usize; - offset += 4; - let name = String::from_utf8_lossy(&data[offset..offset + name_len]); - offset += name_len; - let symbol_len = u32::from_le_bytes(data[offset..offset + 4].try_into().unwrap()) as usize; - offset += 4; - let symbol = String::from_utf8_lossy(&data[offset..offset + symbol_len]); - offset += symbol_len; - let uri_len = u32::from_le_bytes(data[offset..offset + 4].try_into().unwrap()) as usize; - offset += 4; - let uri = String::from_utf8_lossy(&data[offset..offset + uri_len]); - offset += uri_len; - let creator = if offset + 32 <= data.len() { - Pubkey::new_from_array(data[offset..offset + 32].try_into().ok()?) - } else { - Pubkey::default() - }; - - Some(Box::new(PumpFunCreateTokenEvent { - metadata, - name: name.to_string(), - symbol: symbol.to_string(), - uri: uri.to_string(), - creator, - mint: accounts[0], - mint_authority: accounts[1], - bonding_curve: accounts[2], - associated_bonding_curve: accounts[3], - user: accounts[7], - ..Default::default() - })) - } - - // 解析买入指令事件 - fn parse_buy_instruction( - data: &[u8], - accounts: &[Pubkey], - metadata: EventMetadata, - ) -> Option> { - if data.len() < 16 || accounts.len() < 13 { - return None; - } - let amount = u64::from_le_bytes(data[0..8].try_into().unwrap()); - let max_sol_cost = u64::from_le_bytes(data[8..16].try_into().unwrap()); - Some(Box::new(PumpFunTradeEvent { - metadata, - global: accounts[0], - fee_recipient: accounts[1], - mint: accounts[2], - bonding_curve: accounts[3], - associated_bonding_curve: accounts[4], - associated_user: accounts[5], - user: accounts[6], - system_program: accounts[7], - token_program: accounts[8], - creator_vault: accounts[9], - event_authority: accounts[10], - program: accounts[11], - global_volume_accumulator: accounts[12], - user_volume_accumulator: accounts[13], - max_sol_cost, - amount, - is_buy: true, - ..Default::default() - })) - } - - // 解析卖出指令事件 - fn parse_sell_instruction( - data: &[u8], - accounts: &[Pubkey], - metadata: EventMetadata, - ) -> Option> { - if data.len() < 16 || accounts.len() < 11 { - return None; - } - let amount = u64::from_le_bytes(data[0..8].try_into().unwrap()); - let min_sol_output = u64::from_le_bytes(data[8..16].try_into().unwrap()); - Some(Box::new(PumpFunTradeEvent { - metadata, - global: accounts[0], - fee_recipient: accounts[1], - mint: accounts[2], - bonding_curve: accounts[3], - associated_bonding_curve: accounts[4], - associated_user: accounts[5], - user: accounts[6], - system_program: accounts[7], - creator_vault: accounts[8], - token_program: accounts[9], - event_authority: accounts[10], - program: accounts[11], - global_volume_accumulator: *accounts.get(12).unwrap_or(&Pubkey::default()), - user_volume_accumulator: *accounts.get(13).unwrap_or(&Pubkey::default()), - min_sol_output, - amount, - is_buy: false, - ..Default::default() - })) - } - - /// 解析迁移指令事件 - fn parse_migrate_instruction( - _data: &[u8], - accounts: &[Pubkey], - metadata: EventMetadata, - ) -> Option> { - if accounts.len() < 24 { - return None; - } - Some(Box::new(PumpFunMigrateEvent { - metadata, - global: accounts[0], - withdraw_authority: accounts[1], - mint: accounts[2], - bonding_curve: accounts[3], - associated_bonding_curve: accounts[4], - user: accounts[5], - system_program: accounts[6], - token_program: accounts[7], - pump_amm: accounts[8], - pool: accounts[9], - pool_authority: accounts[10], - pool_authority_mint_account: accounts[11], - pool_authority_wsol_account: accounts[12], - amm_global_config: accounts[13], - wsol_mint: accounts[14], - lp_mint: accounts[15], - user_pool_token_account: accounts[16], - pool_base_token_account: accounts[17], - pool_quote_token_account: accounts[18], - token_2022_program: accounts[19], - associated_token_program: accounts[20], - pump_amm_event_authority: accounts[21], - event_authority: accounts[22], - program: accounts[23], - ..Default::default() - })) +/// 解析创建代币日志事件 +fn parse_create_token_inner_instruction( + data: &[u8], + metadata: EventMetadata, +) -> Option> { + if let Some(event) = pumpfun_create_token_event_log_decode(data) { + Some(Box::new(PumpFunCreateTokenEvent { metadata, ..event })) + } else { + None } } -impl_event_parser_delegate!(PumpFunEventParser); +/// 解析交易事件 +fn parse_trade_inner_instruction( + data: &[u8], + metadata: EventMetadata, +) -> Option> { + if let Some(event) = pumpfun_trade_event_log_decode(data) { + Some(Box::new(PumpFunTradeEvent { metadata, ..event })) + } else { + None + } +} + +/// 解析创建代币指令事件 +fn parse_create_token_instruction( + data: &[u8], + accounts: &[Pubkey], + metadata: EventMetadata, +) -> Option> { + if data.len() < 16 || accounts.len() < 11 { + return None; + } + let mut offset = 0; + let name_len = u32::from_le_bytes(data[offset..offset + 4].try_into().unwrap()) as usize; + offset += 4; + let name = String::from_utf8_lossy(&data[offset..offset + name_len]); + offset += name_len; + let symbol_len = u32::from_le_bytes(data[offset..offset + 4].try_into().unwrap()) as usize; + offset += 4; + let symbol = String::from_utf8_lossy(&data[offset..offset + symbol_len]); + offset += symbol_len; + let uri_len = u32::from_le_bytes(data[offset..offset + 4].try_into().unwrap()) as usize; + offset += 4; + let uri = String::from_utf8_lossy(&data[offset..offset + uri_len]); + offset += uri_len; + let creator = if offset + 32 <= data.len() { + Pubkey::new_from_array(data[offset..offset + 32].try_into().ok()?) + } else { + Pubkey::default() + }; + + Some(Box::new(PumpFunCreateTokenEvent { + metadata, + name: name.to_string(), + symbol: symbol.to_string(), + uri: uri.to_string(), + creator, + mint: accounts[0], + mint_authority: accounts[1], + bonding_curve: accounts[2], + associated_bonding_curve: accounts[3], + user: accounts[7], + ..Default::default() + })) +} + +// 解析买入指令事件 +fn parse_buy_instruction( + data: &[u8], + accounts: &[Pubkey], + metadata: EventMetadata, +) -> Option> { + if data.len() < 16 || accounts.len() < 13 { + return None; + } + let amount = u64::from_le_bytes(data[0..8].try_into().unwrap()); + let max_sol_cost = u64::from_le_bytes(data[8..16].try_into().unwrap()); + Some(Box::new(PumpFunTradeEvent { + metadata, + global: accounts[0], + fee_recipient: accounts[1], + mint: accounts[2], + bonding_curve: accounts[3], + associated_bonding_curve: accounts[4], + associated_user: accounts[5], + user: accounts[6], + system_program: accounts[7], + token_program: accounts[8], + creator_vault: accounts[9], + event_authority: accounts[10], + program: accounts[11], + global_volume_accumulator: accounts[12], + user_volume_accumulator: accounts[13], + max_sol_cost, + amount, + is_buy: true, + ..Default::default() + })) +} + +// 解析卖出指令事件 +fn parse_sell_instruction( + data: &[u8], + accounts: &[Pubkey], + metadata: EventMetadata, +) -> Option> { + if data.len() < 16 || accounts.len() < 11 { + return None; + } + let amount = u64::from_le_bytes(data[0..8].try_into().unwrap()); + let min_sol_output = u64::from_le_bytes(data[8..16].try_into().unwrap()); + Some(Box::new(PumpFunTradeEvent { + metadata, + global: accounts[0], + fee_recipient: accounts[1], + mint: accounts[2], + bonding_curve: accounts[3], + associated_bonding_curve: accounts[4], + associated_user: accounts[5], + user: accounts[6], + system_program: accounts[7], + creator_vault: accounts[8], + token_program: accounts[9], + event_authority: accounts[10], + program: accounts[11], + global_volume_accumulator: *accounts.get(12).unwrap_or(&Pubkey::default()), + user_volume_accumulator: *accounts.get(13).unwrap_or(&Pubkey::default()), + min_sol_output, + amount, + is_buy: false, + ..Default::default() + })) +} + +/// 解析迁移指令事件 +fn parse_migrate_instruction( + _data: &[u8], + accounts: &[Pubkey], + metadata: EventMetadata, +) -> Option> { + if accounts.len() < 24 { + return None; + } + Some(Box::new(PumpFunMigrateEvent { + metadata, + global: accounts[0], + withdraw_authority: accounts[1], + mint: accounts[2], + bonding_curve: accounts[3], + associated_bonding_curve: accounts[4], + user: accounts[5], + system_program: accounts[6], + token_program: accounts[7], + pump_amm: accounts[8], + pool: accounts[9], + pool_authority: accounts[10], + pool_authority_mint_account: accounts[11], + pool_authority_wsol_account: accounts[12], + amm_global_config: accounts[13], + wsol_mint: accounts[14], + lp_mint: accounts[15], + user_pool_token_account: accounts[16], + pool_base_token_account: accounts[17], + pool_quote_token_account: accounts[18], + token_2022_program: accounts[19], + associated_token_program: accounts[20], + pump_amm_event_authority: accounts[21], + event_authority: accounts[22], + program: accounts[23], + ..Default::default() + })) +} diff --git a/src/streaming/event_parser/protocols/pumpswap/mod.rs b/src/streaming/event_parser/protocols/pumpswap/mod.rs index ee80dba..86156fa 100755 --- a/src/streaming/event_parser/protocols/pumpswap/mod.rs +++ b/src/streaming/event_parser/protocols/pumpswap/mod.rs @@ -3,4 +3,3 @@ pub mod parser; pub mod types; pub use events::*; -pub use parser::PumpSwapEventParser; diff --git a/src/streaming/event_parser/protocols/pumpswap/parser.rs b/src/streaming/event_parser/protocols/pumpswap/parser.rs index 529badf..9969008 100755 --- a/src/streaming/event_parser/protocols/pumpswap/parser.rs +++ b/src/streaming/event_parser/protocols/pumpswap/parser.rs @@ -1,327 +1,303 @@ -use solana_sdk::pubkey::Pubkey; - -use crate::{ - impl_event_parser_delegate, - streaming::event_parser::{ - common::{read_u64_le, EventMetadata, EventType, ProtocolType}, - core::traits::{GenericEventParseConfig, GenericEventParser, UnifiedEvent}, - 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, - }, +use crate::streaming::event_parser::{ + common::{read_u64_le, EventMetadata, EventType, ProtocolType}, + core::event_parser::GenericEventParseConfig, + 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, }, + UnifiedEvent, }; +use solana_sdk::pubkey::Pubkey; /// PumpSwap程序ID pub const PUMPSWAP_PROGRAM_ID: Pubkey = solana_sdk::pubkey!("pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA"); -/// PumpSwap事件解析器 -pub struct PumpSwapEventParser { - inner: GenericEventParser, -} +// 配置所有事件类型 +pub const CONFIGS: &[GenericEventParseConfig] = &[ + GenericEventParseConfig { + program_id: PUMPSWAP_PROGRAM_ID, + protocol_type: ProtocolType::PumpSwap, + inner_instruction_discriminator: discriminators::BUY_EVENT, + instruction_discriminator: discriminators::BUY_IX, + event_type: EventType::PumpSwapBuy, + inner_instruction_parser: Some(parse_buy_inner_instruction), + instruction_parser: Some(parse_buy_instruction), + requires_inner_instruction: false, + }, + GenericEventParseConfig { + program_id: PUMPSWAP_PROGRAM_ID, + protocol_type: ProtocolType::PumpSwap, + inner_instruction_discriminator: discriminators::SELL_EVENT, + instruction_discriminator: discriminators::SELL_IX, + event_type: EventType::PumpSwapSell, + inner_instruction_parser: Some(parse_sell_inner_instruction), + instruction_parser: Some(parse_sell_instruction), + requires_inner_instruction: false, + }, + GenericEventParseConfig { + program_id: PUMPSWAP_PROGRAM_ID, + protocol_type: ProtocolType::PumpSwap, + inner_instruction_discriminator: discriminators::CREATE_POOL_EVENT, + instruction_discriminator: discriminators::CREATE_POOL_IX, + event_type: EventType::PumpSwapCreatePool, + inner_instruction_parser: Some(parse_create_pool_inner_instruction), + instruction_parser: Some(parse_create_pool_instruction), + requires_inner_instruction: false, + }, + GenericEventParseConfig { + program_id: PUMPSWAP_PROGRAM_ID, + protocol_type: ProtocolType::PumpSwap, + inner_instruction_discriminator: discriminators::DEPOSIT_EVENT, + instruction_discriminator: discriminators::DEPOSIT_IX, + event_type: EventType::PumpSwapDeposit, + inner_instruction_parser: Some(parse_deposit_inner_instruction), + instruction_parser: Some(parse_deposit_instruction), + requires_inner_instruction: false, + }, + GenericEventParseConfig { + program_id: PUMPSWAP_PROGRAM_ID, + protocol_type: ProtocolType::PumpSwap, + inner_instruction_discriminator: discriminators::WITHDRAW_EVENT, + instruction_discriminator: discriminators::WITHDRAW_IX, + event_type: EventType::PumpSwapWithdraw, + inner_instruction_parser: Some(parse_withdraw_inner_instruction), + instruction_parser: Some(parse_withdraw_instruction), + requires_inner_instruction: false, + }, +]; -impl Default for PumpSwapEventParser { - fn default() -> Self { - Self::new() +/// 解析买入日志事件 +fn parse_buy_inner_instruction( + data: &[u8], + metadata: EventMetadata, +) -> Option> { + if let Some(event) = pump_swap_buy_event_log_decode(data) { + Some(Box::new(PumpSwapBuyEvent { metadata, ..event })) + } else { + None } } -impl PumpSwapEventParser { - pub fn new() -> Self { - // 配置所有事件类型 - let configs = vec![ - GenericEventParseConfig { - program_id: PUMPSWAP_PROGRAM_ID, - protocol_type: ProtocolType::PumpSwap, - inner_instruction_discriminator: discriminators::BUY_EVENT, - instruction_discriminator: discriminators::BUY_IX, - event_type: EventType::PumpSwapBuy, - inner_instruction_parser: Some(Self::parse_buy_inner_instruction), - instruction_parser: Some(Self::parse_buy_instruction), - requires_inner_instruction: false, - }, - GenericEventParseConfig { - program_id: PUMPSWAP_PROGRAM_ID, - protocol_type: ProtocolType::PumpSwap, - inner_instruction_discriminator: discriminators::SELL_EVENT, - instruction_discriminator: discriminators::SELL_IX, - event_type: EventType::PumpSwapSell, - inner_instruction_parser: Some(Self::parse_sell_inner_instruction), - instruction_parser: Some(Self::parse_sell_instruction), - requires_inner_instruction: false, - }, - GenericEventParseConfig { - program_id: PUMPSWAP_PROGRAM_ID, - protocol_type: ProtocolType::PumpSwap, - inner_instruction_discriminator: discriminators::CREATE_POOL_EVENT, - instruction_discriminator: discriminators::CREATE_POOL_IX, - event_type: EventType::PumpSwapCreatePool, - inner_instruction_parser: Some(Self::parse_create_pool_inner_instruction), - instruction_parser: Some(Self::parse_create_pool_instruction), - requires_inner_instruction: false, - }, - GenericEventParseConfig { - program_id: PUMPSWAP_PROGRAM_ID, - protocol_type: ProtocolType::PumpSwap, - inner_instruction_discriminator: discriminators::DEPOSIT_EVENT, - instruction_discriminator: discriminators::DEPOSIT_IX, - event_type: EventType::PumpSwapDeposit, - inner_instruction_parser: Some(Self::parse_deposit_inner_instruction), - instruction_parser: Some(Self::parse_deposit_instruction), - requires_inner_instruction: false, - }, - GenericEventParseConfig { - program_id: PUMPSWAP_PROGRAM_ID, - protocol_type: ProtocolType::PumpSwap, - inner_instruction_discriminator: discriminators::WITHDRAW_EVENT, - instruction_discriminator: discriminators::WITHDRAW_IX, - event_type: EventType::PumpSwapWithdraw, - inner_instruction_parser: Some(Self::parse_withdraw_inner_instruction), - instruction_parser: Some(Self::parse_withdraw_instruction), - requires_inner_instruction: false, - }, - ]; - - let inner = GenericEventParser::new(vec![PUMPSWAP_PROGRAM_ID], configs); - - Self { inner } - } - - /// 解析买入日志事件 - fn parse_buy_inner_instruction( - data: &[u8], - metadata: EventMetadata, - ) -> Option> { - if let Some(event) = pump_swap_buy_event_log_decode(data) { - Some(Box::new(PumpSwapBuyEvent { metadata, ..event })) - } else { - None - } - } - - /// 解析卖出日志事件 - fn parse_sell_inner_instruction( - data: &[u8], - metadata: EventMetadata, - ) -> Option> { - if let Some(event) = pump_swap_sell_event_log_decode(data) { - Some(Box::new(PumpSwapSellEvent { metadata, ..event })) - } else { - None - } - } - - /// 解析创建池子日志事件 - fn parse_create_pool_inner_instruction( - data: &[u8], - metadata: EventMetadata, - ) -> Option> { - if let Some(event) = pump_swap_create_pool_event_log_decode(data) { - Some(Box::new(PumpSwapCreatePoolEvent { metadata, ..event })) - } else { - None - } - } - - /// 解析存款日志事件 - fn parse_deposit_inner_instruction( - data: &[u8], - metadata: EventMetadata, - ) -> Option> { - if let Some(event) = pump_swap_deposit_event_log_decode(data) { - Some(Box::new(PumpSwapDepositEvent { metadata, ..event })) - } else { - None - } - } - - /// 解析提款日志事件 - fn parse_withdraw_inner_instruction( - data: &[u8], - metadata: EventMetadata, - ) -> Option> { - if let Some(event) = pump_swap_withdraw_event_log_decode(data) { - Some(Box::new(PumpSwapWithdrawEvent { metadata, ..event })) - } else { - None - } - } - - /// 解析买入指令事件 - fn parse_buy_instruction( - data: &[u8], - accounts: &[Pubkey], - metadata: EventMetadata, - ) -> Option> { - if data.len() < 16 || accounts.len() < 11 { - return None; - } - - let base_amount_out = read_u64_le(data, 0)?; - let max_quote_amount_in = read_u64_le(data, 8)?; - - Some(Box::new(PumpSwapBuyEvent { - metadata, - base_amount_out, - max_quote_amount_in, - 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() - })) - } - - /// 解析卖出指令事件 - fn parse_sell_instruction( - data: &[u8], - accounts: &[Pubkey], - metadata: EventMetadata, - ) -> Option> { - if data.len() < 16 || accounts.len() < 11 { - return None; - } - - let base_amount_in = read_u64_le(data, 0)?; - let min_quote_amount_out = read_u64_le(data, 8)?; - - Some(Box::new(PumpSwapSellEvent { - metadata, - base_amount_in, - min_quote_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() - })) - } - - /// 解析创建池子指令事件 - fn parse_create_pool_instruction( - data: &[u8], - accounts: &[Pubkey], - metadata: EventMetadata, - ) -> Option> { - if data.len() < 18 || accounts.len() < 11 { - return None; - } - - let index = u16::from_le_bytes(data[0..2].try_into().ok()?); - let base_amount_in = u64::from_le_bytes(data[2..10].try_into().ok()?); - let quote_amount_in = u64::from_le_bytes(data[10..18].try_into().ok()?); - let coin_creator = if data.len() >= 50 { - Pubkey::new_from_array(data[18..50].try_into().ok()?) - } else { - Pubkey::default() - }; - - Some(Box::new(PumpSwapCreatePoolEvent { - metadata, - index, - base_amount_in, - quote_amount_in, - pool: accounts[0], - creator: accounts[2], - base_mint: accounts[3], - quote_mint: accounts[4], - lp_mint: accounts[5], - user_base_token_account: accounts[6], - user_quote_token_account: accounts[7], - user_pool_token_account: accounts[8], - pool_base_token_account: accounts[9], - pool_quote_token_account: accounts[10], - coin_creator, - ..Default::default() - })) - } - - /// 解析存款指令事件 - fn parse_deposit_instruction( - data: &[u8], - accounts: &[Pubkey], - metadata: EventMetadata, - ) -> Option> { - if data.len() < 24 || accounts.len() < 11 { - return None; - } - - let lp_token_amount_out = u64::from_le_bytes(data[0..8].try_into().ok()?); - let max_base_amount_in = u64::from_le_bytes(data[8..16].try_into().ok()?); - let max_quote_amount_in = u64::from_le_bytes(data[16..24].try_into().ok()?); - - Some(Box::new(PumpSwapDepositEvent { - metadata, - lp_token_amount_out, - max_base_amount_in, - max_quote_amount_in, - pool: accounts[0], - user: accounts[2], - base_mint: accounts[3], - quote_mint: accounts[4], - user_base_token_account: accounts[6], - user_quote_token_account: accounts[7], - user_pool_token_account: accounts[8], - pool_base_token_account: accounts[9], - pool_quote_token_account: accounts[10], - ..Default::default() - })) - } - - /// 解析提款指令事件 - fn parse_withdraw_instruction( - data: &[u8], - accounts: &[Pubkey], - metadata: EventMetadata, - ) -> Option> { - if data.len() < 24 || accounts.len() < 11 { - return None; - } - - let lp_token_amount_in = u64::from_le_bytes(data[0..8].try_into().ok()?); - let min_base_amount_out = u64::from_le_bytes(data[8..16].try_into().ok()?); - let min_quote_amount_out = u64::from_le_bytes(data[16..24].try_into().ok()?); - - Some(Box::new(PumpSwapWithdrawEvent { - metadata, - lp_token_amount_in, - min_base_amount_out, - min_quote_amount_out, - pool: accounts[0], - user: accounts[2], - base_mint: accounts[3], - quote_mint: accounts[4], - user_base_token_account: accounts[6], - user_quote_token_account: accounts[7], - user_pool_token_account: accounts[8], - pool_base_token_account: accounts[9], - pool_quote_token_account: accounts[10], - ..Default::default() - })) +/// 解析卖出日志事件 +fn parse_sell_inner_instruction( + data: &[u8], + metadata: EventMetadata, +) -> Option> { + if let Some(event) = pump_swap_sell_event_log_decode(data) { + Some(Box::new(PumpSwapSellEvent { metadata, ..event })) + } else { + None } } -impl_event_parser_delegate!(PumpSwapEventParser); +/// 解析创建池子日志事件 +fn parse_create_pool_inner_instruction( + data: &[u8], + metadata: EventMetadata, +) -> Option> { + if let Some(event) = pump_swap_create_pool_event_log_decode(data) { + Some(Box::new(PumpSwapCreatePoolEvent { metadata, ..event })) + } else { + None + } +} + +/// 解析存款日志事件 +fn parse_deposit_inner_instruction( + data: &[u8], + metadata: EventMetadata, +) -> Option> { + if let Some(event) = pump_swap_deposit_event_log_decode(data) { + Some(Box::new(PumpSwapDepositEvent { metadata, ..event })) + } else { + None + } +} + +/// 解析提款日志事件 +fn parse_withdraw_inner_instruction( + data: &[u8], + metadata: EventMetadata, +) -> Option> { + if let Some(event) = pump_swap_withdraw_event_log_decode(data) { + Some(Box::new(PumpSwapWithdrawEvent { metadata, ..event })) + } else { + None + } +} + +/// 解析买入指令事件 +fn parse_buy_instruction( + data: &[u8], + accounts: &[Pubkey], + metadata: EventMetadata, +) -> Option> { + if data.len() < 16 || accounts.len() < 11 { + return None; + } + + let base_amount_out = read_u64_le(data, 0)?; + let max_quote_amount_in = read_u64_le(data, 8)?; + + Some(Box::new(PumpSwapBuyEvent { + metadata, + base_amount_out, + max_quote_amount_in, + 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() + })) +} + +/// 解析卖出指令事件 +fn parse_sell_instruction( + data: &[u8], + accounts: &[Pubkey], + metadata: EventMetadata, +) -> Option> { + if data.len() < 16 || accounts.len() < 11 { + return None; + } + + let base_amount_in = read_u64_le(data, 0)?; + let min_quote_amount_out = read_u64_le(data, 8)?; + + Some(Box::new(PumpSwapSellEvent { + metadata, + base_amount_in, + min_quote_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() + })) +} + +/// 解析创建池子指令事件 +fn parse_create_pool_instruction( + data: &[u8], + accounts: &[Pubkey], + metadata: EventMetadata, +) -> Option> { + if data.len() < 18 || accounts.len() < 11 { + return None; + } + + let index = u16::from_le_bytes(data[0..2].try_into().ok()?); + let base_amount_in = u64::from_le_bytes(data[2..10].try_into().ok()?); + let quote_amount_in = u64::from_le_bytes(data[10..18].try_into().ok()?); + let coin_creator = if data.len() >= 50 { + Pubkey::new_from_array(data[18..50].try_into().ok()?) + } else { + Pubkey::default() + }; + + Some(Box::new(PumpSwapCreatePoolEvent { + metadata, + index, + base_amount_in, + quote_amount_in, + pool: accounts[0], + creator: accounts[2], + base_mint: accounts[3], + quote_mint: accounts[4], + lp_mint: accounts[5], + user_base_token_account: accounts[6], + user_quote_token_account: accounts[7], + user_pool_token_account: accounts[8], + pool_base_token_account: accounts[9], + pool_quote_token_account: accounts[10], + coin_creator, + ..Default::default() + })) +} + +/// 解析存款指令事件 +fn parse_deposit_instruction( + data: &[u8], + accounts: &[Pubkey], + metadata: EventMetadata, +) -> Option> { + if data.len() < 24 || accounts.len() < 11 { + return None; + } + + let lp_token_amount_out = u64::from_le_bytes(data[0..8].try_into().ok()?); + let max_base_amount_in = u64::from_le_bytes(data[8..16].try_into().ok()?); + let max_quote_amount_in = u64::from_le_bytes(data[16..24].try_into().ok()?); + + Some(Box::new(PumpSwapDepositEvent { + metadata, + lp_token_amount_out, + max_base_amount_in, + max_quote_amount_in, + pool: accounts[0], + user: accounts[2], + base_mint: accounts[3], + quote_mint: accounts[4], + user_base_token_account: accounts[6], + user_quote_token_account: accounts[7], + user_pool_token_account: accounts[8], + pool_base_token_account: accounts[9], + pool_quote_token_account: accounts[10], + ..Default::default() + })) +} + +/// 解析提款指令事件 +fn parse_withdraw_instruction( + data: &[u8], + accounts: &[Pubkey], + metadata: EventMetadata, +) -> Option> { + if data.len() < 24 || accounts.len() < 11 { + return None; + } + + let lp_token_amount_in = u64::from_le_bytes(data[0..8].try_into().ok()?); + let min_base_amount_out = u64::from_le_bytes(data[8..16].try_into().ok()?); + let min_quote_amount_out = u64::from_le_bytes(data[16..24].try_into().ok()?); + + Some(Box::new(PumpSwapWithdrawEvent { + metadata, + lp_token_amount_in, + min_base_amount_out, + min_quote_amount_out, + pool: accounts[0], + user: accounts[2], + base_mint: accounts[3], + quote_mint: accounts[4], + user_base_token_account: accounts[6], + user_quote_token_account: accounts[7], + user_pool_token_account: accounts[8], + pool_base_token_account: accounts[9], + pool_quote_token_account: accounts[10], + ..Default::default() + })) +} diff --git a/src/streaming/event_parser/protocols/raydium_amm_v4/mod.rs b/src/streaming/event_parser/protocols/raydium_amm_v4/mod.rs index b8e0ce5..86156fa 100755 --- a/src/streaming/event_parser/protocols/raydium_amm_v4/mod.rs +++ b/src/streaming/event_parser/protocols/raydium_amm_v4/mod.rs @@ -3,4 +3,3 @@ pub mod parser; pub mod types; pub use events::*; -pub use parser::RaydiumAmmV4EventParser; diff --git a/src/streaming/event_parser/protocols/raydium_amm_v4/parser.rs b/src/streaming/event_parser/protocols/raydium_amm_v4/parser.rs index 07ece61..832e6d8 100755 --- a/src/streaming/event_parser/protocols/raydium_amm_v4/parser.rs +++ b/src/streaming/event_parser/protocols/raydium_amm_v4/parser.rs @@ -1,349 +1,325 @@ -use solana_sdk::pubkey::Pubkey; - -use crate::{ - impl_event_parser_delegate, - streaming::event_parser::{ - common::{read_u64_le, EventMetadata, EventType, ProtocolType}, - core::traits::{GenericEventParseConfig, GenericEventParser, UnifiedEvent}, - protocols::raydium_amm_v4::{ - discriminators, RaydiumAmmV4DepositEvent, RaydiumAmmV4Initialize2Event, - RaydiumAmmV4SwapEvent, RaydiumAmmV4WithdrawEvent, RaydiumAmmV4WithdrawPnlEvent, - }, +use crate::streaming::event_parser::{ + common::{read_u64_le, EventMetadata, EventType, ProtocolType}, + core::event_parser::GenericEventParseConfig, + protocols::raydium_amm_v4::{ + discriminators, RaydiumAmmV4DepositEvent, RaydiumAmmV4Initialize2Event, + RaydiumAmmV4SwapEvent, RaydiumAmmV4WithdrawEvent, RaydiumAmmV4WithdrawPnlEvent, }, + UnifiedEvent, }; +use solana_sdk::pubkey::Pubkey; /// Raydium CPMM程序ID pub const RAYDIUM_AMM_V4_PROGRAM_ID: Pubkey = solana_sdk::pubkey!("675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8"); -/// Raydium CPMM事件解析器 -pub struct RaydiumAmmV4EventParser { - inner: GenericEventParser, +// 配置所有事件类型 +pub const CONFIGS: &[GenericEventParseConfig] = &[ + GenericEventParseConfig { + program_id: RAYDIUM_AMM_V4_PROGRAM_ID, + protocol_type: ProtocolType::RaydiumAmmV4, + inner_instruction_discriminator: &[], + instruction_discriminator: discriminators::SWAP_BASE_IN, + event_type: EventType::RaydiumAmmV4SwapBaseIn, + inner_instruction_parser: None, + instruction_parser: Some(parse_swap_base_input_instruction), + requires_inner_instruction: false, + }, + GenericEventParseConfig { + program_id: RAYDIUM_AMM_V4_PROGRAM_ID, + protocol_type: ProtocolType::RaydiumAmmV4, + inner_instruction_discriminator: &[], + instruction_discriminator: discriminators::SWAP_BASE_OUT, + event_type: EventType::RaydiumAmmV4SwapBaseOut, + inner_instruction_parser: None, + instruction_parser: Some(parse_swap_base_output_instruction), + requires_inner_instruction: false, + }, + GenericEventParseConfig { + program_id: RAYDIUM_AMM_V4_PROGRAM_ID, + protocol_type: ProtocolType::RaydiumAmmV4, + inner_instruction_discriminator: &[], + instruction_discriminator: discriminators::DEPOSIT, + event_type: EventType::RaydiumAmmV4Deposit, + inner_instruction_parser: None, + instruction_parser: Some(parse_deposit_instruction), + requires_inner_instruction: false, + }, + GenericEventParseConfig { + program_id: RAYDIUM_AMM_V4_PROGRAM_ID, + protocol_type: ProtocolType::RaydiumAmmV4, + inner_instruction_discriminator: &[], + instruction_discriminator: discriminators::INITIALIZE2, + event_type: EventType::RaydiumAmmV4Initialize2, + inner_instruction_parser: None, + instruction_parser: Some(parse_initialize2_instruction), + requires_inner_instruction: false, + }, + GenericEventParseConfig { + program_id: RAYDIUM_AMM_V4_PROGRAM_ID, + protocol_type: ProtocolType::RaydiumAmmV4, + inner_instruction_discriminator: &[], + instruction_discriminator: discriminators::WITHDRAW, + event_type: EventType::RaydiumAmmV4Withdraw, + inner_instruction_parser: None, + instruction_parser: Some(parse_withdraw_instruction), + requires_inner_instruction: false, + }, + GenericEventParseConfig { + program_id: RAYDIUM_AMM_V4_PROGRAM_ID, + protocol_type: ProtocolType::RaydiumAmmV4, + inner_instruction_discriminator: &[], + instruction_discriminator: discriminators::WITHDRAW_PNL, + event_type: EventType::RaydiumAmmV4WithdrawPnl, + inner_instruction_parser: None, + instruction_parser: Some(parse_withdraw_pnl_instruction), + requires_inner_instruction: false, + }, +]; + +/// 解析提现指令事件 +fn parse_withdraw_pnl_instruction( + _data: &[u8], + accounts: &[Pubkey], + metadata: EventMetadata, +) -> Option> { + if accounts.len() < 17 { + return None; + } + + Some(Box::new(RaydiumAmmV4WithdrawPnlEvent { + metadata, + token_program: accounts[0], + amm: accounts[1], + amm_config: accounts[2], + amm_authority: accounts[3], + amm_open_orders: accounts[4], + pool_coin_token_account: accounts[5], + pool_pc_token_account: accounts[6], + coin_pnl_token_account: accounts[7], + pc_pnl_token_account: accounts[8], + pnl_owner_account: accounts[9], + amm_target_orders: accounts[10], + serum_program: accounts[11], + serum_market: accounts[12], + serum_event_queue: accounts[13], + serum_coin_vault_account: accounts[14], + serum_pc_vault_account: accounts[15], + serum_vault_signer: accounts[16], + })) } -impl Default for RaydiumAmmV4EventParser { - fn default() -> Self { - Self::new() +/// 解析移除流动性指令事件 +fn parse_withdraw_instruction( + data: &[u8], + accounts: &[Pubkey], + metadata: EventMetadata, +) -> Option> { + if data.len() < 8 || accounts.len() < 22 { + return None; } + let amount = read_u64_le(data, 0)?; + + Some(Box::new(RaydiumAmmV4WithdrawEvent { + metadata, + amount, + + token_program: accounts[0], + amm: accounts[1], + amm_authority: accounts[2], + amm_open_orders: accounts[3], + amm_target_orders: accounts[4], + lp_mint_address: accounts[5], + pool_coin_token_account: accounts[6], + pool_pc_token_account: accounts[7], + pool_withdraw_queue: accounts[8], + pool_temp_lp_token_account: accounts[9], + serum_program: accounts[10], + serum_market: accounts[11], + serum_coin_vault_account: accounts[12], + serum_pc_vault_account: accounts[13], + serum_vault_signer: accounts[14], + user_lp_token_account: accounts[15], + user_coin_token_account: accounts[16], + user_pc_token_account: accounts[17], + user_owner: accounts[18], + serum_event_queue: accounts[19], + serum_bids: accounts[20], + serum_asks: accounts[21], + })) } -impl RaydiumAmmV4EventParser { - pub fn new() -> Self { - // 配置所有事件类型 - let configs = vec![ - GenericEventParseConfig { - program_id: RAYDIUM_AMM_V4_PROGRAM_ID, - protocol_type: ProtocolType::RaydiumAmmV4, - inner_instruction_discriminator: &[], - instruction_discriminator: discriminators::SWAP_BASE_IN, - event_type: EventType::RaydiumAmmV4SwapBaseIn, - inner_instruction_parser: None, - instruction_parser: Some(Self::parse_swap_base_input_instruction), - requires_inner_instruction: false, - }, - GenericEventParseConfig { - program_id: RAYDIUM_AMM_V4_PROGRAM_ID, - protocol_type: ProtocolType::RaydiumAmmV4, - inner_instruction_discriminator: &[], - instruction_discriminator: discriminators::SWAP_BASE_OUT, - event_type: EventType::RaydiumAmmV4SwapBaseOut, - inner_instruction_parser: None, - instruction_parser: Some(Self::parse_swap_base_output_instruction), - requires_inner_instruction: false, - }, - GenericEventParseConfig { - program_id: RAYDIUM_AMM_V4_PROGRAM_ID, - protocol_type: ProtocolType::RaydiumAmmV4, - inner_instruction_discriminator: &[], - instruction_discriminator: discriminators::DEPOSIT, - event_type: EventType::RaydiumAmmV4Deposit, - inner_instruction_parser: None, - instruction_parser: Some(Self::parse_deposit_instruction), - requires_inner_instruction: false, - }, - GenericEventParseConfig { - program_id: RAYDIUM_AMM_V4_PROGRAM_ID, - protocol_type: ProtocolType::RaydiumAmmV4, - inner_instruction_discriminator: &[], - instruction_discriminator: discriminators::INITIALIZE2, - event_type: EventType::RaydiumAmmV4Initialize2, - inner_instruction_parser: None, - instruction_parser: Some(Self::parse_initialize2_instruction), - requires_inner_instruction: false, - }, - GenericEventParseConfig { - program_id: RAYDIUM_AMM_V4_PROGRAM_ID, - protocol_type: ProtocolType::RaydiumAmmV4, - inner_instruction_discriminator: &[], - instruction_discriminator: discriminators::WITHDRAW, - event_type: EventType::RaydiumAmmV4Withdraw, - inner_instruction_parser: None, - instruction_parser: Some(Self::parse_withdraw_instruction), - requires_inner_instruction: false, - }, - GenericEventParseConfig { - program_id: RAYDIUM_AMM_V4_PROGRAM_ID, - protocol_type: ProtocolType::RaydiumAmmV4, - inner_instruction_discriminator: &[], - instruction_discriminator: discriminators::WITHDRAW_PNL, - event_type: EventType::RaydiumAmmV4WithdrawPnl, - inner_instruction_parser: None, - instruction_parser: Some(Self::parse_withdraw_pnl_instruction), - requires_inner_instruction: false, - }, - ]; - - let inner = GenericEventParser::new(vec![RAYDIUM_AMM_V4_PROGRAM_ID], configs); - - Self { inner } +/// 解析初始化指令事件 +fn parse_initialize2_instruction( + data: &[u8], + accounts: &[Pubkey], + metadata: EventMetadata, +) -> Option> { + if data.len() < 25 || accounts.len() < 21 { + return None; } + let nonce = data[0]; + let open_time = read_u64_le(data, 1)?; + let init_pc_amount = read_u64_le(data, 9)?; + let init_coin_amount = read_u64_le(data, 17)?; - /// 解析提现指令事件 - fn parse_withdraw_pnl_instruction( - _data: &[u8], - accounts: &[Pubkey], - metadata: EventMetadata, - ) -> Option> { - if accounts.len() < 17 { - return None; - } + Some(Box::new(RaydiumAmmV4Initialize2Event { + metadata, + nonce, + open_time, + init_pc_amount, + init_coin_amount, - Some(Box::new(RaydiumAmmV4WithdrawPnlEvent { - metadata, - token_program: accounts[0], - amm: accounts[1], - amm_config: accounts[2], - amm_authority: accounts[3], - amm_open_orders: accounts[4], - pool_coin_token_account: accounts[5], - pool_pc_token_account: accounts[6], - coin_pnl_token_account: accounts[7], - pc_pnl_token_account: accounts[8], - pnl_owner_account: accounts[9], - amm_target_orders: accounts[10], - serum_program: accounts[11], - serum_market: accounts[12], - serum_event_queue: accounts[13], - serum_coin_vault_account: accounts[14], - serum_pc_vault_account: accounts[15], - serum_vault_signer: accounts[16], - })) - } - - /// 解析移除流动性指令事件 - fn parse_withdraw_instruction( - data: &[u8], - accounts: &[Pubkey], - metadata: EventMetadata, - ) -> Option> { - if data.len() < 8 || accounts.len() < 22 { - return None; - } - let amount = read_u64_le(data, 0)?; - - Some(Box::new(RaydiumAmmV4WithdrawEvent { - metadata, - amount, - - token_program: accounts[0], - amm: accounts[1], - amm_authority: accounts[2], - amm_open_orders: accounts[3], - amm_target_orders: accounts[4], - lp_mint_address: accounts[5], - pool_coin_token_account: accounts[6], - pool_pc_token_account: accounts[7], - pool_withdraw_queue: accounts[8], - pool_temp_lp_token_account: accounts[9], - serum_program: accounts[10], - serum_market: accounts[11], - serum_coin_vault_account: accounts[12], - serum_pc_vault_account: accounts[13], - serum_vault_signer: accounts[14], - user_lp_token_account: accounts[15], - user_coin_token_account: accounts[16], - user_pc_token_account: accounts[17], - user_owner: accounts[18], - serum_event_queue: accounts[19], - serum_bids: accounts[20], - serum_asks: accounts[21], - })) - } - - /// 解析初始化指令事件 - fn parse_initialize2_instruction( - data: &[u8], - accounts: &[Pubkey], - metadata: EventMetadata, - ) -> Option> { - if data.len() < 25 || accounts.len() < 21 { - return None; - } - let nonce = data[0]; - let open_time = read_u64_le(data, 1)?; - let init_pc_amount = read_u64_le(data, 9)?; - let init_coin_amount = read_u64_le(data, 17)?; - - Some(Box::new(RaydiumAmmV4Initialize2Event { - metadata, - nonce, - open_time, - init_pc_amount, - init_coin_amount, - - token_program: accounts[0], - spl_associated_token_account: accounts[1], - system_program: accounts[2], - rent: accounts[3], - amm: accounts[4], - amm_authority: accounts[5], - amm_open_orders: accounts[6], - lp_mint: accounts[7], - coin_mint: accounts[8], - pc_mint: accounts[9], - pool_coin_token_account: accounts[10], - pool_pc_token_account: accounts[11], - pool_withdraw_queue: accounts[12], - amm_target_orders: accounts[13], - pool_temp_lp: accounts[14], - serum_program: accounts[15], - serum_market: accounts[16], - user_wallet: accounts[17], - user_token_coin: accounts[18], - user_token_pc: accounts[19], - user_lp_token_account: accounts[20], - })) - } - - /// 解析添加流动性指令事件 - fn parse_deposit_instruction( - data: &[u8], - accounts: &[Pubkey], - metadata: EventMetadata, - ) -> Option> { - if data.len() < 24 || accounts.len() < 14 { - return None; - } - let max_coin_amount = read_u64_le(data, 0)?; - let max_pc_amount = read_u64_le(data, 8)?; - let base_side = read_u64_le(data, 16)?; - - Some(Box::new(RaydiumAmmV4DepositEvent { - metadata, - max_coin_amount, - max_pc_amount, - base_side, - - token_program: accounts[0], - amm: accounts[1], - amm_authority: accounts[2], - amm_open_orders: accounts[3], - amm_target_orders: accounts[4], - lp_mint_address: accounts[5], - pool_coin_token_account: accounts[6], - pool_pc_token_account: accounts[7], - serum_market: accounts[8], - user_coin_token_account: accounts[9], - user_pc_token_account: accounts[10], - user_lp_token_account: accounts[11], - user_owner: accounts[12], - serum_event_queue: accounts[13], - })) - } - - /// 解析买入指令事件 - fn parse_swap_base_output_instruction( - data: &[u8], - accounts: &[Pubkey], - metadata: EventMetadata, - ) -> Option> { - if data.len() < 16 || accounts.len() < 17 { - return None; - } - let max_amount_in = read_u64_le(data, 0)?; - let amount_out = read_u64_le(data, 8)?; - - let mut accounts = accounts.to_vec(); - if accounts.len() == 17 { - // 添加一个默认的 Pubkey 作为 amm_target_orders 的占位符 - // 因为在某些情况下,amm_target_orders 可能是可选的 - accounts.insert(4, Pubkey::default()); - } - - Some(Box::new(RaydiumAmmV4SwapEvent { - metadata, - max_amount_in, - amount_out, - - token_program: accounts[0], - amm: accounts[1], - amm_authority: accounts[2], - amm_open_orders: accounts[3], - amm_target_orders: Some(accounts[4]), - pool_coin_token_account: accounts[5], - pool_pc_token_account: accounts[6], - serum_program: accounts[7], - serum_market: accounts[8], - serum_bids: accounts[9], - serum_asks: accounts[10], - serum_event_queue: accounts[11], - serum_coin_vault_account: accounts[12], - serum_pc_vault_account: accounts[13], - serum_vault_signer: accounts[14], - user_source_token_account: accounts[15], - user_destination_token_account: accounts[16], - user_source_owner: accounts[17], - - ..Default::default() - })) - } - - /// 解析买入指令事件 - fn parse_swap_base_input_instruction( - data: &[u8], - accounts: &[Pubkey], - metadata: EventMetadata, - ) -> Option> { - if data.len() < 16 || accounts.len() < 17 { - return None; - } - let amount_in = read_u64_le(data, 0)?; - let minimum_amount_out = read_u64_le(data, 8)?; - - let mut accounts = accounts.to_vec(); - if accounts.len() == 17 { - // 添加一个默认的 Pubkey 作为 amm_target_orders 的占位符 - // 因为在某些情况下,amm_target_orders 可能是可选的 - accounts.insert(4, Pubkey::default()); - } - - Some(Box::new(RaydiumAmmV4SwapEvent { - metadata, - amount_in, - minimum_amount_out, - - token_program: accounts[0], - amm: accounts[1], - amm_authority: accounts[2], - amm_open_orders: accounts[3], - amm_target_orders: Some(accounts[4]), - pool_coin_token_account: accounts[5], - pool_pc_token_account: accounts[6], - serum_program: accounts[7], - serum_market: accounts[8], - serum_bids: accounts[9], - serum_asks: accounts[10], - serum_event_queue: accounts[11], - serum_coin_vault_account: accounts[12], - serum_pc_vault_account: accounts[13], - serum_vault_signer: accounts[14], - user_source_token_account: accounts[15], - user_destination_token_account: accounts[16], - user_source_owner: accounts[17], - - ..Default::default() - })) - } + token_program: accounts[0], + spl_associated_token_account: accounts[1], + system_program: accounts[2], + rent: accounts[3], + amm: accounts[4], + amm_authority: accounts[5], + amm_open_orders: accounts[6], + lp_mint: accounts[7], + coin_mint: accounts[8], + pc_mint: accounts[9], + pool_coin_token_account: accounts[10], + pool_pc_token_account: accounts[11], + pool_withdraw_queue: accounts[12], + amm_target_orders: accounts[13], + pool_temp_lp: accounts[14], + serum_program: accounts[15], + serum_market: accounts[16], + user_wallet: accounts[17], + user_token_coin: accounts[18], + user_token_pc: accounts[19], + user_lp_token_account: accounts[20], + })) } -impl_event_parser_delegate!(RaydiumAmmV4EventParser); +/// 解析添加流动性指令事件 +fn parse_deposit_instruction( + data: &[u8], + accounts: &[Pubkey], + metadata: EventMetadata, +) -> Option> { + if data.len() < 24 || accounts.len() < 14 { + return None; + } + let max_coin_amount = read_u64_le(data, 0)?; + let max_pc_amount = read_u64_le(data, 8)?; + let base_side = read_u64_le(data, 16)?; + + Some(Box::new(RaydiumAmmV4DepositEvent { + metadata, + max_coin_amount, + max_pc_amount, + base_side, + + token_program: accounts[0], + amm: accounts[1], + amm_authority: accounts[2], + amm_open_orders: accounts[3], + amm_target_orders: accounts[4], + lp_mint_address: accounts[5], + pool_coin_token_account: accounts[6], + pool_pc_token_account: accounts[7], + serum_market: accounts[8], + user_coin_token_account: accounts[9], + user_pc_token_account: accounts[10], + user_lp_token_account: accounts[11], + user_owner: accounts[12], + serum_event_queue: accounts[13], + })) +} + +/// 解析买入指令事件 +fn parse_swap_base_output_instruction( + data: &[u8], + accounts: &[Pubkey], + metadata: EventMetadata, +) -> Option> { + if data.len() < 16 || accounts.len() < 17 { + return None; + } + let max_amount_in = read_u64_le(data, 0)?; + let amount_out = read_u64_le(data, 8)?; + + let mut accounts = accounts.to_vec(); + if accounts.len() == 17 { + // 添加一个默认的 Pubkey 作为 amm_target_orders 的占位符 + // 因为在某些情况下,amm_target_orders 可能是可选的 + accounts.insert(4, Pubkey::default()); + } + + Some(Box::new(RaydiumAmmV4SwapEvent { + metadata, + max_amount_in, + amount_out, + + token_program: accounts[0], + amm: accounts[1], + amm_authority: accounts[2], + amm_open_orders: accounts[3], + amm_target_orders: Some(accounts[4]), + pool_coin_token_account: accounts[5], + pool_pc_token_account: accounts[6], + serum_program: accounts[7], + serum_market: accounts[8], + serum_bids: accounts[9], + serum_asks: accounts[10], + serum_event_queue: accounts[11], + serum_coin_vault_account: accounts[12], + serum_pc_vault_account: accounts[13], + serum_vault_signer: accounts[14], + user_source_token_account: accounts[15], + user_destination_token_account: accounts[16], + user_source_owner: accounts[17], + + ..Default::default() + })) +} + +/// 解析买入指令事件 +fn parse_swap_base_input_instruction( + data: &[u8], + accounts: &[Pubkey], + metadata: EventMetadata, +) -> Option> { + if data.len() < 16 || accounts.len() < 17 { + return None; + } + let amount_in = read_u64_le(data, 0)?; + let minimum_amount_out = read_u64_le(data, 8)?; + + let mut accounts = accounts.to_vec(); + if accounts.len() == 17 { + // 添加一个默认的 Pubkey 作为 amm_target_orders 的占位符 + // 因为在某些情况下,amm_target_orders 可能是可选的 + accounts.insert(4, Pubkey::default()); + } + + Some(Box::new(RaydiumAmmV4SwapEvent { + metadata, + amount_in, + minimum_amount_out, + + token_program: accounts[0], + amm: accounts[1], + amm_authority: accounts[2], + amm_open_orders: accounts[3], + amm_target_orders: Some(accounts[4]), + pool_coin_token_account: accounts[5], + pool_pc_token_account: accounts[6], + serum_program: accounts[7], + serum_market: accounts[8], + serum_bids: accounts[9], + serum_asks: accounts[10], + serum_event_queue: accounts[11], + serum_coin_vault_account: accounts[12], + serum_pc_vault_account: accounts[13], + serum_vault_signer: accounts[14], + user_source_token_account: accounts[15], + user_destination_token_account: accounts[16], + user_source_owner: accounts[17], + + ..Default::default() + })) +} diff --git a/src/streaming/event_parser/protocols/raydium_clmm/mod.rs b/src/streaming/event_parser/protocols/raydium_clmm/mod.rs index c5eba4c..86156fa 100755 --- a/src/streaming/event_parser/protocols/raydium_clmm/mod.rs +++ b/src/streaming/event_parser/protocols/raydium_clmm/mod.rs @@ -3,4 +3,3 @@ pub mod parser; pub mod types; pub use events::*; -pub use parser::RaydiumClmmEventParser; diff --git a/src/streaming/event_parser/protocols/raydium_clmm/parser.rs b/src/streaming/event_parser/protocols/raydium_clmm/parser.rs index 6e1e6ba..7636119 100755 --- a/src/streaming/event_parser/protocols/raydium_clmm/parser.rs +++ b/src/streaming/event_parser/protocols/raydium_clmm/parser.rs @@ -1,404 +1,380 @@ -use solana_sdk::pubkey::Pubkey; - -use crate::{ - impl_event_parser_delegate, - streaming::event_parser::{ - common::{ - read_i32_le, read_option_bool, read_u128_le, read_u64_le, read_u8_le, EventMetadata, - EventType, ProtocolType, - }, - core::traits::{GenericEventParseConfig, GenericEventParser, UnifiedEvent}, - protocols::raydium_clmm::{ - discriminators, RaydiumClmmClosePositionEvent, RaydiumClmmCreatePoolEvent, - RaydiumClmmDecreaseLiquidityV2Event, RaydiumClmmIncreaseLiquidityV2Event, - RaydiumClmmOpenPositionV2Event, RaydiumClmmOpenPositionWithToken22NftEvent, - RaydiumClmmSwapEvent, RaydiumClmmSwapV2Event, - }, +use crate::streaming::event_parser::{ + common::{ + read_i32_le, read_option_bool, read_u128_le, read_u64_le, read_u8_le, EventMetadata, + EventType, ProtocolType, }, + core::event_parser::GenericEventParseConfig, + protocols::raydium_clmm::{ + discriminators, RaydiumClmmClosePositionEvent, RaydiumClmmCreatePoolEvent, + RaydiumClmmDecreaseLiquidityV2Event, RaydiumClmmIncreaseLiquidityV2Event, + RaydiumClmmOpenPositionV2Event, RaydiumClmmOpenPositionWithToken22NftEvent, + RaydiumClmmSwapEvent, RaydiumClmmSwapV2Event, + }, + UnifiedEvent, }; +use solana_sdk::pubkey::Pubkey; /// Raydium CLMM程序ID pub const RAYDIUM_CLMM_PROGRAM_ID: Pubkey = solana_sdk::pubkey!("CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK"); -/// Raydium CLMM事件解析器 -pub struct RaydiumClmmEventParser { - inner: GenericEventParser, +// 配置所有事件类型 +pub const CONFIGS: &[GenericEventParseConfig] = &[ + GenericEventParseConfig { + program_id: RAYDIUM_CLMM_PROGRAM_ID, + protocol_type: ProtocolType::RaydiumClmm, + inner_instruction_discriminator: &[], + instruction_discriminator: discriminators::SWAP, + event_type: EventType::RaydiumClmmSwap, + inner_instruction_parser: None, + instruction_parser: Some(parse_swap_instruction), + requires_inner_instruction: false, + }, + GenericEventParseConfig { + program_id: RAYDIUM_CLMM_PROGRAM_ID, + protocol_type: ProtocolType::RaydiumClmm, + inner_instruction_discriminator: &[], + instruction_discriminator: discriminators::SWAP_V2, + event_type: EventType::RaydiumClmmSwapV2, + inner_instruction_parser: None, + instruction_parser: Some(parse_swap_v2_instruction), + requires_inner_instruction: false, + }, + GenericEventParseConfig { + program_id: RAYDIUM_CLMM_PROGRAM_ID, + protocol_type: ProtocolType::RaydiumClmm, + inner_instruction_discriminator: &[], + instruction_discriminator: discriminators::CLOSE_POSITION, + event_type: EventType::RaydiumClmmClosePosition, + inner_instruction_parser: None, + instruction_parser: Some(parse_close_position_instruction), + requires_inner_instruction: false, + }, + GenericEventParseConfig { + program_id: RAYDIUM_CLMM_PROGRAM_ID, + protocol_type: ProtocolType::RaydiumClmm, + inner_instruction_discriminator: &[], + instruction_discriminator: discriminators::DECREASE_LIQUIDITY_V2, + event_type: EventType::RaydiumClmmDecreaseLiquidityV2, + inner_instruction_parser: None, + instruction_parser: Some(parse_decrease_liquidity_v2_instruction), + requires_inner_instruction: false, + }, + GenericEventParseConfig { + program_id: RAYDIUM_CLMM_PROGRAM_ID, + protocol_type: ProtocolType::RaydiumClmm, + inner_instruction_discriminator: &[], + instruction_discriminator: discriminators::CREATE_POOL, + event_type: EventType::RaydiumClmmCreatePool, + inner_instruction_parser: None, + instruction_parser: Some(parse_create_pool_instruction), + requires_inner_instruction: false, + }, + GenericEventParseConfig { + program_id: RAYDIUM_CLMM_PROGRAM_ID, + protocol_type: ProtocolType::RaydiumClmm, + inner_instruction_discriminator: &[], + instruction_discriminator: discriminators::INCREASE_LIQUIDITY_V2, + event_type: EventType::RaydiumClmmIncreaseLiquidityV2, + inner_instruction_parser: None, + instruction_parser: Some(parse_increase_liquidity_v2_instruction), + requires_inner_instruction: false, + }, + GenericEventParseConfig { + program_id: RAYDIUM_CLMM_PROGRAM_ID, + protocol_type: ProtocolType::RaydiumClmm, + inner_instruction_discriminator: &[], + instruction_discriminator: discriminators::OPEN_POSITION_WITH_TOKEN_22_NFT, + event_type: EventType::RaydiumClmmOpenPositionWithToken22Nft, + inner_instruction_parser: None, + instruction_parser: Some(parse_open_position_with_token_22_nft_instruction), + requires_inner_instruction: false, + }, + GenericEventParseConfig { + program_id: RAYDIUM_CLMM_PROGRAM_ID, + protocol_type: ProtocolType::RaydiumClmm, + inner_instruction_discriminator: &[], + instruction_discriminator: discriminators::OPEN_POSITION_V2, + event_type: EventType::RaydiumClmmOpenPositionV2, + inner_instruction_parser: None, + instruction_parser: Some(parse_open_position_v2_instruction), + requires_inner_instruction: false, + }, +]; + +/// 解析打开仓位V2指令事件 +fn parse_open_position_v2_instruction( + data: &[u8], + accounts: &[Pubkey], + metadata: EventMetadata, +) -> Option> { + if data.len() < 51 || accounts.len() < 22 { + return None; + } + Some(Box::new(RaydiumClmmOpenPositionV2Event { + metadata, + tick_lower_index: read_i32_le(data, 0)?, + tick_upper_index: read_i32_le(data, 4)?, + tick_array_lower_start_index: read_i32_le(data, 8)?, + tick_array_upper_start_index: read_i32_le(data, 12)?, + liquidity: read_u128_le(data, 16)?, + amount0_max: read_u64_le(data, 32)?, + amount1_max: read_u64_le(data, 40)?, + with_metadata: read_u8_le(data, 48)? == 1, + base_flag: read_option_bool(data, &mut 49)?, + payer: accounts[0], + position_nft_owner: accounts[1], + position_nft_mint: accounts[2], + position_nft_account: accounts[3], + metadata_account: accounts[4], + pool_state: accounts[5], + protocol_position: accounts[6], + tick_array_lower: accounts[7], + tick_array_upper: accounts[8], + personal_position: accounts[9], + token_account0: accounts[10], + token_account1: accounts[11], + token_vault0: accounts[12], + token_vault1: accounts[13], + rent: accounts[14], + system_program: accounts[15], + token_program: accounts[16], + associated_token_program: accounts[17], + metadata_program: accounts[18], + token_program2022: accounts[19], + vault0_mint: accounts[20], + vault1_mint: accounts[21], + remaining_accounts: accounts[22..].to_vec(), + })) } -impl Default for RaydiumClmmEventParser { - fn default() -> Self { - Self::new() +/// 解析打开仓位v2指令事件 +fn parse_open_position_with_token_22_nft_instruction( + data: &[u8], + accounts: &[Pubkey], + metadata: EventMetadata, +) -> Option> { + if data.len() < 51 || accounts.len() < 20 { + return None; } + Some(Box::new(RaydiumClmmOpenPositionWithToken22NftEvent { + metadata, + tick_lower_index: read_i32_le(data, 0)?, + tick_upper_index: read_i32_le(data, 4)?, + tick_array_lower_start_index: read_i32_le(data, 8)?, + tick_array_upper_start_index: read_i32_le(data, 12)?, + liquidity: read_u128_le(data, 16)?, + amount0_max: read_u64_le(data, 32)?, + amount1_max: read_u64_le(data, 40)?, + with_metadata: read_u8_le(data, 48)? == 1, + base_flag: read_option_bool(data, &mut 49)?, + payer: accounts[0], + position_nft_owner: accounts[1], + position_nft_mint: accounts[2], + position_nft_account: accounts[3], + pool_state: accounts[4], + protocol_position: accounts[5], + tick_array_lower: accounts[6], + tick_array_upper: accounts[7], + personal_position: accounts[8], + token_account0: accounts[9], + token_account1: accounts[10], + token_vault0: accounts[11], + token_vault1: accounts[12], + rent: accounts[13], + system_program: accounts[14], + token_program: accounts[15], + associated_token_program: accounts[16], + token_program2022: accounts[17], + vault0_mint: accounts[18], + vault1_mint: accounts[19], + })) } -impl RaydiumClmmEventParser { - pub fn new() -> Self { - // 配置所有事件类型 - let configs = vec![ - GenericEventParseConfig { - program_id: RAYDIUM_CLMM_PROGRAM_ID, - protocol_type: ProtocolType::RaydiumClmm, - inner_instruction_discriminator: &[], - instruction_discriminator: discriminators::SWAP, - event_type: EventType::RaydiumClmmSwap, - inner_instruction_parser: None, - instruction_parser: Some(Self::parse_swap_instruction), - requires_inner_instruction: false, - }, - GenericEventParseConfig { - program_id: RAYDIUM_CLMM_PROGRAM_ID, - protocol_type: ProtocolType::RaydiumClmm, - inner_instruction_discriminator: &[], - instruction_discriminator: discriminators::SWAP_V2, - event_type: EventType::RaydiumClmmSwapV2, - inner_instruction_parser: None, - instruction_parser: Some(Self::parse_swap_v2_instruction), - requires_inner_instruction: false, - }, - GenericEventParseConfig { - program_id: RAYDIUM_CLMM_PROGRAM_ID, - protocol_type: ProtocolType::RaydiumClmm, - inner_instruction_discriminator: &[], - instruction_discriminator: discriminators::CLOSE_POSITION, - event_type: EventType::RaydiumClmmClosePosition, - inner_instruction_parser: None, - instruction_parser: Some(Self::parse_close_position_instruction), - requires_inner_instruction: false, - }, - GenericEventParseConfig { - program_id: RAYDIUM_CLMM_PROGRAM_ID, - protocol_type: ProtocolType::RaydiumClmm, - inner_instruction_discriminator: &[], - instruction_discriminator: discriminators::DECREASE_LIQUIDITY_V2, - event_type: EventType::RaydiumClmmDecreaseLiquidityV2, - inner_instruction_parser: None, - instruction_parser: Some(Self::parse_decrease_liquidity_v2_instruction), - requires_inner_instruction: false, - }, - GenericEventParseConfig { - program_id: RAYDIUM_CLMM_PROGRAM_ID, - protocol_type: ProtocolType::RaydiumClmm, - inner_instruction_discriminator: &[], - instruction_discriminator: discriminators::CREATE_POOL, - event_type: EventType::RaydiumClmmCreatePool, - inner_instruction_parser: None, - instruction_parser: Some(Self::parse_create_pool_instruction), - requires_inner_instruction: false, - }, - GenericEventParseConfig { - program_id: RAYDIUM_CLMM_PROGRAM_ID, - protocol_type: ProtocolType::RaydiumClmm, - inner_instruction_discriminator: &[], - instruction_discriminator: discriminators::INCREASE_LIQUIDITY_V2, - event_type: EventType::RaydiumClmmIncreaseLiquidityV2, - inner_instruction_parser: None, - instruction_parser: Some(Self::parse_increase_liquidity_v2_instruction), - requires_inner_instruction: false, - }, - GenericEventParseConfig { - program_id: RAYDIUM_CLMM_PROGRAM_ID, - protocol_type: ProtocolType::RaydiumClmm, - inner_instruction_discriminator: &[], - instruction_discriminator: discriminators::OPEN_POSITION_WITH_TOKEN_22_NFT, - event_type: EventType::RaydiumClmmOpenPositionWithToken22Nft, - inner_instruction_parser: None, - instruction_parser: Some(Self::parse_open_position_with_token_22_nft_instruction), - requires_inner_instruction: false, - }, - GenericEventParseConfig { - program_id: RAYDIUM_CLMM_PROGRAM_ID, - protocol_type: ProtocolType::RaydiumClmm, - inner_instruction_discriminator: &[], - instruction_discriminator: discriminators::OPEN_POSITION_V2, - event_type: EventType::RaydiumClmmOpenPositionV2, - inner_instruction_parser: None, - instruction_parser: Some(Self::parse_open_position_v2_instruction), - requires_inner_instruction: false, - }, - ]; - - let inner = GenericEventParser::new(vec![RAYDIUM_CLMM_PROGRAM_ID], configs); - - Self { inner } - } - - /// 解析打开仓位V2指令事件 - fn parse_open_position_v2_instruction( - data: &[u8], - accounts: &[Pubkey], - metadata: EventMetadata, - ) -> Option> { - if data.len() < 51 || accounts.len() < 22 { - return None; - } - Some(Box::new(RaydiumClmmOpenPositionV2Event { - metadata, - tick_lower_index: read_i32_le(data, 0)?, - tick_upper_index: read_i32_le(data, 4)?, - tick_array_lower_start_index: read_i32_le(data, 8)?, - tick_array_upper_start_index: read_i32_le(data, 12)?, - liquidity: read_u128_le(data, 16)?, - amount0_max: read_u64_le(data, 32)?, - amount1_max: read_u64_le(data, 40)?, - with_metadata: read_u8_le(data, 48)? == 1, - base_flag: read_option_bool(data, &mut 49)?, - payer: accounts[0], - position_nft_owner: accounts[1], - position_nft_mint: accounts[2], - position_nft_account: accounts[3], - metadata_account: accounts[4], - pool_state: accounts[5], - protocol_position: accounts[6], - tick_array_lower: accounts[7], - tick_array_upper: accounts[8], - personal_position: accounts[9], - token_account0: accounts[10], - token_account1: accounts[11], - token_vault0: accounts[12], - token_vault1: accounts[13], - rent: accounts[14], - system_program: accounts[15], - token_program: accounts[16], - associated_token_program: accounts[17], - metadata_program: accounts[18], - token_program2022: accounts[19], - vault0_mint: accounts[20], - vault1_mint: accounts[21], - remaining_accounts: accounts[22..].to_vec(), - })) - } - - /// 解析打开仓位v2指令事件 - fn parse_open_position_with_token_22_nft_instruction( - data: &[u8], - accounts: &[Pubkey], - metadata: EventMetadata, - ) -> Option> { - if data.len() < 51 || accounts.len() < 20 { - return None; - } - Some(Box::new(RaydiumClmmOpenPositionWithToken22NftEvent { - metadata, - tick_lower_index: read_i32_le(data, 0)?, - tick_upper_index: read_i32_le(data, 4)?, - tick_array_lower_start_index: read_i32_le(data, 8)?, - tick_array_upper_start_index: read_i32_le(data, 12)?, - liquidity: read_u128_le(data, 16)?, - amount0_max: read_u64_le(data, 32)?, - amount1_max: read_u64_le(data, 40)?, - with_metadata: read_u8_le(data, 48)? == 1, - base_flag: read_option_bool(data, &mut 49)?, - payer: accounts[0], - position_nft_owner: accounts[1], - position_nft_mint: accounts[2], - position_nft_account: accounts[3], - pool_state: accounts[4], - protocol_position: accounts[5], - tick_array_lower: accounts[6], - tick_array_upper: accounts[7], - personal_position: accounts[8], - token_account0: accounts[9], - token_account1: accounts[10], - token_vault0: accounts[11], - token_vault1: accounts[12], - rent: accounts[13], - system_program: accounts[14], - token_program: accounts[15], - associated_token_program: accounts[16], - token_program2022: accounts[17], - vault0_mint: accounts[18], - vault1_mint: accounts[19], - })) - } - - /// 解析增加流动性v2指令事件 - fn parse_increase_liquidity_v2_instruction( - data: &[u8], - accounts: &[Pubkey], - metadata: EventMetadata, - ) -> Option> { - if data.len() < 34 || accounts.len() < 15 { - return None; - } - Some(Box::new(RaydiumClmmIncreaseLiquidityV2Event { - metadata, - liquidity: read_u128_le(data, 0)?, - amount0_max: read_u64_le(data, 16)?, - amount1_max: read_u64_le(data, 24)?, - base_flag: read_option_bool(data, &mut 32)?, - nft_owner: accounts[0], - nft_account: accounts[1], - pool_state: accounts[2], - protocol_position: accounts[3], - personal_position: accounts[4], - tick_array_lower: accounts[5], - tick_array_upper: accounts[6], - token_account0: accounts[7], - token_account1: accounts[8], - token_vault0: accounts[9], - token_vault1: accounts[10], - token_program: accounts[11], - token_program2022: accounts[12], - vault0_mint: accounts[13], - vault1_mint: accounts[14], - })) - } - - /// 解析创建池指令事件 - fn parse_create_pool_instruction( - data: &[u8], - accounts: &[Pubkey], - metadata: EventMetadata, - ) -> Option> { - if data.len() < 24 || accounts.len() < 13 { - return None; - } - Some(Box::new(RaydiumClmmCreatePoolEvent { - metadata, - sqrt_price_x64: read_u128_le(data, 0)?, - open_time: read_u64_le(data, 16)?, - pool_creator: accounts[0], - amm_config: accounts[1], - pool_state: accounts[2], - token_mint0: accounts[3], - token_mint1: accounts[4], - token_vault0: accounts[5], - token_vault1: accounts[6], - observation_state: accounts[7], - tick_array_bitmap: accounts[8], - token_program0: accounts[9], - token_program1: accounts[10], - system_program: accounts[11], - rent: accounts[12], - })) - } - - /// 解析减少流动性v2指令事件 - fn parse_decrease_liquidity_v2_instruction( - data: &[u8], - accounts: &[Pubkey], - metadata: EventMetadata, - ) -> Option> { - if data.len() < 32 || accounts.len() < 16 { - return None; - } - Some(Box::new(RaydiumClmmDecreaseLiquidityV2Event { - metadata, - liquidity: read_u128_le(data, 0)?, - amount0_min: read_u64_le(data, 16)?, - amount1_min: read_u64_le(data, 24)?, - nft_owner: accounts[0], - nft_account: accounts[1], - personal_position: accounts[2], - pool_state: accounts[3], - protocol_position: accounts[4], - token_vault0: accounts[5], - token_vault1: accounts[6], - tick_array_lower: accounts[7], - tick_array_upper: accounts[8], - recipient_token_account0: accounts[9], - recipient_token_account1: accounts[10], - token_program: accounts[11], - token_program2022: accounts[12], - memo_program: accounts[13], - vault0_mint: accounts[14], - vault1_mint: accounts[15], - remaining_accounts: accounts[16..].to_vec(), - })) - } - - /// 解析关闭仓位指令事件 - fn parse_close_position_instruction( - _data: &[u8], - accounts: &[Pubkey], - metadata: EventMetadata, - ) -> Option> { - if accounts.len() < 6 { - return None; - } - Some(Box::new(RaydiumClmmClosePositionEvent { - metadata, - nft_owner: accounts[0], - position_nft_mint: accounts[1], - position_nft_account: accounts[2], - personal_position: accounts[3], - system_program: accounts[4], - token_program: accounts[5], - })) - } - - /// 解析交易指令事件 - fn parse_swap_instruction( - data: &[u8], - accounts: &[Pubkey], - metadata: EventMetadata, - ) -> Option> { - if data.len() < 33 || accounts.len() < 10 { - return None; - } - - let amount = read_u64_le(data, 0)?; - let other_amount_threshold = read_u64_le(data, 8)?; - let sqrt_price_limit_x64 = read_u128_le(data, 16)?; - let is_base_input = read_u8_le(data, 32)?; - - Some(Box::new(RaydiumClmmSwapEvent { - metadata, - amount, - other_amount_threshold, - sqrt_price_limit_x64, - is_base_input: is_base_input == 1, - payer: accounts[0], - amm_config: accounts[1], - pool_state: accounts[2], - input_token_account: accounts[3], - output_token_account: accounts[4], - input_vault: accounts[5], - output_vault: accounts[6], - observation_state: accounts[7], - token_program: accounts[8], - tick_array: accounts[9], - remaining_accounts: accounts[10..].to_vec(), - })) - } - - fn parse_swap_v2_instruction( - data: &[u8], - accounts: &[Pubkey], - metadata: EventMetadata, - ) -> Option> { - if data.len() < 33 || accounts.len() < 13 { - return None; - } - - let amount = read_u64_le(data, 0)?; - let other_amount_threshold = read_u64_le(data, 8)?; - let sqrt_price_limit_x64 = read_u128_le(data, 16)?; - let is_base_input = read_u8_le(data, 32)?; - - Some(Box::new(RaydiumClmmSwapV2Event { - metadata, - amount, - other_amount_threshold, - sqrt_price_limit_x64, - is_base_input: is_base_input == 1, - payer: accounts[0], - amm_config: accounts[1], - pool_state: accounts[2], - input_token_account: accounts[3], - output_token_account: accounts[4], - input_vault: accounts[5], - output_vault: accounts[6], - observation_state: accounts[7], - token_program: accounts[8], - token_program2022: accounts[9], - memo_program: accounts[10], - input_vault_mint: accounts[11], - output_vault_mint: accounts[12], - remaining_accounts: accounts[13..].to_vec(), - })) +/// 解析增加流动性v2指令事件 +fn parse_increase_liquidity_v2_instruction( + data: &[u8], + accounts: &[Pubkey], + metadata: EventMetadata, +) -> Option> { + if data.len() < 34 || accounts.len() < 15 { + return None; } + Some(Box::new(RaydiumClmmIncreaseLiquidityV2Event { + metadata, + liquidity: read_u128_le(data, 0)?, + amount0_max: read_u64_le(data, 16)?, + amount1_max: read_u64_le(data, 24)?, + base_flag: read_option_bool(data, &mut 32)?, + nft_owner: accounts[0], + nft_account: accounts[1], + pool_state: accounts[2], + protocol_position: accounts[3], + personal_position: accounts[4], + tick_array_lower: accounts[5], + tick_array_upper: accounts[6], + token_account0: accounts[7], + token_account1: accounts[8], + token_vault0: accounts[9], + token_vault1: accounts[10], + token_program: accounts[11], + token_program2022: accounts[12], + vault0_mint: accounts[13], + vault1_mint: accounts[14], + })) } -impl_event_parser_delegate!(RaydiumClmmEventParser); +/// 解析创建池指令事件 +fn parse_create_pool_instruction( + data: &[u8], + accounts: &[Pubkey], + metadata: EventMetadata, +) -> Option> { + if data.len() < 24 || accounts.len() < 13 { + return None; + } + Some(Box::new(RaydiumClmmCreatePoolEvent { + metadata, + sqrt_price_x64: read_u128_le(data, 0)?, + open_time: read_u64_le(data, 16)?, + pool_creator: accounts[0], + amm_config: accounts[1], + pool_state: accounts[2], + token_mint0: accounts[3], + token_mint1: accounts[4], + token_vault0: accounts[5], + token_vault1: accounts[6], + observation_state: accounts[7], + tick_array_bitmap: accounts[8], + token_program0: accounts[9], + token_program1: accounts[10], + system_program: accounts[11], + rent: accounts[12], + })) +} + +/// 解析减少流动性v2指令事件 +fn parse_decrease_liquidity_v2_instruction( + data: &[u8], + accounts: &[Pubkey], + metadata: EventMetadata, +) -> Option> { + if data.len() < 32 || accounts.len() < 16 { + return None; + } + Some(Box::new(RaydiumClmmDecreaseLiquidityV2Event { + metadata, + liquidity: read_u128_le(data, 0)?, + amount0_min: read_u64_le(data, 16)?, + amount1_min: read_u64_le(data, 24)?, + nft_owner: accounts[0], + nft_account: accounts[1], + personal_position: accounts[2], + pool_state: accounts[3], + protocol_position: accounts[4], + token_vault0: accounts[5], + token_vault1: accounts[6], + tick_array_lower: accounts[7], + tick_array_upper: accounts[8], + recipient_token_account0: accounts[9], + recipient_token_account1: accounts[10], + token_program: accounts[11], + token_program2022: accounts[12], + memo_program: accounts[13], + vault0_mint: accounts[14], + vault1_mint: accounts[15], + remaining_accounts: accounts[16..].to_vec(), + })) +} + +/// 解析关闭仓位指令事件 +fn parse_close_position_instruction( + _data: &[u8], + accounts: &[Pubkey], + metadata: EventMetadata, +) -> Option> { + if accounts.len() < 6 { + return None; + } + Some(Box::new(RaydiumClmmClosePositionEvent { + metadata, + nft_owner: accounts[0], + position_nft_mint: accounts[1], + position_nft_account: accounts[2], + personal_position: accounts[3], + system_program: accounts[4], + token_program: accounts[5], + })) +} + +/// 解析交易指令事件 +fn parse_swap_instruction( + data: &[u8], + accounts: &[Pubkey], + metadata: EventMetadata, +) -> Option> { + if data.len() < 33 || accounts.len() < 10 { + return None; + } + + let amount = read_u64_le(data, 0)?; + let other_amount_threshold = read_u64_le(data, 8)?; + let sqrt_price_limit_x64 = read_u128_le(data, 16)?; + let is_base_input = read_u8_le(data, 32)?; + + Some(Box::new(RaydiumClmmSwapEvent { + metadata, + amount, + other_amount_threshold, + sqrt_price_limit_x64, + is_base_input: is_base_input == 1, + payer: accounts[0], + amm_config: accounts[1], + pool_state: accounts[2], + input_token_account: accounts[3], + output_token_account: accounts[4], + input_vault: accounts[5], + output_vault: accounts[6], + observation_state: accounts[7], + token_program: accounts[8], + tick_array: accounts[9], + remaining_accounts: accounts[10..].to_vec(), + })) +} + +fn parse_swap_v2_instruction( + data: &[u8], + accounts: &[Pubkey], + metadata: EventMetadata, +) -> Option> { + if data.len() < 33 || accounts.len() < 13 { + return None; + } + + let amount = read_u64_le(data, 0)?; + let other_amount_threshold = read_u64_le(data, 8)?; + let sqrt_price_limit_x64 = read_u128_le(data, 16)?; + let is_base_input = read_u8_le(data, 32)?; + + Some(Box::new(RaydiumClmmSwapV2Event { + metadata, + amount, + other_amount_threshold, + sqrt_price_limit_x64, + is_base_input: is_base_input == 1, + payer: accounts[0], + amm_config: accounts[1], + pool_state: accounts[2], + input_token_account: accounts[3], + output_token_account: accounts[4], + input_vault: accounts[5], + output_vault: accounts[6], + observation_state: accounts[7], + token_program: accounts[8], + token_program2022: accounts[9], + memo_program: accounts[10], + input_vault_mint: accounts[11], + output_vault_mint: accounts[12], + remaining_accounts: accounts[13..].to_vec(), + })) +} diff --git a/src/streaming/event_parser/protocols/raydium_cpmm/mod.rs b/src/streaming/event_parser/protocols/raydium_cpmm/mod.rs index d24db63..86156fa 100755 --- a/src/streaming/event_parser/protocols/raydium_cpmm/mod.rs +++ b/src/streaming/event_parser/protocols/raydium_cpmm/mod.rs @@ -3,4 +3,3 @@ pub mod parser; pub mod types; pub use events::*; -pub use parser::RaydiumCpmmEventParser; diff --git a/src/streaming/event_parser/protocols/raydium_cpmm/parser.rs b/src/streaming/event_parser/protocols/raydium_cpmm/parser.rs index 25020f1..e479aae 100755 --- a/src/streaming/event_parser/protocols/raydium_cpmm/parser.rs +++ b/src/streaming/event_parser/protocols/raydium_cpmm/parser.rs @@ -1,257 +1,234 @@ use solana_sdk::pubkey::Pubkey; -use crate::{ - impl_event_parser_delegate, - streaming::event_parser::{ - common::{read_u64_le, EventMetadata, EventType, ProtocolType}, - core::traits::{GenericEventParseConfig, GenericEventParser, UnifiedEvent}, - protocols::raydium_cpmm::{ - discriminators, RaydiumCpmmDepositEvent, RaydiumCpmmInitializeEvent, - RaydiumCpmmSwapEvent, RaydiumCpmmWithdrawEvent, - }, +use crate::streaming::event_parser::{ + common::{read_u64_le, EventMetadata, EventType, ProtocolType}, + core::event_parser::{EventParser, GenericEventParseConfig}, + protocols::raydium_cpmm::{ + discriminators, RaydiumCpmmDepositEvent, RaydiumCpmmInitializeEvent, RaydiumCpmmSwapEvent, + RaydiumCpmmWithdrawEvent, }, + UnifiedEvent, }; /// Raydium CPMM程序ID pub const RAYDIUM_CPMM_PROGRAM_ID: Pubkey = solana_sdk::pubkey!("CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C"); -/// Raydium CPMM事件解析器 -pub struct RaydiumCpmmEventParser { - inner: GenericEventParser, +// 配置所有事件类型 +pub const CONFIGS: &[GenericEventParseConfig] = &[ + GenericEventParseConfig { + program_id: RAYDIUM_CPMM_PROGRAM_ID, + protocol_type: ProtocolType::RaydiumCpmm, + inner_instruction_discriminator: &[], + instruction_discriminator: discriminators::SWAP_BASE_IN, + event_type: EventType::RaydiumCpmmSwapBaseInput, + inner_instruction_parser: None, + instruction_parser: Some(parse_swap_base_input_instruction), + requires_inner_instruction: false, + }, + GenericEventParseConfig { + program_id: RAYDIUM_CPMM_PROGRAM_ID, + protocol_type: ProtocolType::RaydiumCpmm, + inner_instruction_discriminator: &[], + instruction_discriminator: discriminators::SWAP_BASE_OUT, + event_type: EventType::RaydiumCpmmSwapBaseOutput, + inner_instruction_parser: None, + instruction_parser: Some(parse_swap_base_output_instruction), + requires_inner_instruction: false, + }, + GenericEventParseConfig { + program_id: RAYDIUM_CPMM_PROGRAM_ID, + protocol_type: ProtocolType::RaydiumCpmm, + inner_instruction_discriminator: &[], + instruction_discriminator: discriminators::DEPOSIT, + event_type: EventType::RaydiumCpmmDeposit, + inner_instruction_parser: None, + instruction_parser: Some(parse_deposit_instruction), + requires_inner_instruction: false, + }, + GenericEventParseConfig { + program_id: RAYDIUM_CPMM_PROGRAM_ID, + protocol_type: ProtocolType::RaydiumCpmm, + inner_instruction_discriminator: &[], + instruction_discriminator: discriminators::INITIALIZE, + event_type: EventType::RaydiumCpmmInitialize, + inner_instruction_parser: None, + instruction_parser: Some(parse_initialize_instruction), + requires_inner_instruction: false, + }, + GenericEventParseConfig { + program_id: RAYDIUM_CPMM_PROGRAM_ID, + protocol_type: ProtocolType::RaydiumCpmm, + inner_instruction_discriminator: &[], + instruction_discriminator: discriminators::WITHDRAW, + event_type: EventType::RaydiumCpmmWithdraw, + inner_instruction_parser: None, + instruction_parser: Some(parse_withdraw_instruction), + requires_inner_instruction: false, + }, +]; + +/// 解析提款指令事件 +fn parse_withdraw_instruction( + data: &[u8], + accounts: &[Pubkey], + metadata: EventMetadata, +) -> Option> { + if data.len() < 24 || accounts.len() < 14 { + return None; + } + Some(Box::new(RaydiumCpmmWithdrawEvent { + metadata, + lp_token_amount: read_u64_le(data, 0)?, + minimum_token0_amount: read_u64_le(data, 8)?, + minimum_token1_amount: read_u64_le(data, 16)?, + owner: accounts[0], + authority: accounts[1], + pool_state: accounts[2], + owner_lp_token: accounts[3], + token0_account: accounts[4], + token1_account: accounts[5], + token0_vault: accounts[6], + token1_vault: accounts[7], + token_program: accounts[8], + token_program2022: accounts[9], + vault0_mint: accounts[10], + vault1_mint: accounts[11], + lp_mint: accounts[12], + memo_program: accounts[13], + })) } -impl Default for RaydiumCpmmEventParser { - fn default() -> Self { - Self::new() +/// 解析初始化指令事件 +fn parse_initialize_instruction( + data: &[u8], + accounts: &[Pubkey], + metadata: EventMetadata, +) -> Option> { + if data.len() < 24 || accounts.len() < 20 { + return None; } + Some(Box::new(RaydiumCpmmInitializeEvent { + metadata, + init_amount0: read_u64_le(data, 0)?, + init_amount1: read_u64_le(data, 8)?, + open_time: read_u64_le(data, 16)?, + creator: accounts[0], + amm_config: accounts[1], + authority: accounts[2], + pool_state: accounts[3], + token0_mint: accounts[4], + token1_mint: accounts[5], + lp_mint: accounts[6], + creator_token0: accounts[7], + creator_token1: accounts[8], + creator_lp_token: accounts[9], + token0_vault: accounts[10], + token1_vault: accounts[11], + create_pool_fee: accounts[12], + observation_state: accounts[13], + token_program: accounts[14], + token0_program: accounts[15], + token1_program: accounts[16], + associated_token_program: accounts[17], + system_program: accounts[18], + rent: accounts[19], + })) } -impl RaydiumCpmmEventParser { - pub fn new() -> Self { - // 配置所有事件类型 - let configs = vec![ - GenericEventParseConfig { - program_id: RAYDIUM_CPMM_PROGRAM_ID, - protocol_type: ProtocolType::RaydiumCpmm, - inner_instruction_discriminator: &[], - instruction_discriminator: discriminators::SWAP_BASE_IN, - event_type: EventType::RaydiumCpmmSwapBaseInput, - inner_instruction_parser: None, - instruction_parser: Some(Self::parse_swap_base_input_instruction), - requires_inner_instruction: false, - }, - GenericEventParseConfig { - program_id: RAYDIUM_CPMM_PROGRAM_ID, - protocol_type: ProtocolType::RaydiumCpmm, - inner_instruction_discriminator: &[], - instruction_discriminator: discriminators::SWAP_BASE_OUT, - event_type: EventType::RaydiumCpmmSwapBaseOutput, - inner_instruction_parser: None, - instruction_parser: Some(Self::parse_swap_base_output_instruction), - requires_inner_instruction: false, - }, - GenericEventParseConfig { - program_id: RAYDIUM_CPMM_PROGRAM_ID, - protocol_type: ProtocolType::RaydiumCpmm, - inner_instruction_discriminator: &[], - instruction_discriminator: discriminators::DEPOSIT, - event_type: EventType::RaydiumCpmmDeposit, - inner_instruction_parser: None, - instruction_parser: Some(Self::parse_deposit_instruction), - requires_inner_instruction: false, - }, - GenericEventParseConfig { - program_id: RAYDIUM_CPMM_PROGRAM_ID, - protocol_type: ProtocolType::RaydiumCpmm, - inner_instruction_discriminator: &[], - instruction_discriminator: discriminators::INITIALIZE, - event_type: EventType::RaydiumCpmmInitialize, - inner_instruction_parser: None, - instruction_parser: Some(Self::parse_initialize_instruction), - requires_inner_instruction: false, - }, - GenericEventParseConfig { - program_id: RAYDIUM_CPMM_PROGRAM_ID, - protocol_type: ProtocolType::RaydiumCpmm, - inner_instruction_discriminator: &[], - instruction_discriminator: discriminators::WITHDRAW, - event_type: EventType::RaydiumCpmmWithdraw, - inner_instruction_parser: None, - instruction_parser: Some(Self::parse_withdraw_instruction), - requires_inner_instruction: false, - }, - ]; - - let inner = GenericEventParser::new(vec![RAYDIUM_CPMM_PROGRAM_ID], configs); - - Self { inner } - } - - /// 解析提款指令事件 - fn parse_withdraw_instruction( - data: &[u8], - accounts: &[Pubkey], - metadata: EventMetadata, - ) -> Option> { - if data.len() < 24 || accounts.len() < 14 { - return None; - } - Some(Box::new(RaydiumCpmmWithdrawEvent { - metadata, - lp_token_amount: read_u64_le(data, 0)?, - minimum_token0_amount: read_u64_le(data, 8)?, - minimum_token1_amount: read_u64_le(data, 16)?, - owner: accounts[0], - authority: accounts[1], - pool_state: accounts[2], - owner_lp_token: accounts[3], - token0_account: accounts[4], - token1_account: accounts[5], - token0_vault: accounts[6], - token1_vault: accounts[7], - token_program: accounts[8], - token_program2022: accounts[9], - vault0_mint: accounts[10], - vault1_mint: accounts[11], - lp_mint: accounts[12], - memo_program: accounts[13], - })) - } - - /// 解析初始化指令事件 - fn parse_initialize_instruction( - data: &[u8], - accounts: &[Pubkey], - metadata: EventMetadata, - ) -> Option> { - if data.len() < 24 || accounts.len() < 20 { - return None; - } - Some(Box::new(RaydiumCpmmInitializeEvent { - metadata, - init_amount0: read_u64_le(data, 0)?, - init_amount1: read_u64_le(data, 8)?, - open_time: read_u64_le(data, 16)?, - creator: accounts[0], - amm_config: accounts[1], - authority: accounts[2], - pool_state: accounts[3], - token0_mint: accounts[4], - token1_mint: accounts[5], - lp_mint: accounts[6], - creator_token0: accounts[7], - creator_token1: accounts[8], - creator_lp_token: accounts[9], - token0_vault: accounts[10], - token1_vault: accounts[11], - create_pool_fee: accounts[12], - observation_state: accounts[13], - token_program: accounts[14], - token0_program: accounts[15], - token1_program: accounts[16], - associated_token_program: accounts[17], - system_program: accounts[18], - rent: accounts[19], - })) - } - - /// 解析存款指令事件 - fn parse_deposit_instruction( - data: &[u8], - accounts: &[Pubkey], - metadata: EventMetadata, - ) -> Option> { - if data.len() < 24 || accounts.len() < 13 { - return None; - } - Some(Box::new(RaydiumCpmmDepositEvent { - metadata, - lp_token_amount: read_u64_le(data, 0)?, - maximum_token0_amount: read_u64_le(data, 8)?, - maximum_token1_amount: read_u64_le(data, 16)?, - owner: accounts[0], - authority: accounts[1], - pool_state: accounts[2], - owner_lp_token: accounts[3], - token0_account: accounts[4], - token1_account: accounts[5], - token0_vault: accounts[6], - token1_vault: accounts[7], - token_program: accounts[8], - token_program2022: accounts[9], - vault0_mint: accounts[10], - vault1_mint: accounts[11], - lp_mint: accounts[12], - })) - } - - /// 解析买入指令事件 - fn parse_swap_base_input_instruction( - data: &[u8], - accounts: &[Pubkey], - metadata: EventMetadata, - ) -> Option> { - if data.len() < 16 || accounts.len() < 13 { - return None; - } - - let amount_in = read_u64_le(data, 0)?; - let minimum_amount_out = read_u64_le(data, 8)?; - - Some(Box::new(RaydiumCpmmSwapEvent { - metadata, - amount_in, - minimum_amount_out, - payer: accounts[0], - authority: accounts[1], - amm_config: accounts[2], - pool_state: accounts[3], - input_token_account: accounts[4], - output_token_account: accounts[5], - input_vault: accounts[6], - output_vault: accounts[7], - input_token_program: accounts[8], - output_token_program: accounts[9], - input_token_mint: accounts[10], - output_token_mint: accounts[11], - observation_state: accounts[12], - ..Default::default() - })) - } - - fn parse_swap_base_output_instruction( - data: &[u8], - accounts: &[Pubkey], - metadata: EventMetadata, - ) -> Option> { - if data.len() < 16 || accounts.len() < 13 { - return None; - } - - let max_amount_in = read_u64_le(data, 0)?; - let amount_out = read_u64_le(data, 8)?; - - Some(Box::new(RaydiumCpmmSwapEvent { - metadata, - max_amount_in, - amount_out, - payer: accounts[0], - authority: accounts[1], - amm_config: accounts[2], - pool_state: accounts[3], - input_token_account: accounts[4], - output_token_account: accounts[5], - input_vault: accounts[6], - output_vault: accounts[7], - input_token_program: accounts[8], - output_token_program: accounts[9], - input_token_mint: accounts[10], - output_token_mint: accounts[11], - observation_state: accounts[12], - ..Default::default() - })) +/// 解析存款指令事件 +fn parse_deposit_instruction( + data: &[u8], + accounts: &[Pubkey], + metadata: EventMetadata, +) -> Option> { + if data.len() < 24 || accounts.len() < 13 { + return None; } + Some(Box::new(RaydiumCpmmDepositEvent { + metadata, + lp_token_amount: read_u64_le(data, 0)?, + maximum_token0_amount: read_u64_le(data, 8)?, + maximum_token1_amount: read_u64_le(data, 16)?, + owner: accounts[0], + authority: accounts[1], + pool_state: accounts[2], + owner_lp_token: accounts[3], + token0_account: accounts[4], + token1_account: accounts[5], + token0_vault: accounts[6], + token1_vault: accounts[7], + token_program: accounts[8], + token_program2022: accounts[9], + vault0_mint: accounts[10], + vault1_mint: accounts[11], + lp_mint: accounts[12], + })) } -impl_event_parser_delegate!(RaydiumCpmmEventParser); +/// 解析买入指令事件 +fn parse_swap_base_input_instruction( + data: &[u8], + accounts: &[Pubkey], + metadata: EventMetadata, +) -> Option> { + if data.len() < 16 || accounts.len() < 13 { + return None; + } + + let amount_in = read_u64_le(data, 0)?; + let minimum_amount_out = read_u64_le(data, 8)?; + + Some(Box::new(RaydiumCpmmSwapEvent { + metadata, + amount_in, + minimum_amount_out, + payer: accounts[0], + authority: accounts[1], + amm_config: accounts[2], + pool_state: accounts[3], + input_token_account: accounts[4], + output_token_account: accounts[5], + input_vault: accounts[6], + output_vault: accounts[7], + input_token_program: accounts[8], + output_token_program: accounts[9], + input_token_mint: accounts[10], + output_token_mint: accounts[11], + observation_state: accounts[12], + ..Default::default() + })) +} + +fn parse_swap_base_output_instruction( + data: &[u8], + accounts: &[Pubkey], + metadata: EventMetadata, +) -> Option> { + if data.len() < 16 || accounts.len() < 13 { + return None; + } + + let max_amount_in = read_u64_le(data, 0)?; + let amount_out = read_u64_le(data, 8)?; + + Some(Box::new(RaydiumCpmmSwapEvent { + metadata, + max_amount_in, + amount_out, + payer: accounts[0], + authority: accounts[1], + amm_config: accounts[2], + pool_state: accounts[3], + input_token_account: accounts[4], + output_token_account: accounts[5], + input_vault: accounts[6], + output_vault: accounts[7], + input_token_program: accounts[8], + output_token_program: accounts[9], + input_token_mint: accounts[10], + output_token_mint: accounts[11], + observation_state: accounts[12], + ..Default::default() + })) +} diff --git a/src/streaming/event_parser/protocols/types.rs b/src/streaming/event_parser/protocols/types.rs new file mode 100755 index 0000000..bc81193 --- /dev/null +++ b/src/streaming/event_parser/protocols/types.rs @@ -0,0 +1,60 @@ +use crate::streaming::event_parser::protocols::{ + bonk::parser::BONK_PROGRAM_ID, pumpfun::parser::PUMPFUN_PROGRAM_ID, + pumpswap::parser::PUMPSWAP_PROGRAM_ID, raydium_amm_v4::parser::RAYDIUM_AMM_V4_PROGRAM_ID, + raydium_clmm::parser::RAYDIUM_CLMM_PROGRAM_ID, raydium_cpmm::parser::RAYDIUM_CPMM_PROGRAM_ID, +}; +use anyhow::{anyhow, Result}; +use solana_sdk::pubkey::Pubkey; + +/// 支持的协议 +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum Protocol { + PumpSwap, + PumpFun, + Bonk, + RaydiumCpmm, + RaydiumClmm, + RaydiumAmmV4, +} + +impl Protocol { + pub fn get_program_id(&self) -> Vec { + match self { + Protocol::PumpSwap => vec![PUMPSWAP_PROGRAM_ID], + Protocol::PumpFun => vec![PUMPFUN_PROGRAM_ID], + Protocol::Bonk => vec![BONK_PROGRAM_ID], + Protocol::RaydiumCpmm => vec![RAYDIUM_CPMM_PROGRAM_ID], + Protocol::RaydiumClmm => vec![RAYDIUM_CLMM_PROGRAM_ID], + Protocol::RaydiumAmmV4 => vec![RAYDIUM_AMM_V4_PROGRAM_ID], + } + } +} + +impl std::fmt::Display for Protocol { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Protocol::PumpSwap => write!(f, "PumpSwap"), + Protocol::PumpFun => write!(f, "PumpFun"), + Protocol::Bonk => write!(f, "Bonk"), + Protocol::RaydiumCpmm => write!(f, "RaydiumCpmm"), + Protocol::RaydiumClmm => write!(f, "RaydiumClmm"), + Protocol::RaydiumAmmV4 => write!(f, "RaydiumAmmV4"), + } + } +} + +impl std::str::FromStr for Protocol { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result { + match s.to_lowercase().as_str() { + "pumpswap" => Ok(Protocol::PumpSwap), + "pumpfun" => Ok(Protocol::PumpFun), + "bonk" => Ok(Protocol::Bonk), + "raydiumcpmm" => Ok(Protocol::RaydiumCpmm), + "raydiumclmm" => Ok(Protocol::RaydiumClmm), + "raydiumammv4" => Ok(Protocol::RaydiumAmmV4), + _ => Err(anyhow!("Unsupported protocol: {}", s)), + } + } +} diff --git a/src/streaming/grpc/pool.rs b/src/streaming/grpc/pool.rs index 56627c0..b95c4c6 100644 --- a/src/streaming/grpc/pool.rs +++ b/src/streaming/grpc/pool.rs @@ -1,3 +1,5 @@ +use super::types::{AccountPretty, BlockMetaPretty, TransactionPretty}; +use crate::streaming::event_parser::common::high_performance_clock::get_high_perf_clock; use solana_sdk::{pubkey::Pubkey, signature::Signature}; use std::collections::VecDeque; use std::ops::DerefMut; @@ -7,9 +9,6 @@ use yellowstone_grpc_proto::{ prost_types::Timestamp, }; -use super::types::{AccountPretty, BlockMetaPretty, TransactionPretty}; -use crate::streaming::event_parser::core::traits::get_high_perf_clock; - /// 通用对象池特征 pub trait ObjectPool { fn acquire(&self) -> PooledObject; diff --git a/src/streaming/shred_stream.rs b/src/streaming/shred_stream.rs index 7fe862a..2cb6a49 100755 --- a/src/streaming/shred_stream.rs +++ b/src/streaming/shred_stream.rs @@ -7,8 +7,8 @@ use crate::common::AnyResult; use crate::protos::shredstream::SubscribeEntriesRequest; use crate::streaming::common::{EventProcessor, SubscriptionHandle}; use crate::streaming::event_parser::common::filter::EventTypeFilter; +use crate::streaming::event_parser::common::high_performance_clock::get_high_perf_clock; use crate::streaming::event_parser::{Protocol, UnifiedEvent}; -use crate::streaming::event_parser::core::traits::get_high_perf_clock; use crate::streaming::shred::pool::factory; use log::error; use solana_entry::entry::Entry; @@ -58,11 +58,12 @@ impl ShredStreamGrpc { if let Ok(entries) = bincode::deserialize::>(&msg.entries) { for entry in entries { for transaction in entry.transactions { - let transaction_with_slot = factory::create_transaction_with_slot_pooled( - transaction.clone(), - msg.slot, - get_high_perf_clock(), - ); + let transaction_with_slot = + factory::create_transaction_with_slot_pooled( + transaction.clone(), + msg.slot, + get_high_perf_clock(), + ); // 直接处理,背压控制在 EventProcessor 内部处理 if let Err(e) = event_processor_clone .process_shred_transaction_with_metrics(