mirror of
https://github.com/0xfnzero/solana-streamer.git
synced 2026-07-27 17:37:45 +00:00
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:
+1
-1
@@ -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"
|
||||
|
||||
@@ -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
@@ -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"
|
||||
```
|
||||
|
||||
## ⚙️ 配置系统
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,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
@@ -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()
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,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(
|
||||
|
||||
Reference in New Issue
Block a user