refactor: overhaul event parser architecture for better performance

- Centralize event parsing logic and remove factory pattern
- Add high-performance clock module to reduce latency
- Consolidate protocol parsers with unified interface
- Introduce account pubkey caching and streamline traits
- Remove deprecated macro-based and multi-protocol implementations
This commit is contained in:
ysq
2025-09-14 01:13:11 +08:00
parent 9c311f63c1
commit a76563314f
34 changed files with 3374 additions and 3878 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "solana-streamer-sdk"
version = "0.4.8"
version = "0.4.9"
edition = "2021"
authors = ["William <byteblock6@gmail.com>", "sgxiang <sgxiang@gmail.com>", "wei <1415121722@qq.com>"]
repository = "https://github.com/0xfnzero/solana-streamer"
+2 -2
View File
@@ -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
+2 -2
View File
@@ -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"
```
## ⚙️ 配置系统
+3 -4
View File
@@ -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<dyn EventParser> = Arc::new(MutilEventParser::new(protocols, None));
let parser: Arc<EventParser> = Arc::new(EventParser::new(protocols, None));
parser
.parse_encoded_confirmed_transaction_with_status_meta(
signature,
+5 -7
View File
@@ -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<Arc<dyn EventParser>>,
pub(crate) parser_cache: OnceCell<Arc<EventParser>>,
pub(crate) protocols: Vec<Protocol>,
pub(crate) event_type_filter: Option<EventTypeFilter>,
pub(crate) callback: Option<Arc<dyn Fn(Box<dyn UnifiedEvent>) + 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<dyn EventParser> {
pub fn get_parser(&self) -> Arc<EventParser> {
self.parser_cache.get().unwrap().clone()
}
@@ -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<HighPerformanceClock> =
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
}
+1
View File
@@ -1,6 +1,7 @@
pub mod types;
pub mod utils;
pub mod filter;
pub mod high_performance_clock;
/// 自动生成UnifiedEvent trait实现的宏
#[macro_export]
@@ -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 {
@@ -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;
@@ -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<dyn UnifiedEvent> {
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)
}
}
File diff suppressed because it is too large Load Diff
-133
View File
@@ -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<u8>,
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<prost_types::Timestamp>,
recv_us: i64,
outer_index: i64,
inner_index: Option<i64>,
transaction_index: Option<u64>,
config: &GenericEventParseConfig,
) -> Vec<Box<dyn $crate::streaming::event_parser::core::traits::UnifiedEvent>> {
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<prost_types::Timestamp>,
recv_us: i64,
outer_index: i64,
inner_index: Option<i64>,
transaction_index: Option<u64>,
config: &GenericEventParseConfig,
) -> Vec<Box<dyn $crate::streaming::event_parser::core::traits::UnifiedEvent>> {
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<prost_types::Timestamp>,
recv_us: i64,
outer_index: i64,
inner_index: Option<i64>,
bot_wallet: Option<solana_sdk::pubkey::Pubkey>,
transaction_index: Option<u64>,
inner_instructions: Option<&solana_transaction_status::InnerInstructions>,
callback: std::sync::Arc<
dyn for<'a> Fn(&'a Box<dyn $crate::streaming::event_parser::core::traits::UnifiedEvent>)
+ 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<prost_types::Timestamp>,
recv_us: i64,
outer_index: i64,
inner_index: Option<i64>,
bot_wallet: Option<solana_sdk::pubkey::Pubkey>,
transaction_index: Option<u64>,
inner_instructions: Option<&yellowstone_grpc_proto::prelude::InnerInstructions>,
callback: std::sync::Arc<
dyn for<'a> Fn(&'a Box<dyn $crate::streaming::event_parser::core::traits::UnifiedEvent>)
+ 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<solana_sdk::pubkey::Pubkey> {
self.inner.supported_program_ids()
}
}
};
}
+5 -4
View File
@@ -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;
File diff suppressed because it is too large Load Diff
-109
View File
@@ -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<Pubkey> {
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<Self, Self::Err> {
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<HashMap<Protocol, Arc<dyn EventParser>>> = LazyLock::new(|| {
// 预分配容量,避免动态扩容
let mut parsers: HashMap<Protocol, Arc<dyn EventParser>> = 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<dyn EventParser> {
EVENT_PARSERS.get(&protocol).cloned().unwrap_or_else(|| {
panic!("Parser for protocol {protocol} not found");
})
}
/// 创建所有协议的事件解析器
pub fn create_all_parsers() -> Vec<Arc<dyn EventParser>> {
Self::supported_protocols()
.into_iter()
.map(Self::create_parser)
.collect()
}
/// 获取所有支持的协议
pub fn supported_protocols() -> Vec<Protocol> {
vec![Protocol::PumpSwap]
}
/// 检查协议是否支持
pub fn is_supported(protocol: &Protocol) -> bool {
Self::supported_protocols().contains(protocol)
}
}
+4 -5
View File
@@ -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);
@@ -3,5 +3,4 @@ pub mod parser;
pub mod types;
pub use events::*;
pub use parser::BonkEventParser;
pub use types::*;
pub use types::*;
File diff suppressed because it is too large Load Diff
+6 -13
View File
@@ -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;
pub use types::Protocol;
@@ -1,3 +0,0 @@
pub mod parser;
pub use parser::MutilEventParser;
@@ -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<Protocol>, event_type_filter: Option<EventTypeFilter>) -> 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<GenericEventParseConfig> = 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);
@@ -3,4 +3,3 @@ pub mod parser;
pub mod types;
pub use events::*;
pub use parser::PumpFunEventParser;
@@ -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<Box<dyn UnifiedEvent>> {
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<Box<dyn UnifiedEvent>> {
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<Box<dyn UnifiedEvent>> {
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<Box<dyn UnifiedEvent>> {
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<Box<dyn UnifiedEvent>> {
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<Box<dyn UnifiedEvent>> {
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<Box<dyn UnifiedEvent>> {
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<Box<dyn UnifiedEvent>> {
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<Box<dyn UnifiedEvent>> {
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<Box<dyn UnifiedEvent>> {
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<Box<dyn UnifiedEvent>> {
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<Box<dyn UnifiedEvent>> {
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<Box<dyn UnifiedEvent>> {
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<Box<dyn UnifiedEvent>> {
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()
}))
}
@@ -3,4 +3,3 @@ pub mod parser;
pub mod types;
pub use events::*;
pub use parser::PumpSwapEventParser;
@@ -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<Box<dyn UnifiedEvent>> {
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<Box<dyn UnifiedEvent>> {
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<Box<dyn UnifiedEvent>> {
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<Box<dyn UnifiedEvent>> {
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<Box<dyn UnifiedEvent>> {
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<Box<dyn UnifiedEvent>> {
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<Box<dyn UnifiedEvent>> {
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<Box<dyn UnifiedEvent>> {
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<Box<dyn UnifiedEvent>> {
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<Box<dyn UnifiedEvent>> {
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<Box<dyn UnifiedEvent>> {
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<Box<dyn UnifiedEvent>> {
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<Box<dyn UnifiedEvent>> {
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<Box<dyn UnifiedEvent>> {
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<Box<dyn UnifiedEvent>> {
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<Box<dyn UnifiedEvent>> {
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<Box<dyn UnifiedEvent>> {
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<Box<dyn UnifiedEvent>> {
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<Box<dyn UnifiedEvent>> {
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<Box<dyn UnifiedEvent>> {
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()
}))
}
@@ -3,4 +3,3 @@ pub mod parser;
pub mod types;
pub use events::*;
pub use parser::RaydiumAmmV4EventParser;
@@ -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<Box<dyn UnifiedEvent>> {
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<Box<dyn UnifiedEvent>> {
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<Box<dyn UnifiedEvent>> {
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<Box<dyn UnifiedEvent>> {
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<Box<dyn UnifiedEvent>> {
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<Box<dyn UnifiedEvent>> {
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<Box<dyn UnifiedEvent>> {
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<Box<dyn UnifiedEvent>> {
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<Box<dyn UnifiedEvent>> {
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<Box<dyn UnifiedEvent>> {
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<Box<dyn UnifiedEvent>> {
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<Box<dyn UnifiedEvent>> {
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()
}))
}
@@ -3,4 +3,3 @@ pub mod parser;
pub mod types;
pub use events::*;
pub use parser::RaydiumClmmEventParser;
@@ -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<Box<dyn UnifiedEvent>> {
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<Box<dyn UnifiedEvent>> {
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<Box<dyn UnifiedEvent>> {
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<Box<dyn UnifiedEvent>> {
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<Box<dyn UnifiedEvent>> {
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<Box<dyn UnifiedEvent>> {
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<Box<dyn UnifiedEvent>> {
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<Box<dyn UnifiedEvent>> {
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<Box<dyn UnifiedEvent>> {
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<Box<dyn UnifiedEvent>> {
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<Box<dyn UnifiedEvent>> {
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<Box<dyn UnifiedEvent>> {
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<Box<dyn UnifiedEvent>> {
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<Box<dyn UnifiedEvent>> {
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<Box<dyn UnifiedEvent>> {
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<Box<dyn UnifiedEvent>> {
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(),
}))
}
@@ -3,4 +3,3 @@ pub mod parser;
pub mod types;
pub use events::*;
pub use parser::RaydiumCpmmEventParser;
@@ -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<Box<dyn UnifiedEvent>> {
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<Box<dyn UnifiedEvent>> {
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<Box<dyn UnifiedEvent>> {
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<Box<dyn UnifiedEvent>> {
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<Box<dyn UnifiedEvent>> {
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<Box<dyn UnifiedEvent>> {
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<Box<dyn UnifiedEvent>> {
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<Box<dyn UnifiedEvent>> {
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<Box<dyn UnifiedEvent>> {
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<Box<dyn UnifiedEvent>> {
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()
}))
}
+60
View File
@@ -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<Pubkey> {
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<Self, Self::Err> {
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)),
}
}
}
+2 -3
View File
@@ -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<T> {
fn acquire(&self) -> PooledObject<T>;
+7 -6
View File
@@ -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::<Vec<Entry>>(&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(