diff --git a/Cargo.toml b/Cargo.toml index 93b228d..847a40c 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "solana-streamer-sdk" -version = "1.1.2" +version = "1.1.3" 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 ffebcc4..e9714ab 100755 --- a/README.md +++ b/README.md @@ -109,14 +109,14 @@ Add the dependency to your `Cargo.toml`: ```toml # Add to your Cargo.toml -solana-streamer-sdk = { path = "./solana-streamer", version = "1.1.2" } +solana-streamer-sdk = { path = "./solana-streamer", version = "1.1.3" } ``` ### Use crates.io ```toml # Add to your Cargo.toml -solana-streamer-sdk = "1.1.2" +solana-streamer-sdk = "1.1.3" ``` ## 🔄 Migration Guide diff --git a/README_CN.md b/README_CN.md index 8c1c01e..1d96957 100644 --- a/README_CN.md +++ b/README_CN.md @@ -108,14 +108,14 @@ git clone https://github.com/0xfnzero/solana-streamer ```toml # 添加到您的 Cargo.toml -solana-streamer-sdk = { path = "./solana-streamer", version = "1.1.2" } +solana-streamer-sdk = { path = "./solana-streamer", version = "1.1.3" } ``` ### 使用 crates.io ```toml # 添加到您的 Cargo.toml -solana-streamer-sdk = "1.1.2" +solana-streamer-sdk = "1.1.3" ``` ## 🔄 迁移指南 diff --git a/src/streaming/event_parser/common/types.rs b/src/streaming/event_parser/common/types.rs index 91fd51d..7bcaa96 100755 --- a/src/streaming/event_parser/common/types.rs +++ b/src/streaming/event_parser/common/types.rs @@ -139,6 +139,8 @@ pub enum EventType { // Common events BlockMeta, + SetComputeUnitLimit, + SetComputeUnitPrice, Unknown, } @@ -233,6 +235,8 @@ impl fmt::Display for EventType { EventType::TokenAccount => write!(f, "TokenAccount"), EventType::NonceAccount => write!(f, "NonceAccount"), EventType::BlockMeta => write!(f, "BlockMeta"), + EventType::SetComputeUnitLimit => write!(f, "SetComputeUnitLimit"), + EventType::SetComputeUnitPrice => write!(f, "SetComputeUnitPrice"), EventType::Unknown => write!(f, "Unknown"), } } diff --git a/src/streaming/event_parser/core/common_event_parser.rs b/src/streaming/event_parser/core/common_event_parser.rs index 6f34fd3..948676b 100644 --- a/src/streaming/event_parser/core/common_event_parser.rs +++ b/src/streaming/event_parser/core/common_event_parser.rs @@ -1,6 +1,33 @@ use crate::streaming::event_parser::common::high_performance_clock::elapsed_micros_since; +use crate::streaming::event_parser::common::types::{EventType, ProtocolType}; +use crate::streaming::event_parser::common::EventMetadata; use crate::streaming::event_parser::core::traits::DexEvent; use crate::streaming::event_parser::protocols::block::block_meta_event::BlockMetaEvent; +use borsh::BorshDeserialize; +use serde::{Deserialize, Serialize}; +use solana_sdk::pubkey::Pubkey; + +// Compute Budget Program ID +pub const COMPUTE_BUDGET_PROGRAM_ID: Pubkey = + solana_sdk::pubkey!("ComputeBudget111111111111111111111111111111"); + +/// SetComputeUnitLimit 事件 +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)] +pub struct SetComputeUnitLimitEvent { + #[borsh(skip)] + pub metadata: EventMetadata, + /// 请求的计算单元数量 + pub units: u32, +} + +/// SetComputeUnitPrice 事件 +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)] +pub struct SetComputeUnitPriceEvent { + #[borsh(skip)] + pub metadata: EventMetadata, + /// 每个计算单元的价格 (micro-lamports) + pub micro_lamports: u64, +} pub struct CommonEventParser {} @@ -15,4 +42,42 @@ impl CommonEventParser { block_meta_event.metadata.handle_us = elapsed_micros_since(recv_us); DexEvent::BlockMetaEvent(block_meta_event) } + + /// 解析 Compute Budget 指令 + pub fn parse_compute_budget_instruction( + instruction_data: &[u8], + mut metadata: EventMetadata, + ) -> Option { + if instruction_data.is_empty() { + return None; + } + + // 设置 protocol 为 Common + metadata.protocol = ProtocolType::Common; + + // Compute Budget 指令使用单字节判别器 + match instruction_data[0] { + // SetComputeUnitLimit: discriminator = 2 + 2 => { + if instruction_data.len() < 5 { + return None; + } + let units = u32::from_le_bytes(instruction_data[1..5].try_into().ok()?); + metadata.event_type = EventType::SetComputeUnitLimit; + let event = SetComputeUnitLimitEvent { metadata, units }; + Some(DexEvent::SetComputeUnitLimitEvent(event)) + } + // SetComputeUnitPrice: discriminator = 3 + 3 => { + if instruction_data.len() < 9 { + return None; + } + let micro_lamports = u64::from_le_bytes(instruction_data[1..9].try_into().ok()?); + metadata.event_type = EventType::SetComputeUnitPrice; + let event = SetComputeUnitPriceEvent { metadata, micro_lamports }; + Some(DexEvent::SetComputeUnitPriceEvent(event)) + } + _ => None, + } + } } diff --git a/src/streaming/event_parser/core/dispatcher.rs b/src/streaming/event_parser/core/dispatcher.rs index 6429f3a..77691ce 100644 --- a/src/streaming/event_parser/core/dispatcher.rs +++ b/src/streaming/event_parser/core/dispatcher.rs @@ -9,6 +9,7 @@ use crate::streaming::event_parser::{ common::EventMetadata, + core::common_event_parser::{CommonEventParser, COMPUTE_BUDGET_PROGRAM_ID}, protocols::{ bonk::parser as bonk, meteora_damm_v2::parser as meteora_damm_v2, pumpfun::parser as pumpfun, pumpswap::parser as pumpswap, raydium_amm_v4::parser as raydium_amm_v4, @@ -191,6 +192,28 @@ impl EventDispatcher { } } + /// 检查是否为 Compute Budget Program + #[inline] + pub fn is_compute_budget_program(program_id: &Pubkey) -> bool { + program_id == &COMPUTE_BUDGET_PROGRAM_ID + } + + /// 解析 Compute Budget 指令 + /// + /// # 参数 + /// - `instruction_data`: 指令数据 + /// - `metadata`: 事件元数据 + /// + /// # 返回 + /// 解析成功返回 `Some(DexEvent)`,否则返回 `None` + #[inline] + pub fn dispatch_compute_budget_instruction( + instruction_data: &[u8], + metadata: EventMetadata, + ) -> Option { + CommonEventParser::parse_compute_budget_instruction(instruction_data, metadata) + } + /// 获取指定协议的 program_id #[inline] pub fn get_program_id(protocol: Protocol) -> Pubkey { diff --git a/src/streaming/event_parser/core/event_parser.rs b/src/streaming/event_parser/core/event_parser.rs index 0da6ee1..93a5319 100644 --- a/src/streaming/event_parser/core/event_parser.rs +++ b/src/streaming/event_parser/core/event_parser.rs @@ -325,28 +325,13 @@ impl EventParser { return Ok(()); } - // 使用 EventDispatcher 匹配协议 - let protocol = match EventDispatcher::match_protocol_by_program_id(&program_id) { - Some(p) => p, - None => return Ok(()), - }; + let is_cu_program = EventDispatcher::is_compute_budget_program(&program_id); // 检查指令数据长度(至少需要 8 字节的 discriminator) - if instruction.data.len() < 8 { + if !is_cu_program && instruction.data.len() < 8 { return Ok(()); } - // 提取 discriminator 和数据 - let instruction_discriminator = &instruction.data[..8]; - let instruction_data = &instruction.data[8..]; - - // 构建账户公钥列表 - let account_pubkeys: Vec = instruction - .accounts - .iter() - .filter_map(|&idx| accounts.get(idx as usize).copied()) - .collect(); - // 创建元数据 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; @@ -364,6 +349,33 @@ impl EventParser { transaction_index, ); + if is_cu_program { + if let Some(event) = EventDispatcher::dispatch_compute_budget_instruction( + &instruction.data, + metadata.clone(), + ) { + callback(&event); + } + return Ok(()); + } + + // 使用 EventDispatcher 匹配协议 + let protocol = match EventDispatcher::match_protocol_by_program_id(&program_id) { + Some(p) => p, + None => return Ok(()), + }; + + // 提取 discriminator 和数据 + let instruction_discriminator = &instruction.data[..8]; + let instruction_data = &instruction.data[8..]; + + // 构建账户公钥列表 + let account_pubkeys: Vec = instruction + .accounts + .iter() + .filter_map(|&idx| accounts.get(idx as usize).copied()) + .collect(); + // 使用 EventDispatcher 解析 instruction 事件 let mut event = match EventDispatcher::dispatch_instruction( protocol.clone(), @@ -483,28 +495,13 @@ impl EventParser { return Ok(()); } - // 使用 EventDispatcher 匹配协议 - let protocol = match EventDispatcher::match_protocol_by_program_id(&program_id) { - Some(p) => p, - None => return Ok(()), - }; + let is_cu_program = EventDispatcher::is_compute_budget_program(&program_id); // 检查指令数据长度(至少需要 8 字节的 discriminator) - if instruction.data.len() < 8 { + if !is_cu_program && instruction.data.len() < 8 { return Ok(()); } - // 提取 discriminator 和数据 - let instruction_discriminator = &instruction.data[..8]; - let instruction_data = &instruction.data[8..]; - - // 构建账户公钥列表 - let account_pubkeys: Vec = instruction - .accounts - .iter() - .filter_map(|&idx| accounts.get(idx as usize).copied()) - .collect(); - // 创建元数据 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; @@ -522,6 +519,33 @@ impl EventParser { transaction_index, ); + if is_cu_program { + if let Some(event) = EventDispatcher::dispatch_compute_budget_instruction( + &instruction.data, + metadata.clone(), + ) { + callback(&event); + } + return Ok(()); + } + + // 使用 EventDispatcher 匹配协议 + let protocol = match EventDispatcher::match_protocol_by_program_id(&program_id) { + Some(p) => p, + None => return Ok(()), + }; + + // 提取 discriminator 和数据 + let instruction_discriminator = &instruction.data[..8]; + let instruction_data = &instruction.data[8..]; + + // 构建账户公钥列表 + let account_pubkeys: Vec = instruction + .accounts + .iter() + .filter_map(|&idx| accounts.get(idx as usize).copied()) + .collect(); + // 使用 EventDispatcher 解析 instruction 事件 let mut event = match EventDispatcher::dispatch_instruction( protocol.clone(), @@ -621,6 +645,8 @@ impl EventParser { // 使用 EventDispatcher 来匹配协议 if let Some(protocol) = EventDispatcher::match_protocol_by_program_id(program_id) { protocols.contains(&protocol) + } else if EventDispatcher::is_compute_budget_program(program_id) { + return true; } else { false } @@ -648,6 +674,14 @@ impl EventParser { } DexEvent::PumpFunCreateTokenEvent(token_info) } + DexEvent::PumpFunCreateV2TokenEvent(token_info) => { + 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); + } + DexEvent::PumpFunCreateV2TokenEvent(token_info) + } DexEvent::PumpFunTradeEvent(mut trade_info) => { trade_info.is_dev_create_token_trade = is_dev_address_in_signature(&signature, &trade_info.user) diff --git a/src/streaming/event_parser/core/traits.rs b/src/streaming/event_parser/core/traits.rs index 6fd4c7f..0dc95f8 100755 --- a/src/streaming/event_parser/core/traits.rs +++ b/src/streaming/event_parser/core/traits.rs @@ -2,6 +2,9 @@ use crate::streaming::event_parser::common::EventMetadata; use crate::streaming::event_parser::core::account_event_parser::{ NonceAccountEvent, TokenAccountEvent, TokenInfoEvent, }; +use crate::streaming::event_parser::core::common_event_parser::{ + SetComputeUnitLimitEvent, SetComputeUnitPriceEvent, +}; use crate::streaming::event_parser::protocols::block::block_meta_event::BlockMetaEvent; use crate::streaming::event_parser::protocols::bonk::events::*; use crate::streaming::event_parser::protocols::meteora_damm_v2::events::*; @@ -83,6 +86,8 @@ pub enum DexEvent { NonceAccountEvent(NonceAccountEvent), TokenInfoEvent(TokenInfoEvent), BlockMetaEvent(BlockMetaEvent), + SetComputeUnitLimitEvent(SetComputeUnitLimitEvent), + SetComputeUnitPriceEvent(SetComputeUnitPriceEvent), } impl DexEvent { @@ -140,6 +145,8 @@ impl DexEvent { DexEvent::NonceAccountEvent(e) => &e.metadata, DexEvent::TokenInfoEvent(e) => &e.metadata, DexEvent::BlockMetaEvent(e) => &e.metadata, + DexEvent::SetComputeUnitLimitEvent(e) => &e.metadata, + DexEvent::SetComputeUnitPriceEvent(e) => &e.metadata, } } @@ -197,6 +204,8 @@ impl DexEvent { DexEvent::NonceAccountEvent(e) => &mut e.metadata, DexEvent::TokenInfoEvent(e) => &mut e.metadata, DexEvent::BlockMetaEvent(e) => &mut e.metadata, + DexEvent::SetComputeUnitLimitEvent(e) => &mut e.metadata, + DexEvent::SetComputeUnitPriceEvent(e) => &mut e.metadata, } } }