refactor: overhaul event parser architecture for better performance

- Centralize event parsing logic and remove factory pattern
- Add high-performance clock module to reduce latency
- Consolidate protocol parsers with unified interface
- Introduce account pubkey caching and streamline traits
- Remove deprecated macro-based and multi-protocol implementations
This commit is contained in:
ysq
2025-09-14 01:13:11 +08:00
parent 9c311f63c1
commit a76563314f
34 changed files with 3374 additions and 3878 deletions
@@ -3,5 +3,4 @@ pub mod parser;
pub mod types;
pub use events::*;
pub use parser::BonkEventParser;
pub use types::*;
pub use types::*;
File diff suppressed because it is too large Load Diff
+6 -13
View File
@@ -1,17 +1,10 @@
pub mod block;
pub mod bonk;
pub mod pumpfun;
pub mod pumpswap;
pub mod bonk;
pub mod raydium_cpmm;
pub mod raydium_clmm;
pub mod raydium_amm_v4;
pub mod block;
pub mod mutil;
pub use pumpfun::PumpFunEventParser;
pub use pumpswap::PumpSwapEventParser;
pub use bonk::BonkEventParser;
pub use raydium_cpmm::RaydiumCpmmEventParser;
pub use raydium_clmm::RaydiumClmmEventParser;
pub use raydium_amm_v4::RaydiumAmmV4EventParser;
pub mod raydium_clmm;
pub mod raydium_cpmm;
pub mod types;
pub use block::block_meta_event::BlockMetaEvent;
pub use mutil::MutilEventParser;
pub use types::Protocol;
@@ -1,3 +0,0 @@
pub mod parser;
pub use parser::MutilEventParser;
@@ -1,46 +0,0 @@
use crate::{
impl_event_parser_delegate,
streaming::event_parser::{
common::filter::EventTypeFilter,
core::traits::{GenericEventParseConfig, GenericEventParser},
EventParserFactory, Protocol,
},
};
pub struct MutilEventParser {
inner: GenericEventParser,
}
impl MutilEventParser {
pub fn new(protocols: Vec<Protocol>, event_type_filter: Option<EventTypeFilter>) -> Self {
let mut inner = GenericEventParser::new(vec![], vec![]);
// Configure all event types
for protocol in protocols {
let parse = EventParserFactory::create_parser(protocol);
// Merge instruction_configs, append configurations to existing Vec
for (key, configs) in parse.instruction_configs() {
let filtered_configs: Vec<GenericEventParseConfig> = configs
.into_iter()
.filter(|config| {
event_type_filter
.as_ref()
.map(|filter| filter.include.contains(&config.event_type))
.unwrap_or(true)
})
.collect();
inner
.instruction_configs
.entry(key)
.or_insert_with(Vec::new)
.extend(filtered_configs);
}
// Append program_ids (this is already appending)
inner.program_ids.extend(parse.supported_program_ids().clone());
}
Self { inner }
}
}
impl_event_parser_delegate!(MutilEventParser);
@@ -3,4 +3,3 @@ pub mod parser;
pub mod types;
pub use events::*;
pub use parser::PumpFunEventParser;
@@ -1,270 +1,246 @@
use solana_sdk::pubkey::Pubkey;
use crate::{
impl_event_parser_delegate,
streaming::event_parser::{
common::{EventMetadata, EventType, ProtocolType},
core::traits::{GenericEventParseConfig, GenericEventParser, UnifiedEvent},
protocols::pumpfun::{
discriminators, pumpfun_create_token_event_log_decode,
pumpfun_migrate_event_log_decode, pumpfun_trade_event_log_decode,
PumpFunCreateTokenEvent, PumpFunMigrateEvent, PumpFunTradeEvent,
},
use crate::streaming::event_parser::{
common::{EventMetadata, EventType, ProtocolType},
core::event_parser::GenericEventParseConfig,
protocols::pumpfun::{
discriminators, pumpfun_create_token_event_log_decode, pumpfun_migrate_event_log_decode,
pumpfun_trade_event_log_decode, PumpFunCreateTokenEvent, PumpFunMigrateEvent,
PumpFunTradeEvent,
},
UnifiedEvent,
};
use solana_sdk::pubkey::Pubkey;
/// PumpFun程序ID
pub const PUMPFUN_PROGRAM_ID: Pubkey =
solana_sdk::pubkey!("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P");
/// PumpFun事件解析器
pub struct PumpFunEventParser {
inner: GenericEventParser,
}
// 配置所有事件类型
pub const CONFIGS: &[GenericEventParseConfig] = &[
GenericEventParseConfig {
program_id: PUMPFUN_PROGRAM_ID,
protocol_type: ProtocolType::PumpFun,
inner_instruction_discriminator: discriminators::CREATE_TOKEN_EVENT,
instruction_discriminator: discriminators::CREATE_TOKEN_IX,
event_type: EventType::PumpFunCreateToken,
inner_instruction_parser: Some(parse_create_token_inner_instruction),
instruction_parser: Some(parse_create_token_instruction),
requires_inner_instruction: false,
},
GenericEventParseConfig {
program_id: PUMPFUN_PROGRAM_ID,
protocol_type: ProtocolType::PumpFun,
inner_instruction_discriminator: discriminators::TRADE_EVENT,
instruction_discriminator: discriminators::BUY_IX,
event_type: EventType::PumpFunBuy,
inner_instruction_parser: Some(parse_trade_inner_instruction),
instruction_parser: Some(parse_buy_instruction),
requires_inner_instruction: false,
},
GenericEventParseConfig {
program_id: PUMPFUN_PROGRAM_ID,
protocol_type: ProtocolType::PumpFun,
inner_instruction_discriminator: discriminators::TRADE_EVENT,
instruction_discriminator: discriminators::SELL_IX,
event_type: EventType::PumpFunSell,
inner_instruction_parser: Some(parse_trade_inner_instruction),
instruction_parser: Some(parse_sell_instruction),
requires_inner_instruction: false,
},
GenericEventParseConfig {
program_id: PUMPFUN_PROGRAM_ID,
protocol_type: ProtocolType::PumpFun,
inner_instruction_discriminator: discriminators::COMPLETE_PUMP_AMM_MIGRATION_EVENT,
instruction_discriminator: discriminators::MIGRATE_IX,
event_type: EventType::PumpFunMigrate,
inner_instruction_parser: Some(parse_migrate_inner_instruction),
instruction_parser: Some(parse_migrate_instruction),
// Failed migrations lack inner instruction data (typically "Bonding curve already migrated")
requires_inner_instruction: true,
},
];
impl Default for PumpFunEventParser {
fn default() -> Self {
Self::new()
/// 解析迁移事件
fn parse_migrate_inner_instruction(
data: &[u8],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if let Some(event) = pumpfun_migrate_event_log_decode(data) {
Some(Box::new(PumpFunMigrateEvent { metadata, ..event }))
} else {
None
}
}
impl PumpFunEventParser {
pub fn new() -> Self {
// 配置所有事件类型
let configs = vec![
GenericEventParseConfig {
program_id: PUMPFUN_PROGRAM_ID,
protocol_type: ProtocolType::PumpFun,
inner_instruction_discriminator: discriminators::CREATE_TOKEN_EVENT,
instruction_discriminator: discriminators::CREATE_TOKEN_IX,
event_type: EventType::PumpFunCreateToken,
inner_instruction_parser: Some(Self::parse_create_token_inner_instruction),
instruction_parser: Some(Self::parse_create_token_instruction),
requires_inner_instruction: false,
},
GenericEventParseConfig {
program_id: PUMPFUN_PROGRAM_ID,
protocol_type: ProtocolType::PumpFun,
inner_instruction_discriminator: discriminators::TRADE_EVENT,
instruction_discriminator: discriminators::BUY_IX,
event_type: EventType::PumpFunBuy,
inner_instruction_parser: Some(Self::parse_trade_inner_instruction),
instruction_parser: Some(Self::parse_buy_instruction),
requires_inner_instruction: false,
},
GenericEventParseConfig {
program_id: PUMPFUN_PROGRAM_ID,
protocol_type: ProtocolType::PumpFun,
inner_instruction_discriminator: discriminators::TRADE_EVENT,
instruction_discriminator: discriminators::SELL_IX,
event_type: EventType::PumpFunSell,
inner_instruction_parser: Some(Self::parse_trade_inner_instruction),
instruction_parser: Some(Self::parse_sell_instruction),
requires_inner_instruction: false,
},
GenericEventParseConfig {
program_id: PUMPFUN_PROGRAM_ID,
protocol_type: ProtocolType::PumpFun,
inner_instruction_discriminator: discriminators::COMPLETE_PUMP_AMM_MIGRATION_EVENT,
instruction_discriminator: discriminators::MIGRATE_IX,
event_type: EventType::PumpFunMigrate,
inner_instruction_parser: Some(Self::parse_migrate_inner_instruction),
instruction_parser: Some(Self::parse_migrate_instruction),
// Failed migrations lack inner instruction data (typically "Bonding curve already migrated")
requires_inner_instruction: true,
},
];
let inner = GenericEventParser::new(vec![PUMPFUN_PROGRAM_ID], configs);
Self { inner }
}
/// 解析迁移事件
fn parse_migrate_inner_instruction(
data: &[u8],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if let Some(event) = pumpfun_migrate_event_log_decode(data) {
Some(Box::new(PumpFunMigrateEvent { metadata, ..event }))
} else {
None
}
}
/// 解析创建代币日志事件
fn parse_create_token_inner_instruction(
data: &[u8],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if let Some(event) = pumpfun_create_token_event_log_decode(data) {
Some(Box::new(PumpFunCreateTokenEvent { metadata, ..event }))
} else {
None
}
}
/// 解析交易事件
fn parse_trade_inner_instruction(
data: &[u8],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if let Some(event) = pumpfun_trade_event_log_decode(data) {
Some(Box::new(PumpFunTradeEvent { metadata, ..event }))
} else {
None
}
}
/// 解析创建代币指令事件
fn parse_create_token_instruction(
data: &[u8],
accounts: &[Pubkey],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if data.len() < 16 || accounts.len() < 11 {
return None;
}
let mut offset = 0;
let name_len = u32::from_le_bytes(data[offset..offset + 4].try_into().unwrap()) as usize;
offset += 4;
let name = String::from_utf8_lossy(&data[offset..offset + name_len]);
offset += name_len;
let symbol_len = u32::from_le_bytes(data[offset..offset + 4].try_into().unwrap()) as usize;
offset += 4;
let symbol = String::from_utf8_lossy(&data[offset..offset + symbol_len]);
offset += symbol_len;
let uri_len = u32::from_le_bytes(data[offset..offset + 4].try_into().unwrap()) as usize;
offset += 4;
let uri = String::from_utf8_lossy(&data[offset..offset + uri_len]);
offset += uri_len;
let creator = if offset + 32 <= data.len() {
Pubkey::new_from_array(data[offset..offset + 32].try_into().ok()?)
} else {
Pubkey::default()
};
Some(Box::new(PumpFunCreateTokenEvent {
metadata,
name: name.to_string(),
symbol: symbol.to_string(),
uri: uri.to_string(),
creator,
mint: accounts[0],
mint_authority: accounts[1],
bonding_curve: accounts[2],
associated_bonding_curve: accounts[3],
user: accounts[7],
..Default::default()
}))
}
// 解析买入指令事件
fn parse_buy_instruction(
data: &[u8],
accounts: &[Pubkey],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if data.len() < 16 || accounts.len() < 13 {
return None;
}
let amount = u64::from_le_bytes(data[0..8].try_into().unwrap());
let max_sol_cost = u64::from_le_bytes(data[8..16].try_into().unwrap());
Some(Box::new(PumpFunTradeEvent {
metadata,
global: accounts[0],
fee_recipient: accounts[1],
mint: accounts[2],
bonding_curve: accounts[3],
associated_bonding_curve: accounts[4],
associated_user: accounts[5],
user: accounts[6],
system_program: accounts[7],
token_program: accounts[8],
creator_vault: accounts[9],
event_authority: accounts[10],
program: accounts[11],
global_volume_accumulator: accounts[12],
user_volume_accumulator: accounts[13],
max_sol_cost,
amount,
is_buy: true,
..Default::default()
}))
}
// 解析卖出指令事件
fn parse_sell_instruction(
data: &[u8],
accounts: &[Pubkey],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if data.len() < 16 || accounts.len() < 11 {
return None;
}
let amount = u64::from_le_bytes(data[0..8].try_into().unwrap());
let min_sol_output = u64::from_le_bytes(data[8..16].try_into().unwrap());
Some(Box::new(PumpFunTradeEvent {
metadata,
global: accounts[0],
fee_recipient: accounts[1],
mint: accounts[2],
bonding_curve: accounts[3],
associated_bonding_curve: accounts[4],
associated_user: accounts[5],
user: accounts[6],
system_program: accounts[7],
creator_vault: accounts[8],
token_program: accounts[9],
event_authority: accounts[10],
program: accounts[11],
global_volume_accumulator: *accounts.get(12).unwrap_or(&Pubkey::default()),
user_volume_accumulator: *accounts.get(13).unwrap_or(&Pubkey::default()),
min_sol_output,
amount,
is_buy: false,
..Default::default()
}))
}
/// 解析迁移指令事件
fn parse_migrate_instruction(
_data: &[u8],
accounts: &[Pubkey],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if accounts.len() < 24 {
return None;
}
Some(Box::new(PumpFunMigrateEvent {
metadata,
global: accounts[0],
withdraw_authority: accounts[1],
mint: accounts[2],
bonding_curve: accounts[3],
associated_bonding_curve: accounts[4],
user: accounts[5],
system_program: accounts[6],
token_program: accounts[7],
pump_amm: accounts[8],
pool: accounts[9],
pool_authority: accounts[10],
pool_authority_mint_account: accounts[11],
pool_authority_wsol_account: accounts[12],
amm_global_config: accounts[13],
wsol_mint: accounts[14],
lp_mint: accounts[15],
user_pool_token_account: accounts[16],
pool_base_token_account: accounts[17],
pool_quote_token_account: accounts[18],
token_2022_program: accounts[19],
associated_token_program: accounts[20],
pump_amm_event_authority: accounts[21],
event_authority: accounts[22],
program: accounts[23],
..Default::default()
}))
/// 解析创建代币日志事件
fn parse_create_token_inner_instruction(
data: &[u8],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if let Some(event) = pumpfun_create_token_event_log_decode(data) {
Some(Box::new(PumpFunCreateTokenEvent { metadata, ..event }))
} else {
None
}
}
impl_event_parser_delegate!(PumpFunEventParser);
/// 解析交易事件
fn parse_trade_inner_instruction(
data: &[u8],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if let Some(event) = pumpfun_trade_event_log_decode(data) {
Some(Box::new(PumpFunTradeEvent { metadata, ..event }))
} else {
None
}
}
/// 解析创建代币指令事件
fn parse_create_token_instruction(
data: &[u8],
accounts: &[Pubkey],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if data.len() < 16 || accounts.len() < 11 {
return None;
}
let mut offset = 0;
let name_len = u32::from_le_bytes(data[offset..offset + 4].try_into().unwrap()) as usize;
offset += 4;
let name = String::from_utf8_lossy(&data[offset..offset + name_len]);
offset += name_len;
let symbol_len = u32::from_le_bytes(data[offset..offset + 4].try_into().unwrap()) as usize;
offset += 4;
let symbol = String::from_utf8_lossy(&data[offset..offset + symbol_len]);
offset += symbol_len;
let uri_len = u32::from_le_bytes(data[offset..offset + 4].try_into().unwrap()) as usize;
offset += 4;
let uri = String::from_utf8_lossy(&data[offset..offset + uri_len]);
offset += uri_len;
let creator = if offset + 32 <= data.len() {
Pubkey::new_from_array(data[offset..offset + 32].try_into().ok()?)
} else {
Pubkey::default()
};
Some(Box::new(PumpFunCreateTokenEvent {
metadata,
name: name.to_string(),
symbol: symbol.to_string(),
uri: uri.to_string(),
creator,
mint: accounts[0],
mint_authority: accounts[1],
bonding_curve: accounts[2],
associated_bonding_curve: accounts[3],
user: accounts[7],
..Default::default()
}))
}
// 解析买入指令事件
fn parse_buy_instruction(
data: &[u8],
accounts: &[Pubkey],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if data.len() < 16 || accounts.len() < 13 {
return None;
}
let amount = u64::from_le_bytes(data[0..8].try_into().unwrap());
let max_sol_cost = u64::from_le_bytes(data[8..16].try_into().unwrap());
Some(Box::new(PumpFunTradeEvent {
metadata,
global: accounts[0],
fee_recipient: accounts[1],
mint: accounts[2],
bonding_curve: accounts[3],
associated_bonding_curve: accounts[4],
associated_user: accounts[5],
user: accounts[6],
system_program: accounts[7],
token_program: accounts[8],
creator_vault: accounts[9],
event_authority: accounts[10],
program: accounts[11],
global_volume_accumulator: accounts[12],
user_volume_accumulator: accounts[13],
max_sol_cost,
amount,
is_buy: true,
..Default::default()
}))
}
// 解析卖出指令事件
fn parse_sell_instruction(
data: &[u8],
accounts: &[Pubkey],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if data.len() < 16 || accounts.len() < 11 {
return None;
}
let amount = u64::from_le_bytes(data[0..8].try_into().unwrap());
let min_sol_output = u64::from_le_bytes(data[8..16].try_into().unwrap());
Some(Box::new(PumpFunTradeEvent {
metadata,
global: accounts[0],
fee_recipient: accounts[1],
mint: accounts[2],
bonding_curve: accounts[3],
associated_bonding_curve: accounts[4],
associated_user: accounts[5],
user: accounts[6],
system_program: accounts[7],
creator_vault: accounts[8],
token_program: accounts[9],
event_authority: accounts[10],
program: accounts[11],
global_volume_accumulator: *accounts.get(12).unwrap_or(&Pubkey::default()),
user_volume_accumulator: *accounts.get(13).unwrap_or(&Pubkey::default()),
min_sol_output,
amount,
is_buy: false,
..Default::default()
}))
}
/// 解析迁移指令事件
fn parse_migrate_instruction(
_data: &[u8],
accounts: &[Pubkey],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if accounts.len() < 24 {
return None;
}
Some(Box::new(PumpFunMigrateEvent {
metadata,
global: accounts[0],
withdraw_authority: accounts[1],
mint: accounts[2],
bonding_curve: accounts[3],
associated_bonding_curve: accounts[4],
user: accounts[5],
system_program: accounts[6],
token_program: accounts[7],
pump_amm: accounts[8],
pool: accounts[9],
pool_authority: accounts[10],
pool_authority_mint_account: accounts[11],
pool_authority_wsol_account: accounts[12],
amm_global_config: accounts[13],
wsol_mint: accounts[14],
lp_mint: accounts[15],
user_pool_token_account: accounts[16],
pool_base_token_account: accounts[17],
pool_quote_token_account: accounts[18],
token_2022_program: accounts[19],
associated_token_program: accounts[20],
pump_amm_event_authority: accounts[21],
event_authority: accounts[22],
program: accounts[23],
..Default::default()
}))
}
@@ -3,4 +3,3 @@ pub mod parser;
pub mod types;
pub use events::*;
pub use parser::PumpSwapEventParser;
@@ -1,327 +1,303 @@
use solana_sdk::pubkey::Pubkey;
use crate::{
impl_event_parser_delegate,
streaming::event_parser::{
common::{read_u64_le, EventMetadata, EventType, ProtocolType},
core::traits::{GenericEventParseConfig, GenericEventParser, UnifiedEvent},
protocols::pumpswap::{
discriminators, pump_swap_buy_event_log_decode, pump_swap_create_pool_event_log_decode,
pump_swap_deposit_event_log_decode, pump_swap_sell_event_log_decode,
pump_swap_withdraw_event_log_decode, PumpSwapBuyEvent, PumpSwapCreatePoolEvent,
PumpSwapDepositEvent, PumpSwapSellEvent, PumpSwapWithdrawEvent,
},
use crate::streaming::event_parser::{
common::{read_u64_le, EventMetadata, EventType, ProtocolType},
core::event_parser::GenericEventParseConfig,
protocols::pumpswap::{
discriminators, pump_swap_buy_event_log_decode, pump_swap_create_pool_event_log_decode,
pump_swap_deposit_event_log_decode, pump_swap_sell_event_log_decode,
pump_swap_withdraw_event_log_decode, PumpSwapBuyEvent, PumpSwapCreatePoolEvent,
PumpSwapDepositEvent, PumpSwapSellEvent, PumpSwapWithdrawEvent,
},
UnifiedEvent,
};
use solana_sdk::pubkey::Pubkey;
/// PumpSwap程序ID
pub const PUMPSWAP_PROGRAM_ID: Pubkey =
solana_sdk::pubkey!("pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA");
/// PumpSwap事件解析器
pub struct PumpSwapEventParser {
inner: GenericEventParser,
}
// 配置所有事件类型
pub const CONFIGS: &[GenericEventParseConfig] = &[
GenericEventParseConfig {
program_id: PUMPSWAP_PROGRAM_ID,
protocol_type: ProtocolType::PumpSwap,
inner_instruction_discriminator: discriminators::BUY_EVENT,
instruction_discriminator: discriminators::BUY_IX,
event_type: EventType::PumpSwapBuy,
inner_instruction_parser: Some(parse_buy_inner_instruction),
instruction_parser: Some(parse_buy_instruction),
requires_inner_instruction: false,
},
GenericEventParseConfig {
program_id: PUMPSWAP_PROGRAM_ID,
protocol_type: ProtocolType::PumpSwap,
inner_instruction_discriminator: discriminators::SELL_EVENT,
instruction_discriminator: discriminators::SELL_IX,
event_type: EventType::PumpSwapSell,
inner_instruction_parser: Some(parse_sell_inner_instruction),
instruction_parser: Some(parse_sell_instruction),
requires_inner_instruction: false,
},
GenericEventParseConfig {
program_id: PUMPSWAP_PROGRAM_ID,
protocol_type: ProtocolType::PumpSwap,
inner_instruction_discriminator: discriminators::CREATE_POOL_EVENT,
instruction_discriminator: discriminators::CREATE_POOL_IX,
event_type: EventType::PumpSwapCreatePool,
inner_instruction_parser: Some(parse_create_pool_inner_instruction),
instruction_parser: Some(parse_create_pool_instruction),
requires_inner_instruction: false,
},
GenericEventParseConfig {
program_id: PUMPSWAP_PROGRAM_ID,
protocol_type: ProtocolType::PumpSwap,
inner_instruction_discriminator: discriminators::DEPOSIT_EVENT,
instruction_discriminator: discriminators::DEPOSIT_IX,
event_type: EventType::PumpSwapDeposit,
inner_instruction_parser: Some(parse_deposit_inner_instruction),
instruction_parser: Some(parse_deposit_instruction),
requires_inner_instruction: false,
},
GenericEventParseConfig {
program_id: PUMPSWAP_PROGRAM_ID,
protocol_type: ProtocolType::PumpSwap,
inner_instruction_discriminator: discriminators::WITHDRAW_EVENT,
instruction_discriminator: discriminators::WITHDRAW_IX,
event_type: EventType::PumpSwapWithdraw,
inner_instruction_parser: Some(parse_withdraw_inner_instruction),
instruction_parser: Some(parse_withdraw_instruction),
requires_inner_instruction: false,
},
];
impl Default for PumpSwapEventParser {
fn default() -> Self {
Self::new()
/// 解析买入日志事件
fn parse_buy_inner_instruction(
data: &[u8],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if let Some(event) = pump_swap_buy_event_log_decode(data) {
Some(Box::new(PumpSwapBuyEvent { metadata, ..event }))
} else {
None
}
}
impl PumpSwapEventParser {
pub fn new() -> Self {
// 配置所有事件类型
let configs = vec![
GenericEventParseConfig {
program_id: PUMPSWAP_PROGRAM_ID,
protocol_type: ProtocolType::PumpSwap,
inner_instruction_discriminator: discriminators::BUY_EVENT,
instruction_discriminator: discriminators::BUY_IX,
event_type: EventType::PumpSwapBuy,
inner_instruction_parser: Some(Self::parse_buy_inner_instruction),
instruction_parser: Some(Self::parse_buy_instruction),
requires_inner_instruction: false,
},
GenericEventParseConfig {
program_id: PUMPSWAP_PROGRAM_ID,
protocol_type: ProtocolType::PumpSwap,
inner_instruction_discriminator: discriminators::SELL_EVENT,
instruction_discriminator: discriminators::SELL_IX,
event_type: EventType::PumpSwapSell,
inner_instruction_parser: Some(Self::parse_sell_inner_instruction),
instruction_parser: Some(Self::parse_sell_instruction),
requires_inner_instruction: false,
},
GenericEventParseConfig {
program_id: PUMPSWAP_PROGRAM_ID,
protocol_type: ProtocolType::PumpSwap,
inner_instruction_discriminator: discriminators::CREATE_POOL_EVENT,
instruction_discriminator: discriminators::CREATE_POOL_IX,
event_type: EventType::PumpSwapCreatePool,
inner_instruction_parser: Some(Self::parse_create_pool_inner_instruction),
instruction_parser: Some(Self::parse_create_pool_instruction),
requires_inner_instruction: false,
},
GenericEventParseConfig {
program_id: PUMPSWAP_PROGRAM_ID,
protocol_type: ProtocolType::PumpSwap,
inner_instruction_discriminator: discriminators::DEPOSIT_EVENT,
instruction_discriminator: discriminators::DEPOSIT_IX,
event_type: EventType::PumpSwapDeposit,
inner_instruction_parser: Some(Self::parse_deposit_inner_instruction),
instruction_parser: Some(Self::parse_deposit_instruction),
requires_inner_instruction: false,
},
GenericEventParseConfig {
program_id: PUMPSWAP_PROGRAM_ID,
protocol_type: ProtocolType::PumpSwap,
inner_instruction_discriminator: discriminators::WITHDRAW_EVENT,
instruction_discriminator: discriminators::WITHDRAW_IX,
event_type: EventType::PumpSwapWithdraw,
inner_instruction_parser: Some(Self::parse_withdraw_inner_instruction),
instruction_parser: Some(Self::parse_withdraw_instruction),
requires_inner_instruction: false,
},
];
let inner = GenericEventParser::new(vec![PUMPSWAP_PROGRAM_ID], configs);
Self { inner }
}
/// 解析买入日志事件
fn parse_buy_inner_instruction(
data: &[u8],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if let Some(event) = pump_swap_buy_event_log_decode(data) {
Some(Box::new(PumpSwapBuyEvent { metadata, ..event }))
} else {
None
}
}
/// 解析卖出日志事件
fn parse_sell_inner_instruction(
data: &[u8],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if let Some(event) = pump_swap_sell_event_log_decode(data) {
Some(Box::new(PumpSwapSellEvent { metadata, ..event }))
} else {
None
}
}
/// 解析创建池子日志事件
fn parse_create_pool_inner_instruction(
data: &[u8],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if let Some(event) = pump_swap_create_pool_event_log_decode(data) {
Some(Box::new(PumpSwapCreatePoolEvent { metadata, ..event }))
} else {
None
}
}
/// 解析存款日志事件
fn parse_deposit_inner_instruction(
data: &[u8],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if let Some(event) = pump_swap_deposit_event_log_decode(data) {
Some(Box::new(PumpSwapDepositEvent { metadata, ..event }))
} else {
None
}
}
/// 解析提款日志事件
fn parse_withdraw_inner_instruction(
data: &[u8],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if let Some(event) = pump_swap_withdraw_event_log_decode(data) {
Some(Box::new(PumpSwapWithdrawEvent { metadata, ..event }))
} else {
None
}
}
/// 解析买入指令事件
fn parse_buy_instruction(
data: &[u8],
accounts: &[Pubkey],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if data.len() < 16 || accounts.len() < 11 {
return None;
}
let base_amount_out = read_u64_le(data, 0)?;
let max_quote_amount_in = read_u64_le(data, 8)?;
Some(Box::new(PumpSwapBuyEvent {
metadata,
base_amount_out,
max_quote_amount_in,
pool: accounts[0],
user: accounts[1],
base_mint: accounts[3],
quote_mint: accounts[4],
user_base_token_account: accounts[5],
user_quote_token_account: accounts[6],
pool_base_token_account: accounts[7],
pool_quote_token_account: accounts[8],
protocol_fee_recipient: accounts[9],
protocol_fee_recipient_token_account: accounts[10],
base_token_program: accounts[11],
quote_token_program: accounts[12],
coin_creator_vault_ata: accounts.get(17).copied().unwrap_or_default(),
coin_creator_vault_authority: accounts.get(18).copied().unwrap_or_default(),
..Default::default()
}))
}
/// 解析卖出指令事件
fn parse_sell_instruction(
data: &[u8],
accounts: &[Pubkey],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if data.len() < 16 || accounts.len() < 11 {
return None;
}
let base_amount_in = read_u64_le(data, 0)?;
let min_quote_amount_out = read_u64_le(data, 8)?;
Some(Box::new(PumpSwapSellEvent {
metadata,
base_amount_in,
min_quote_amount_out,
pool: accounts[0],
user: accounts[1],
base_mint: accounts[3],
quote_mint: accounts[4],
user_base_token_account: accounts[5],
user_quote_token_account: accounts[6],
pool_base_token_account: accounts[7],
pool_quote_token_account: accounts[8],
protocol_fee_recipient: accounts[9],
protocol_fee_recipient_token_account: accounts[10],
base_token_program: accounts[11],
quote_token_program: accounts[12],
coin_creator_vault_ata: accounts.get(17).copied().unwrap_or_default(),
coin_creator_vault_authority: accounts.get(18).copied().unwrap_or_default(),
..Default::default()
}))
}
/// 解析创建池子指令事件
fn parse_create_pool_instruction(
data: &[u8],
accounts: &[Pubkey],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if data.len() < 18 || accounts.len() < 11 {
return None;
}
let index = u16::from_le_bytes(data[0..2].try_into().ok()?);
let base_amount_in = u64::from_le_bytes(data[2..10].try_into().ok()?);
let quote_amount_in = u64::from_le_bytes(data[10..18].try_into().ok()?);
let coin_creator = if data.len() >= 50 {
Pubkey::new_from_array(data[18..50].try_into().ok()?)
} else {
Pubkey::default()
};
Some(Box::new(PumpSwapCreatePoolEvent {
metadata,
index,
base_amount_in,
quote_amount_in,
pool: accounts[0],
creator: accounts[2],
base_mint: accounts[3],
quote_mint: accounts[4],
lp_mint: accounts[5],
user_base_token_account: accounts[6],
user_quote_token_account: accounts[7],
user_pool_token_account: accounts[8],
pool_base_token_account: accounts[9],
pool_quote_token_account: accounts[10],
coin_creator,
..Default::default()
}))
}
/// 解析存款指令事件
fn parse_deposit_instruction(
data: &[u8],
accounts: &[Pubkey],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if data.len() < 24 || accounts.len() < 11 {
return None;
}
let lp_token_amount_out = u64::from_le_bytes(data[0..8].try_into().ok()?);
let max_base_amount_in = u64::from_le_bytes(data[8..16].try_into().ok()?);
let max_quote_amount_in = u64::from_le_bytes(data[16..24].try_into().ok()?);
Some(Box::new(PumpSwapDepositEvent {
metadata,
lp_token_amount_out,
max_base_amount_in,
max_quote_amount_in,
pool: accounts[0],
user: accounts[2],
base_mint: accounts[3],
quote_mint: accounts[4],
user_base_token_account: accounts[6],
user_quote_token_account: accounts[7],
user_pool_token_account: accounts[8],
pool_base_token_account: accounts[9],
pool_quote_token_account: accounts[10],
..Default::default()
}))
}
/// 解析提款指令事件
fn parse_withdraw_instruction(
data: &[u8],
accounts: &[Pubkey],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if data.len() < 24 || accounts.len() < 11 {
return None;
}
let lp_token_amount_in = u64::from_le_bytes(data[0..8].try_into().ok()?);
let min_base_amount_out = u64::from_le_bytes(data[8..16].try_into().ok()?);
let min_quote_amount_out = u64::from_le_bytes(data[16..24].try_into().ok()?);
Some(Box::new(PumpSwapWithdrawEvent {
metadata,
lp_token_amount_in,
min_base_amount_out,
min_quote_amount_out,
pool: accounts[0],
user: accounts[2],
base_mint: accounts[3],
quote_mint: accounts[4],
user_base_token_account: accounts[6],
user_quote_token_account: accounts[7],
user_pool_token_account: accounts[8],
pool_base_token_account: accounts[9],
pool_quote_token_account: accounts[10],
..Default::default()
}))
/// 解析卖出日志事件
fn parse_sell_inner_instruction(
data: &[u8],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if let Some(event) = pump_swap_sell_event_log_decode(data) {
Some(Box::new(PumpSwapSellEvent { metadata, ..event }))
} else {
None
}
}
impl_event_parser_delegate!(PumpSwapEventParser);
/// 解析创建池子日志事件
fn parse_create_pool_inner_instruction(
data: &[u8],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if let Some(event) = pump_swap_create_pool_event_log_decode(data) {
Some(Box::new(PumpSwapCreatePoolEvent { metadata, ..event }))
} else {
None
}
}
/// 解析存款日志事件
fn parse_deposit_inner_instruction(
data: &[u8],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if let Some(event) = pump_swap_deposit_event_log_decode(data) {
Some(Box::new(PumpSwapDepositEvent { metadata, ..event }))
} else {
None
}
}
/// 解析提款日志事件
fn parse_withdraw_inner_instruction(
data: &[u8],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if let Some(event) = pump_swap_withdraw_event_log_decode(data) {
Some(Box::new(PumpSwapWithdrawEvent { metadata, ..event }))
} else {
None
}
}
/// 解析买入指令事件
fn parse_buy_instruction(
data: &[u8],
accounts: &[Pubkey],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if data.len() < 16 || accounts.len() < 11 {
return None;
}
let base_amount_out = read_u64_le(data, 0)?;
let max_quote_amount_in = read_u64_le(data, 8)?;
Some(Box::new(PumpSwapBuyEvent {
metadata,
base_amount_out,
max_quote_amount_in,
pool: accounts[0],
user: accounts[1],
base_mint: accounts[3],
quote_mint: accounts[4],
user_base_token_account: accounts[5],
user_quote_token_account: accounts[6],
pool_base_token_account: accounts[7],
pool_quote_token_account: accounts[8],
protocol_fee_recipient: accounts[9],
protocol_fee_recipient_token_account: accounts[10],
base_token_program: accounts[11],
quote_token_program: accounts[12],
coin_creator_vault_ata: accounts.get(17).copied().unwrap_or_default(),
coin_creator_vault_authority: accounts.get(18).copied().unwrap_or_default(),
..Default::default()
}))
}
/// 解析卖出指令事件
fn parse_sell_instruction(
data: &[u8],
accounts: &[Pubkey],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if data.len() < 16 || accounts.len() < 11 {
return None;
}
let base_amount_in = read_u64_le(data, 0)?;
let min_quote_amount_out = read_u64_le(data, 8)?;
Some(Box::new(PumpSwapSellEvent {
metadata,
base_amount_in,
min_quote_amount_out,
pool: accounts[0],
user: accounts[1],
base_mint: accounts[3],
quote_mint: accounts[4],
user_base_token_account: accounts[5],
user_quote_token_account: accounts[6],
pool_base_token_account: accounts[7],
pool_quote_token_account: accounts[8],
protocol_fee_recipient: accounts[9],
protocol_fee_recipient_token_account: accounts[10],
base_token_program: accounts[11],
quote_token_program: accounts[12],
coin_creator_vault_ata: accounts.get(17).copied().unwrap_or_default(),
coin_creator_vault_authority: accounts.get(18).copied().unwrap_or_default(),
..Default::default()
}))
}
/// 解析创建池子指令事件
fn parse_create_pool_instruction(
data: &[u8],
accounts: &[Pubkey],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if data.len() < 18 || accounts.len() < 11 {
return None;
}
let index = u16::from_le_bytes(data[0..2].try_into().ok()?);
let base_amount_in = u64::from_le_bytes(data[2..10].try_into().ok()?);
let quote_amount_in = u64::from_le_bytes(data[10..18].try_into().ok()?);
let coin_creator = if data.len() >= 50 {
Pubkey::new_from_array(data[18..50].try_into().ok()?)
} else {
Pubkey::default()
};
Some(Box::new(PumpSwapCreatePoolEvent {
metadata,
index,
base_amount_in,
quote_amount_in,
pool: accounts[0],
creator: accounts[2],
base_mint: accounts[3],
quote_mint: accounts[4],
lp_mint: accounts[5],
user_base_token_account: accounts[6],
user_quote_token_account: accounts[7],
user_pool_token_account: accounts[8],
pool_base_token_account: accounts[9],
pool_quote_token_account: accounts[10],
coin_creator,
..Default::default()
}))
}
/// 解析存款指令事件
fn parse_deposit_instruction(
data: &[u8],
accounts: &[Pubkey],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if data.len() < 24 || accounts.len() < 11 {
return None;
}
let lp_token_amount_out = u64::from_le_bytes(data[0..8].try_into().ok()?);
let max_base_amount_in = u64::from_le_bytes(data[8..16].try_into().ok()?);
let max_quote_amount_in = u64::from_le_bytes(data[16..24].try_into().ok()?);
Some(Box::new(PumpSwapDepositEvent {
metadata,
lp_token_amount_out,
max_base_amount_in,
max_quote_amount_in,
pool: accounts[0],
user: accounts[2],
base_mint: accounts[3],
quote_mint: accounts[4],
user_base_token_account: accounts[6],
user_quote_token_account: accounts[7],
user_pool_token_account: accounts[8],
pool_base_token_account: accounts[9],
pool_quote_token_account: accounts[10],
..Default::default()
}))
}
/// 解析提款指令事件
fn parse_withdraw_instruction(
data: &[u8],
accounts: &[Pubkey],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if data.len() < 24 || accounts.len() < 11 {
return None;
}
let lp_token_amount_in = u64::from_le_bytes(data[0..8].try_into().ok()?);
let min_base_amount_out = u64::from_le_bytes(data[8..16].try_into().ok()?);
let min_quote_amount_out = u64::from_le_bytes(data[16..24].try_into().ok()?);
Some(Box::new(PumpSwapWithdrawEvent {
metadata,
lp_token_amount_in,
min_base_amount_out,
min_quote_amount_out,
pool: accounts[0],
user: accounts[2],
base_mint: accounts[3],
quote_mint: accounts[4],
user_base_token_account: accounts[6],
user_quote_token_account: accounts[7],
user_pool_token_account: accounts[8],
pool_base_token_account: accounts[9],
pool_quote_token_account: accounts[10],
..Default::default()
}))
}
@@ -3,4 +3,3 @@ pub mod parser;
pub mod types;
pub use events::*;
pub use parser::RaydiumAmmV4EventParser;
@@ -1,349 +1,325 @@
use solana_sdk::pubkey::Pubkey;
use crate::{
impl_event_parser_delegate,
streaming::event_parser::{
common::{read_u64_le, EventMetadata, EventType, ProtocolType},
core::traits::{GenericEventParseConfig, GenericEventParser, UnifiedEvent},
protocols::raydium_amm_v4::{
discriminators, RaydiumAmmV4DepositEvent, RaydiumAmmV4Initialize2Event,
RaydiumAmmV4SwapEvent, RaydiumAmmV4WithdrawEvent, RaydiumAmmV4WithdrawPnlEvent,
},
use crate::streaming::event_parser::{
common::{read_u64_le, EventMetadata, EventType, ProtocolType},
core::event_parser::GenericEventParseConfig,
protocols::raydium_amm_v4::{
discriminators, RaydiumAmmV4DepositEvent, RaydiumAmmV4Initialize2Event,
RaydiumAmmV4SwapEvent, RaydiumAmmV4WithdrawEvent, RaydiumAmmV4WithdrawPnlEvent,
},
UnifiedEvent,
};
use solana_sdk::pubkey::Pubkey;
/// Raydium CPMM程序ID
pub const RAYDIUM_AMM_V4_PROGRAM_ID: Pubkey =
solana_sdk::pubkey!("675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8");
/// Raydium CPMM事件解析器
pub struct RaydiumAmmV4EventParser {
inner: GenericEventParser,
// 配置所有事件类型
pub const CONFIGS: &[GenericEventParseConfig] = &[
GenericEventParseConfig {
program_id: RAYDIUM_AMM_V4_PROGRAM_ID,
protocol_type: ProtocolType::RaydiumAmmV4,
inner_instruction_discriminator: &[],
instruction_discriminator: discriminators::SWAP_BASE_IN,
event_type: EventType::RaydiumAmmV4SwapBaseIn,
inner_instruction_parser: None,
instruction_parser: Some(parse_swap_base_input_instruction),
requires_inner_instruction: false,
},
GenericEventParseConfig {
program_id: RAYDIUM_AMM_V4_PROGRAM_ID,
protocol_type: ProtocolType::RaydiumAmmV4,
inner_instruction_discriminator: &[],
instruction_discriminator: discriminators::SWAP_BASE_OUT,
event_type: EventType::RaydiumAmmV4SwapBaseOut,
inner_instruction_parser: None,
instruction_parser: Some(parse_swap_base_output_instruction),
requires_inner_instruction: false,
},
GenericEventParseConfig {
program_id: RAYDIUM_AMM_V4_PROGRAM_ID,
protocol_type: ProtocolType::RaydiumAmmV4,
inner_instruction_discriminator: &[],
instruction_discriminator: discriminators::DEPOSIT,
event_type: EventType::RaydiumAmmV4Deposit,
inner_instruction_parser: None,
instruction_parser: Some(parse_deposit_instruction),
requires_inner_instruction: false,
},
GenericEventParseConfig {
program_id: RAYDIUM_AMM_V4_PROGRAM_ID,
protocol_type: ProtocolType::RaydiumAmmV4,
inner_instruction_discriminator: &[],
instruction_discriminator: discriminators::INITIALIZE2,
event_type: EventType::RaydiumAmmV4Initialize2,
inner_instruction_parser: None,
instruction_parser: Some(parse_initialize2_instruction),
requires_inner_instruction: false,
},
GenericEventParseConfig {
program_id: RAYDIUM_AMM_V4_PROGRAM_ID,
protocol_type: ProtocolType::RaydiumAmmV4,
inner_instruction_discriminator: &[],
instruction_discriminator: discriminators::WITHDRAW,
event_type: EventType::RaydiumAmmV4Withdraw,
inner_instruction_parser: None,
instruction_parser: Some(parse_withdraw_instruction),
requires_inner_instruction: false,
},
GenericEventParseConfig {
program_id: RAYDIUM_AMM_V4_PROGRAM_ID,
protocol_type: ProtocolType::RaydiumAmmV4,
inner_instruction_discriminator: &[],
instruction_discriminator: discriminators::WITHDRAW_PNL,
event_type: EventType::RaydiumAmmV4WithdrawPnl,
inner_instruction_parser: None,
instruction_parser: Some(parse_withdraw_pnl_instruction),
requires_inner_instruction: false,
},
];
/// 解析提现指令事件
fn parse_withdraw_pnl_instruction(
_data: &[u8],
accounts: &[Pubkey],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if accounts.len() < 17 {
return None;
}
Some(Box::new(RaydiumAmmV4WithdrawPnlEvent {
metadata,
token_program: accounts[0],
amm: accounts[1],
amm_config: accounts[2],
amm_authority: accounts[3],
amm_open_orders: accounts[4],
pool_coin_token_account: accounts[5],
pool_pc_token_account: accounts[6],
coin_pnl_token_account: accounts[7],
pc_pnl_token_account: accounts[8],
pnl_owner_account: accounts[9],
amm_target_orders: accounts[10],
serum_program: accounts[11],
serum_market: accounts[12],
serum_event_queue: accounts[13],
serum_coin_vault_account: accounts[14],
serum_pc_vault_account: accounts[15],
serum_vault_signer: accounts[16],
}))
}
impl Default for RaydiumAmmV4EventParser {
fn default() -> Self {
Self::new()
/// 解析移除流动性指令事件
fn parse_withdraw_instruction(
data: &[u8],
accounts: &[Pubkey],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if data.len() < 8 || accounts.len() < 22 {
return None;
}
let amount = read_u64_le(data, 0)?;
Some(Box::new(RaydiumAmmV4WithdrawEvent {
metadata,
amount,
token_program: accounts[0],
amm: accounts[1],
amm_authority: accounts[2],
amm_open_orders: accounts[3],
amm_target_orders: accounts[4],
lp_mint_address: accounts[5],
pool_coin_token_account: accounts[6],
pool_pc_token_account: accounts[7],
pool_withdraw_queue: accounts[8],
pool_temp_lp_token_account: accounts[9],
serum_program: accounts[10],
serum_market: accounts[11],
serum_coin_vault_account: accounts[12],
serum_pc_vault_account: accounts[13],
serum_vault_signer: accounts[14],
user_lp_token_account: accounts[15],
user_coin_token_account: accounts[16],
user_pc_token_account: accounts[17],
user_owner: accounts[18],
serum_event_queue: accounts[19],
serum_bids: accounts[20],
serum_asks: accounts[21],
}))
}
impl RaydiumAmmV4EventParser {
pub fn new() -> Self {
// 配置所有事件类型
let configs = vec![
GenericEventParseConfig {
program_id: RAYDIUM_AMM_V4_PROGRAM_ID,
protocol_type: ProtocolType::RaydiumAmmV4,
inner_instruction_discriminator: &[],
instruction_discriminator: discriminators::SWAP_BASE_IN,
event_type: EventType::RaydiumAmmV4SwapBaseIn,
inner_instruction_parser: None,
instruction_parser: Some(Self::parse_swap_base_input_instruction),
requires_inner_instruction: false,
},
GenericEventParseConfig {
program_id: RAYDIUM_AMM_V4_PROGRAM_ID,
protocol_type: ProtocolType::RaydiumAmmV4,
inner_instruction_discriminator: &[],
instruction_discriminator: discriminators::SWAP_BASE_OUT,
event_type: EventType::RaydiumAmmV4SwapBaseOut,
inner_instruction_parser: None,
instruction_parser: Some(Self::parse_swap_base_output_instruction),
requires_inner_instruction: false,
},
GenericEventParseConfig {
program_id: RAYDIUM_AMM_V4_PROGRAM_ID,
protocol_type: ProtocolType::RaydiumAmmV4,
inner_instruction_discriminator: &[],
instruction_discriminator: discriminators::DEPOSIT,
event_type: EventType::RaydiumAmmV4Deposit,
inner_instruction_parser: None,
instruction_parser: Some(Self::parse_deposit_instruction),
requires_inner_instruction: false,
},
GenericEventParseConfig {
program_id: RAYDIUM_AMM_V4_PROGRAM_ID,
protocol_type: ProtocolType::RaydiumAmmV4,
inner_instruction_discriminator: &[],
instruction_discriminator: discriminators::INITIALIZE2,
event_type: EventType::RaydiumAmmV4Initialize2,
inner_instruction_parser: None,
instruction_parser: Some(Self::parse_initialize2_instruction),
requires_inner_instruction: false,
},
GenericEventParseConfig {
program_id: RAYDIUM_AMM_V4_PROGRAM_ID,
protocol_type: ProtocolType::RaydiumAmmV4,
inner_instruction_discriminator: &[],
instruction_discriminator: discriminators::WITHDRAW,
event_type: EventType::RaydiumAmmV4Withdraw,
inner_instruction_parser: None,
instruction_parser: Some(Self::parse_withdraw_instruction),
requires_inner_instruction: false,
},
GenericEventParseConfig {
program_id: RAYDIUM_AMM_V4_PROGRAM_ID,
protocol_type: ProtocolType::RaydiumAmmV4,
inner_instruction_discriminator: &[],
instruction_discriminator: discriminators::WITHDRAW_PNL,
event_type: EventType::RaydiumAmmV4WithdrawPnl,
inner_instruction_parser: None,
instruction_parser: Some(Self::parse_withdraw_pnl_instruction),
requires_inner_instruction: false,
},
];
let inner = GenericEventParser::new(vec![RAYDIUM_AMM_V4_PROGRAM_ID], configs);
Self { inner }
/// 解析初始化指令事件
fn parse_initialize2_instruction(
data: &[u8],
accounts: &[Pubkey],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if data.len() < 25 || accounts.len() < 21 {
return None;
}
let nonce = data[0];
let open_time = read_u64_le(data, 1)?;
let init_pc_amount = read_u64_le(data, 9)?;
let init_coin_amount = read_u64_le(data, 17)?;
/// 解析提现指令事件
fn parse_withdraw_pnl_instruction(
_data: &[u8],
accounts: &[Pubkey],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if accounts.len() < 17 {
return None;
}
Some(Box::new(RaydiumAmmV4Initialize2Event {
metadata,
nonce,
open_time,
init_pc_amount,
init_coin_amount,
Some(Box::new(RaydiumAmmV4WithdrawPnlEvent {
metadata,
token_program: accounts[0],
amm: accounts[1],
amm_config: accounts[2],
amm_authority: accounts[3],
amm_open_orders: accounts[4],
pool_coin_token_account: accounts[5],
pool_pc_token_account: accounts[6],
coin_pnl_token_account: accounts[7],
pc_pnl_token_account: accounts[8],
pnl_owner_account: accounts[9],
amm_target_orders: accounts[10],
serum_program: accounts[11],
serum_market: accounts[12],
serum_event_queue: accounts[13],
serum_coin_vault_account: accounts[14],
serum_pc_vault_account: accounts[15],
serum_vault_signer: accounts[16],
}))
}
/// 解析移除流动性指令事件
fn parse_withdraw_instruction(
data: &[u8],
accounts: &[Pubkey],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if data.len() < 8 || accounts.len() < 22 {
return None;
}
let amount = read_u64_le(data, 0)?;
Some(Box::new(RaydiumAmmV4WithdrawEvent {
metadata,
amount,
token_program: accounts[0],
amm: accounts[1],
amm_authority: accounts[2],
amm_open_orders: accounts[3],
amm_target_orders: accounts[4],
lp_mint_address: accounts[5],
pool_coin_token_account: accounts[6],
pool_pc_token_account: accounts[7],
pool_withdraw_queue: accounts[8],
pool_temp_lp_token_account: accounts[9],
serum_program: accounts[10],
serum_market: accounts[11],
serum_coin_vault_account: accounts[12],
serum_pc_vault_account: accounts[13],
serum_vault_signer: accounts[14],
user_lp_token_account: accounts[15],
user_coin_token_account: accounts[16],
user_pc_token_account: accounts[17],
user_owner: accounts[18],
serum_event_queue: accounts[19],
serum_bids: accounts[20],
serum_asks: accounts[21],
}))
}
/// 解析初始化指令事件
fn parse_initialize2_instruction(
data: &[u8],
accounts: &[Pubkey],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if data.len() < 25 || accounts.len() < 21 {
return None;
}
let nonce = data[0];
let open_time = read_u64_le(data, 1)?;
let init_pc_amount = read_u64_le(data, 9)?;
let init_coin_amount = read_u64_le(data, 17)?;
Some(Box::new(RaydiumAmmV4Initialize2Event {
metadata,
nonce,
open_time,
init_pc_amount,
init_coin_amount,
token_program: accounts[0],
spl_associated_token_account: accounts[1],
system_program: accounts[2],
rent: accounts[3],
amm: accounts[4],
amm_authority: accounts[5],
amm_open_orders: accounts[6],
lp_mint: accounts[7],
coin_mint: accounts[8],
pc_mint: accounts[9],
pool_coin_token_account: accounts[10],
pool_pc_token_account: accounts[11],
pool_withdraw_queue: accounts[12],
amm_target_orders: accounts[13],
pool_temp_lp: accounts[14],
serum_program: accounts[15],
serum_market: accounts[16],
user_wallet: accounts[17],
user_token_coin: accounts[18],
user_token_pc: accounts[19],
user_lp_token_account: accounts[20],
}))
}
/// 解析添加流动性指令事件
fn parse_deposit_instruction(
data: &[u8],
accounts: &[Pubkey],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if data.len() < 24 || accounts.len() < 14 {
return None;
}
let max_coin_amount = read_u64_le(data, 0)?;
let max_pc_amount = read_u64_le(data, 8)?;
let base_side = read_u64_le(data, 16)?;
Some(Box::new(RaydiumAmmV4DepositEvent {
metadata,
max_coin_amount,
max_pc_amount,
base_side,
token_program: accounts[0],
amm: accounts[1],
amm_authority: accounts[2],
amm_open_orders: accounts[3],
amm_target_orders: accounts[4],
lp_mint_address: accounts[5],
pool_coin_token_account: accounts[6],
pool_pc_token_account: accounts[7],
serum_market: accounts[8],
user_coin_token_account: accounts[9],
user_pc_token_account: accounts[10],
user_lp_token_account: accounts[11],
user_owner: accounts[12],
serum_event_queue: accounts[13],
}))
}
/// 解析买入指令事件
fn parse_swap_base_output_instruction(
data: &[u8],
accounts: &[Pubkey],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if data.len() < 16 || accounts.len() < 17 {
return None;
}
let max_amount_in = read_u64_le(data, 0)?;
let amount_out = read_u64_le(data, 8)?;
let mut accounts = accounts.to_vec();
if accounts.len() == 17 {
// 添加一个默认的 Pubkey 作为 amm_target_orders 的占位符
// 因为在某些情况下,amm_target_orders 可能是可选的
accounts.insert(4, Pubkey::default());
}
Some(Box::new(RaydiumAmmV4SwapEvent {
metadata,
max_amount_in,
amount_out,
token_program: accounts[0],
amm: accounts[1],
amm_authority: accounts[2],
amm_open_orders: accounts[3],
amm_target_orders: Some(accounts[4]),
pool_coin_token_account: accounts[5],
pool_pc_token_account: accounts[6],
serum_program: accounts[7],
serum_market: accounts[8],
serum_bids: accounts[9],
serum_asks: accounts[10],
serum_event_queue: accounts[11],
serum_coin_vault_account: accounts[12],
serum_pc_vault_account: accounts[13],
serum_vault_signer: accounts[14],
user_source_token_account: accounts[15],
user_destination_token_account: accounts[16],
user_source_owner: accounts[17],
..Default::default()
}))
}
/// 解析买入指令事件
fn parse_swap_base_input_instruction(
data: &[u8],
accounts: &[Pubkey],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if data.len() < 16 || accounts.len() < 17 {
return None;
}
let amount_in = read_u64_le(data, 0)?;
let minimum_amount_out = read_u64_le(data, 8)?;
let mut accounts = accounts.to_vec();
if accounts.len() == 17 {
// 添加一个默认的 Pubkey 作为 amm_target_orders 的占位符
// 因为在某些情况下,amm_target_orders 可能是可选的
accounts.insert(4, Pubkey::default());
}
Some(Box::new(RaydiumAmmV4SwapEvent {
metadata,
amount_in,
minimum_amount_out,
token_program: accounts[0],
amm: accounts[1],
amm_authority: accounts[2],
amm_open_orders: accounts[3],
amm_target_orders: Some(accounts[4]),
pool_coin_token_account: accounts[5],
pool_pc_token_account: accounts[6],
serum_program: accounts[7],
serum_market: accounts[8],
serum_bids: accounts[9],
serum_asks: accounts[10],
serum_event_queue: accounts[11],
serum_coin_vault_account: accounts[12],
serum_pc_vault_account: accounts[13],
serum_vault_signer: accounts[14],
user_source_token_account: accounts[15],
user_destination_token_account: accounts[16],
user_source_owner: accounts[17],
..Default::default()
}))
}
token_program: accounts[0],
spl_associated_token_account: accounts[1],
system_program: accounts[2],
rent: accounts[3],
amm: accounts[4],
amm_authority: accounts[5],
amm_open_orders: accounts[6],
lp_mint: accounts[7],
coin_mint: accounts[8],
pc_mint: accounts[9],
pool_coin_token_account: accounts[10],
pool_pc_token_account: accounts[11],
pool_withdraw_queue: accounts[12],
amm_target_orders: accounts[13],
pool_temp_lp: accounts[14],
serum_program: accounts[15],
serum_market: accounts[16],
user_wallet: accounts[17],
user_token_coin: accounts[18],
user_token_pc: accounts[19],
user_lp_token_account: accounts[20],
}))
}
impl_event_parser_delegate!(RaydiumAmmV4EventParser);
/// 解析添加流动性指令事件
fn parse_deposit_instruction(
data: &[u8],
accounts: &[Pubkey],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if data.len() < 24 || accounts.len() < 14 {
return None;
}
let max_coin_amount = read_u64_le(data, 0)?;
let max_pc_amount = read_u64_le(data, 8)?;
let base_side = read_u64_le(data, 16)?;
Some(Box::new(RaydiumAmmV4DepositEvent {
metadata,
max_coin_amount,
max_pc_amount,
base_side,
token_program: accounts[0],
amm: accounts[1],
amm_authority: accounts[2],
amm_open_orders: accounts[3],
amm_target_orders: accounts[4],
lp_mint_address: accounts[5],
pool_coin_token_account: accounts[6],
pool_pc_token_account: accounts[7],
serum_market: accounts[8],
user_coin_token_account: accounts[9],
user_pc_token_account: accounts[10],
user_lp_token_account: accounts[11],
user_owner: accounts[12],
serum_event_queue: accounts[13],
}))
}
/// 解析买入指令事件
fn parse_swap_base_output_instruction(
data: &[u8],
accounts: &[Pubkey],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if data.len() < 16 || accounts.len() < 17 {
return None;
}
let max_amount_in = read_u64_le(data, 0)?;
let amount_out = read_u64_le(data, 8)?;
let mut accounts = accounts.to_vec();
if accounts.len() == 17 {
// 添加一个默认的 Pubkey 作为 amm_target_orders 的占位符
// 因为在某些情况下,amm_target_orders 可能是可选的
accounts.insert(4, Pubkey::default());
}
Some(Box::new(RaydiumAmmV4SwapEvent {
metadata,
max_amount_in,
amount_out,
token_program: accounts[0],
amm: accounts[1],
amm_authority: accounts[2],
amm_open_orders: accounts[3],
amm_target_orders: Some(accounts[4]),
pool_coin_token_account: accounts[5],
pool_pc_token_account: accounts[6],
serum_program: accounts[7],
serum_market: accounts[8],
serum_bids: accounts[9],
serum_asks: accounts[10],
serum_event_queue: accounts[11],
serum_coin_vault_account: accounts[12],
serum_pc_vault_account: accounts[13],
serum_vault_signer: accounts[14],
user_source_token_account: accounts[15],
user_destination_token_account: accounts[16],
user_source_owner: accounts[17],
..Default::default()
}))
}
/// 解析买入指令事件
fn parse_swap_base_input_instruction(
data: &[u8],
accounts: &[Pubkey],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if data.len() < 16 || accounts.len() < 17 {
return None;
}
let amount_in = read_u64_le(data, 0)?;
let minimum_amount_out = read_u64_le(data, 8)?;
let mut accounts = accounts.to_vec();
if accounts.len() == 17 {
// 添加一个默认的 Pubkey 作为 amm_target_orders 的占位符
// 因为在某些情况下,amm_target_orders 可能是可选的
accounts.insert(4, Pubkey::default());
}
Some(Box::new(RaydiumAmmV4SwapEvent {
metadata,
amount_in,
minimum_amount_out,
token_program: accounts[0],
amm: accounts[1],
amm_authority: accounts[2],
amm_open_orders: accounts[3],
amm_target_orders: Some(accounts[4]),
pool_coin_token_account: accounts[5],
pool_pc_token_account: accounts[6],
serum_program: accounts[7],
serum_market: accounts[8],
serum_bids: accounts[9],
serum_asks: accounts[10],
serum_event_queue: accounts[11],
serum_coin_vault_account: accounts[12],
serum_pc_vault_account: accounts[13],
serum_vault_signer: accounts[14],
user_source_token_account: accounts[15],
user_destination_token_account: accounts[16],
user_source_owner: accounts[17],
..Default::default()
}))
}
@@ -3,4 +3,3 @@ pub mod parser;
pub mod types;
pub use events::*;
pub use parser::RaydiumClmmEventParser;
@@ -1,404 +1,380 @@
use solana_sdk::pubkey::Pubkey;
use crate::{
impl_event_parser_delegate,
streaming::event_parser::{
common::{
read_i32_le, read_option_bool, read_u128_le, read_u64_le, read_u8_le, EventMetadata,
EventType, ProtocolType,
},
core::traits::{GenericEventParseConfig, GenericEventParser, UnifiedEvent},
protocols::raydium_clmm::{
discriminators, RaydiumClmmClosePositionEvent, RaydiumClmmCreatePoolEvent,
RaydiumClmmDecreaseLiquidityV2Event, RaydiumClmmIncreaseLiquidityV2Event,
RaydiumClmmOpenPositionV2Event, RaydiumClmmOpenPositionWithToken22NftEvent,
RaydiumClmmSwapEvent, RaydiumClmmSwapV2Event,
},
use crate::streaming::event_parser::{
common::{
read_i32_le, read_option_bool, read_u128_le, read_u64_le, read_u8_le, EventMetadata,
EventType, ProtocolType,
},
core::event_parser::GenericEventParseConfig,
protocols::raydium_clmm::{
discriminators, RaydiumClmmClosePositionEvent, RaydiumClmmCreatePoolEvent,
RaydiumClmmDecreaseLiquidityV2Event, RaydiumClmmIncreaseLiquidityV2Event,
RaydiumClmmOpenPositionV2Event, RaydiumClmmOpenPositionWithToken22NftEvent,
RaydiumClmmSwapEvent, RaydiumClmmSwapV2Event,
},
UnifiedEvent,
};
use solana_sdk::pubkey::Pubkey;
/// Raydium CLMM程序ID
pub const RAYDIUM_CLMM_PROGRAM_ID: Pubkey =
solana_sdk::pubkey!("CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK");
/// Raydium CLMM事件解析器
pub struct RaydiumClmmEventParser {
inner: GenericEventParser,
// 配置所有事件类型
pub const CONFIGS: &[GenericEventParseConfig] = &[
GenericEventParseConfig {
program_id: RAYDIUM_CLMM_PROGRAM_ID,
protocol_type: ProtocolType::RaydiumClmm,
inner_instruction_discriminator: &[],
instruction_discriminator: discriminators::SWAP,
event_type: EventType::RaydiumClmmSwap,
inner_instruction_parser: None,
instruction_parser: Some(parse_swap_instruction),
requires_inner_instruction: false,
},
GenericEventParseConfig {
program_id: RAYDIUM_CLMM_PROGRAM_ID,
protocol_type: ProtocolType::RaydiumClmm,
inner_instruction_discriminator: &[],
instruction_discriminator: discriminators::SWAP_V2,
event_type: EventType::RaydiumClmmSwapV2,
inner_instruction_parser: None,
instruction_parser: Some(parse_swap_v2_instruction),
requires_inner_instruction: false,
},
GenericEventParseConfig {
program_id: RAYDIUM_CLMM_PROGRAM_ID,
protocol_type: ProtocolType::RaydiumClmm,
inner_instruction_discriminator: &[],
instruction_discriminator: discriminators::CLOSE_POSITION,
event_type: EventType::RaydiumClmmClosePosition,
inner_instruction_parser: None,
instruction_parser: Some(parse_close_position_instruction),
requires_inner_instruction: false,
},
GenericEventParseConfig {
program_id: RAYDIUM_CLMM_PROGRAM_ID,
protocol_type: ProtocolType::RaydiumClmm,
inner_instruction_discriminator: &[],
instruction_discriminator: discriminators::DECREASE_LIQUIDITY_V2,
event_type: EventType::RaydiumClmmDecreaseLiquidityV2,
inner_instruction_parser: None,
instruction_parser: Some(parse_decrease_liquidity_v2_instruction),
requires_inner_instruction: false,
},
GenericEventParseConfig {
program_id: RAYDIUM_CLMM_PROGRAM_ID,
protocol_type: ProtocolType::RaydiumClmm,
inner_instruction_discriminator: &[],
instruction_discriminator: discriminators::CREATE_POOL,
event_type: EventType::RaydiumClmmCreatePool,
inner_instruction_parser: None,
instruction_parser: Some(parse_create_pool_instruction),
requires_inner_instruction: false,
},
GenericEventParseConfig {
program_id: RAYDIUM_CLMM_PROGRAM_ID,
protocol_type: ProtocolType::RaydiumClmm,
inner_instruction_discriminator: &[],
instruction_discriminator: discriminators::INCREASE_LIQUIDITY_V2,
event_type: EventType::RaydiumClmmIncreaseLiquidityV2,
inner_instruction_parser: None,
instruction_parser: Some(parse_increase_liquidity_v2_instruction),
requires_inner_instruction: false,
},
GenericEventParseConfig {
program_id: RAYDIUM_CLMM_PROGRAM_ID,
protocol_type: ProtocolType::RaydiumClmm,
inner_instruction_discriminator: &[],
instruction_discriminator: discriminators::OPEN_POSITION_WITH_TOKEN_22_NFT,
event_type: EventType::RaydiumClmmOpenPositionWithToken22Nft,
inner_instruction_parser: None,
instruction_parser: Some(parse_open_position_with_token_22_nft_instruction),
requires_inner_instruction: false,
},
GenericEventParseConfig {
program_id: RAYDIUM_CLMM_PROGRAM_ID,
protocol_type: ProtocolType::RaydiumClmm,
inner_instruction_discriminator: &[],
instruction_discriminator: discriminators::OPEN_POSITION_V2,
event_type: EventType::RaydiumClmmOpenPositionV2,
inner_instruction_parser: None,
instruction_parser: Some(parse_open_position_v2_instruction),
requires_inner_instruction: false,
},
];
/// 解析打开仓位V2指令事件
fn parse_open_position_v2_instruction(
data: &[u8],
accounts: &[Pubkey],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if data.len() < 51 || accounts.len() < 22 {
return None;
}
Some(Box::new(RaydiumClmmOpenPositionV2Event {
metadata,
tick_lower_index: read_i32_le(data, 0)?,
tick_upper_index: read_i32_le(data, 4)?,
tick_array_lower_start_index: read_i32_le(data, 8)?,
tick_array_upper_start_index: read_i32_le(data, 12)?,
liquidity: read_u128_le(data, 16)?,
amount0_max: read_u64_le(data, 32)?,
amount1_max: read_u64_le(data, 40)?,
with_metadata: read_u8_le(data, 48)? == 1,
base_flag: read_option_bool(data, &mut 49)?,
payer: accounts[0],
position_nft_owner: accounts[1],
position_nft_mint: accounts[2],
position_nft_account: accounts[3],
metadata_account: accounts[4],
pool_state: accounts[5],
protocol_position: accounts[6],
tick_array_lower: accounts[7],
tick_array_upper: accounts[8],
personal_position: accounts[9],
token_account0: accounts[10],
token_account1: accounts[11],
token_vault0: accounts[12],
token_vault1: accounts[13],
rent: accounts[14],
system_program: accounts[15],
token_program: accounts[16],
associated_token_program: accounts[17],
metadata_program: accounts[18],
token_program2022: accounts[19],
vault0_mint: accounts[20],
vault1_mint: accounts[21],
remaining_accounts: accounts[22..].to_vec(),
}))
}
impl Default for RaydiumClmmEventParser {
fn default() -> Self {
Self::new()
/// 解析打开仓位v2指令事件
fn parse_open_position_with_token_22_nft_instruction(
data: &[u8],
accounts: &[Pubkey],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if data.len() < 51 || accounts.len() < 20 {
return None;
}
Some(Box::new(RaydiumClmmOpenPositionWithToken22NftEvent {
metadata,
tick_lower_index: read_i32_le(data, 0)?,
tick_upper_index: read_i32_le(data, 4)?,
tick_array_lower_start_index: read_i32_le(data, 8)?,
tick_array_upper_start_index: read_i32_le(data, 12)?,
liquidity: read_u128_le(data, 16)?,
amount0_max: read_u64_le(data, 32)?,
amount1_max: read_u64_le(data, 40)?,
with_metadata: read_u8_le(data, 48)? == 1,
base_flag: read_option_bool(data, &mut 49)?,
payer: accounts[0],
position_nft_owner: accounts[1],
position_nft_mint: accounts[2],
position_nft_account: accounts[3],
pool_state: accounts[4],
protocol_position: accounts[5],
tick_array_lower: accounts[6],
tick_array_upper: accounts[7],
personal_position: accounts[8],
token_account0: accounts[9],
token_account1: accounts[10],
token_vault0: accounts[11],
token_vault1: accounts[12],
rent: accounts[13],
system_program: accounts[14],
token_program: accounts[15],
associated_token_program: accounts[16],
token_program2022: accounts[17],
vault0_mint: accounts[18],
vault1_mint: accounts[19],
}))
}
impl RaydiumClmmEventParser {
pub fn new() -> Self {
// 配置所有事件类型
let configs = vec![
GenericEventParseConfig {
program_id: RAYDIUM_CLMM_PROGRAM_ID,
protocol_type: ProtocolType::RaydiumClmm,
inner_instruction_discriminator: &[],
instruction_discriminator: discriminators::SWAP,
event_type: EventType::RaydiumClmmSwap,
inner_instruction_parser: None,
instruction_parser: Some(Self::parse_swap_instruction),
requires_inner_instruction: false,
},
GenericEventParseConfig {
program_id: RAYDIUM_CLMM_PROGRAM_ID,
protocol_type: ProtocolType::RaydiumClmm,
inner_instruction_discriminator: &[],
instruction_discriminator: discriminators::SWAP_V2,
event_type: EventType::RaydiumClmmSwapV2,
inner_instruction_parser: None,
instruction_parser: Some(Self::parse_swap_v2_instruction),
requires_inner_instruction: false,
},
GenericEventParseConfig {
program_id: RAYDIUM_CLMM_PROGRAM_ID,
protocol_type: ProtocolType::RaydiumClmm,
inner_instruction_discriminator: &[],
instruction_discriminator: discriminators::CLOSE_POSITION,
event_type: EventType::RaydiumClmmClosePosition,
inner_instruction_parser: None,
instruction_parser: Some(Self::parse_close_position_instruction),
requires_inner_instruction: false,
},
GenericEventParseConfig {
program_id: RAYDIUM_CLMM_PROGRAM_ID,
protocol_type: ProtocolType::RaydiumClmm,
inner_instruction_discriminator: &[],
instruction_discriminator: discriminators::DECREASE_LIQUIDITY_V2,
event_type: EventType::RaydiumClmmDecreaseLiquidityV2,
inner_instruction_parser: None,
instruction_parser: Some(Self::parse_decrease_liquidity_v2_instruction),
requires_inner_instruction: false,
},
GenericEventParseConfig {
program_id: RAYDIUM_CLMM_PROGRAM_ID,
protocol_type: ProtocolType::RaydiumClmm,
inner_instruction_discriminator: &[],
instruction_discriminator: discriminators::CREATE_POOL,
event_type: EventType::RaydiumClmmCreatePool,
inner_instruction_parser: None,
instruction_parser: Some(Self::parse_create_pool_instruction),
requires_inner_instruction: false,
},
GenericEventParseConfig {
program_id: RAYDIUM_CLMM_PROGRAM_ID,
protocol_type: ProtocolType::RaydiumClmm,
inner_instruction_discriminator: &[],
instruction_discriminator: discriminators::INCREASE_LIQUIDITY_V2,
event_type: EventType::RaydiumClmmIncreaseLiquidityV2,
inner_instruction_parser: None,
instruction_parser: Some(Self::parse_increase_liquidity_v2_instruction),
requires_inner_instruction: false,
},
GenericEventParseConfig {
program_id: RAYDIUM_CLMM_PROGRAM_ID,
protocol_type: ProtocolType::RaydiumClmm,
inner_instruction_discriminator: &[],
instruction_discriminator: discriminators::OPEN_POSITION_WITH_TOKEN_22_NFT,
event_type: EventType::RaydiumClmmOpenPositionWithToken22Nft,
inner_instruction_parser: None,
instruction_parser: Some(Self::parse_open_position_with_token_22_nft_instruction),
requires_inner_instruction: false,
},
GenericEventParseConfig {
program_id: RAYDIUM_CLMM_PROGRAM_ID,
protocol_type: ProtocolType::RaydiumClmm,
inner_instruction_discriminator: &[],
instruction_discriminator: discriminators::OPEN_POSITION_V2,
event_type: EventType::RaydiumClmmOpenPositionV2,
inner_instruction_parser: None,
instruction_parser: Some(Self::parse_open_position_v2_instruction),
requires_inner_instruction: false,
},
];
let inner = GenericEventParser::new(vec![RAYDIUM_CLMM_PROGRAM_ID], configs);
Self { inner }
}
/// 解析打开仓位V2指令事件
fn parse_open_position_v2_instruction(
data: &[u8],
accounts: &[Pubkey],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if data.len() < 51 || accounts.len() < 22 {
return None;
}
Some(Box::new(RaydiumClmmOpenPositionV2Event {
metadata,
tick_lower_index: read_i32_le(data, 0)?,
tick_upper_index: read_i32_le(data, 4)?,
tick_array_lower_start_index: read_i32_le(data, 8)?,
tick_array_upper_start_index: read_i32_le(data, 12)?,
liquidity: read_u128_le(data, 16)?,
amount0_max: read_u64_le(data, 32)?,
amount1_max: read_u64_le(data, 40)?,
with_metadata: read_u8_le(data, 48)? == 1,
base_flag: read_option_bool(data, &mut 49)?,
payer: accounts[0],
position_nft_owner: accounts[1],
position_nft_mint: accounts[2],
position_nft_account: accounts[3],
metadata_account: accounts[4],
pool_state: accounts[5],
protocol_position: accounts[6],
tick_array_lower: accounts[7],
tick_array_upper: accounts[8],
personal_position: accounts[9],
token_account0: accounts[10],
token_account1: accounts[11],
token_vault0: accounts[12],
token_vault1: accounts[13],
rent: accounts[14],
system_program: accounts[15],
token_program: accounts[16],
associated_token_program: accounts[17],
metadata_program: accounts[18],
token_program2022: accounts[19],
vault0_mint: accounts[20],
vault1_mint: accounts[21],
remaining_accounts: accounts[22..].to_vec(),
}))
}
/// 解析打开仓位v2指令事件
fn parse_open_position_with_token_22_nft_instruction(
data: &[u8],
accounts: &[Pubkey],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if data.len() < 51 || accounts.len() < 20 {
return None;
}
Some(Box::new(RaydiumClmmOpenPositionWithToken22NftEvent {
metadata,
tick_lower_index: read_i32_le(data, 0)?,
tick_upper_index: read_i32_le(data, 4)?,
tick_array_lower_start_index: read_i32_le(data, 8)?,
tick_array_upper_start_index: read_i32_le(data, 12)?,
liquidity: read_u128_le(data, 16)?,
amount0_max: read_u64_le(data, 32)?,
amount1_max: read_u64_le(data, 40)?,
with_metadata: read_u8_le(data, 48)? == 1,
base_flag: read_option_bool(data, &mut 49)?,
payer: accounts[0],
position_nft_owner: accounts[1],
position_nft_mint: accounts[2],
position_nft_account: accounts[3],
pool_state: accounts[4],
protocol_position: accounts[5],
tick_array_lower: accounts[6],
tick_array_upper: accounts[7],
personal_position: accounts[8],
token_account0: accounts[9],
token_account1: accounts[10],
token_vault0: accounts[11],
token_vault1: accounts[12],
rent: accounts[13],
system_program: accounts[14],
token_program: accounts[15],
associated_token_program: accounts[16],
token_program2022: accounts[17],
vault0_mint: accounts[18],
vault1_mint: accounts[19],
}))
}
/// 解析增加流动性v2指令事件
fn parse_increase_liquidity_v2_instruction(
data: &[u8],
accounts: &[Pubkey],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if data.len() < 34 || accounts.len() < 15 {
return None;
}
Some(Box::new(RaydiumClmmIncreaseLiquidityV2Event {
metadata,
liquidity: read_u128_le(data, 0)?,
amount0_max: read_u64_le(data, 16)?,
amount1_max: read_u64_le(data, 24)?,
base_flag: read_option_bool(data, &mut 32)?,
nft_owner: accounts[0],
nft_account: accounts[1],
pool_state: accounts[2],
protocol_position: accounts[3],
personal_position: accounts[4],
tick_array_lower: accounts[5],
tick_array_upper: accounts[6],
token_account0: accounts[7],
token_account1: accounts[8],
token_vault0: accounts[9],
token_vault1: accounts[10],
token_program: accounts[11],
token_program2022: accounts[12],
vault0_mint: accounts[13],
vault1_mint: accounts[14],
}))
}
/// 解析创建池指令事件
fn parse_create_pool_instruction(
data: &[u8],
accounts: &[Pubkey],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if data.len() < 24 || accounts.len() < 13 {
return None;
}
Some(Box::new(RaydiumClmmCreatePoolEvent {
metadata,
sqrt_price_x64: read_u128_le(data, 0)?,
open_time: read_u64_le(data, 16)?,
pool_creator: accounts[0],
amm_config: accounts[1],
pool_state: accounts[2],
token_mint0: accounts[3],
token_mint1: accounts[4],
token_vault0: accounts[5],
token_vault1: accounts[6],
observation_state: accounts[7],
tick_array_bitmap: accounts[8],
token_program0: accounts[9],
token_program1: accounts[10],
system_program: accounts[11],
rent: accounts[12],
}))
}
/// 解析减少流动性v2指令事件
fn parse_decrease_liquidity_v2_instruction(
data: &[u8],
accounts: &[Pubkey],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if data.len() < 32 || accounts.len() < 16 {
return None;
}
Some(Box::new(RaydiumClmmDecreaseLiquidityV2Event {
metadata,
liquidity: read_u128_le(data, 0)?,
amount0_min: read_u64_le(data, 16)?,
amount1_min: read_u64_le(data, 24)?,
nft_owner: accounts[0],
nft_account: accounts[1],
personal_position: accounts[2],
pool_state: accounts[3],
protocol_position: accounts[4],
token_vault0: accounts[5],
token_vault1: accounts[6],
tick_array_lower: accounts[7],
tick_array_upper: accounts[8],
recipient_token_account0: accounts[9],
recipient_token_account1: accounts[10],
token_program: accounts[11],
token_program2022: accounts[12],
memo_program: accounts[13],
vault0_mint: accounts[14],
vault1_mint: accounts[15],
remaining_accounts: accounts[16..].to_vec(),
}))
}
/// 解析关闭仓位指令事件
fn parse_close_position_instruction(
_data: &[u8],
accounts: &[Pubkey],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if accounts.len() < 6 {
return None;
}
Some(Box::new(RaydiumClmmClosePositionEvent {
metadata,
nft_owner: accounts[0],
position_nft_mint: accounts[1],
position_nft_account: accounts[2],
personal_position: accounts[3],
system_program: accounts[4],
token_program: accounts[5],
}))
}
/// 解析交易指令事件
fn parse_swap_instruction(
data: &[u8],
accounts: &[Pubkey],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if data.len() < 33 || accounts.len() < 10 {
return None;
}
let amount = read_u64_le(data, 0)?;
let other_amount_threshold = read_u64_le(data, 8)?;
let sqrt_price_limit_x64 = read_u128_le(data, 16)?;
let is_base_input = read_u8_le(data, 32)?;
Some(Box::new(RaydiumClmmSwapEvent {
metadata,
amount,
other_amount_threshold,
sqrt_price_limit_x64,
is_base_input: is_base_input == 1,
payer: accounts[0],
amm_config: accounts[1],
pool_state: accounts[2],
input_token_account: accounts[3],
output_token_account: accounts[4],
input_vault: accounts[5],
output_vault: accounts[6],
observation_state: accounts[7],
token_program: accounts[8],
tick_array: accounts[9],
remaining_accounts: accounts[10..].to_vec(),
}))
}
fn parse_swap_v2_instruction(
data: &[u8],
accounts: &[Pubkey],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if data.len() < 33 || accounts.len() < 13 {
return None;
}
let amount = read_u64_le(data, 0)?;
let other_amount_threshold = read_u64_le(data, 8)?;
let sqrt_price_limit_x64 = read_u128_le(data, 16)?;
let is_base_input = read_u8_le(data, 32)?;
Some(Box::new(RaydiumClmmSwapV2Event {
metadata,
amount,
other_amount_threshold,
sqrt_price_limit_x64,
is_base_input: is_base_input == 1,
payer: accounts[0],
amm_config: accounts[1],
pool_state: accounts[2],
input_token_account: accounts[3],
output_token_account: accounts[4],
input_vault: accounts[5],
output_vault: accounts[6],
observation_state: accounts[7],
token_program: accounts[8],
token_program2022: accounts[9],
memo_program: accounts[10],
input_vault_mint: accounts[11],
output_vault_mint: accounts[12],
remaining_accounts: accounts[13..].to_vec(),
}))
/// 解析增加流动性v2指令事件
fn parse_increase_liquidity_v2_instruction(
data: &[u8],
accounts: &[Pubkey],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if data.len() < 34 || accounts.len() < 15 {
return None;
}
Some(Box::new(RaydiumClmmIncreaseLiquidityV2Event {
metadata,
liquidity: read_u128_le(data, 0)?,
amount0_max: read_u64_le(data, 16)?,
amount1_max: read_u64_le(data, 24)?,
base_flag: read_option_bool(data, &mut 32)?,
nft_owner: accounts[0],
nft_account: accounts[1],
pool_state: accounts[2],
protocol_position: accounts[3],
personal_position: accounts[4],
tick_array_lower: accounts[5],
tick_array_upper: accounts[6],
token_account0: accounts[7],
token_account1: accounts[8],
token_vault0: accounts[9],
token_vault1: accounts[10],
token_program: accounts[11],
token_program2022: accounts[12],
vault0_mint: accounts[13],
vault1_mint: accounts[14],
}))
}
impl_event_parser_delegate!(RaydiumClmmEventParser);
/// 解析创建池指令事件
fn parse_create_pool_instruction(
data: &[u8],
accounts: &[Pubkey],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if data.len() < 24 || accounts.len() < 13 {
return None;
}
Some(Box::new(RaydiumClmmCreatePoolEvent {
metadata,
sqrt_price_x64: read_u128_le(data, 0)?,
open_time: read_u64_le(data, 16)?,
pool_creator: accounts[0],
amm_config: accounts[1],
pool_state: accounts[2],
token_mint0: accounts[3],
token_mint1: accounts[4],
token_vault0: accounts[5],
token_vault1: accounts[6],
observation_state: accounts[7],
tick_array_bitmap: accounts[8],
token_program0: accounts[9],
token_program1: accounts[10],
system_program: accounts[11],
rent: accounts[12],
}))
}
/// 解析减少流动性v2指令事件
fn parse_decrease_liquidity_v2_instruction(
data: &[u8],
accounts: &[Pubkey],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if data.len() < 32 || accounts.len() < 16 {
return None;
}
Some(Box::new(RaydiumClmmDecreaseLiquidityV2Event {
metadata,
liquidity: read_u128_le(data, 0)?,
amount0_min: read_u64_le(data, 16)?,
amount1_min: read_u64_le(data, 24)?,
nft_owner: accounts[0],
nft_account: accounts[1],
personal_position: accounts[2],
pool_state: accounts[3],
protocol_position: accounts[4],
token_vault0: accounts[5],
token_vault1: accounts[6],
tick_array_lower: accounts[7],
tick_array_upper: accounts[8],
recipient_token_account0: accounts[9],
recipient_token_account1: accounts[10],
token_program: accounts[11],
token_program2022: accounts[12],
memo_program: accounts[13],
vault0_mint: accounts[14],
vault1_mint: accounts[15],
remaining_accounts: accounts[16..].to_vec(),
}))
}
/// 解析关闭仓位指令事件
fn parse_close_position_instruction(
_data: &[u8],
accounts: &[Pubkey],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if accounts.len() < 6 {
return None;
}
Some(Box::new(RaydiumClmmClosePositionEvent {
metadata,
nft_owner: accounts[0],
position_nft_mint: accounts[1],
position_nft_account: accounts[2],
personal_position: accounts[3],
system_program: accounts[4],
token_program: accounts[5],
}))
}
/// 解析交易指令事件
fn parse_swap_instruction(
data: &[u8],
accounts: &[Pubkey],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if data.len() < 33 || accounts.len() < 10 {
return None;
}
let amount = read_u64_le(data, 0)?;
let other_amount_threshold = read_u64_le(data, 8)?;
let sqrt_price_limit_x64 = read_u128_le(data, 16)?;
let is_base_input = read_u8_le(data, 32)?;
Some(Box::new(RaydiumClmmSwapEvent {
metadata,
amount,
other_amount_threshold,
sqrt_price_limit_x64,
is_base_input: is_base_input == 1,
payer: accounts[0],
amm_config: accounts[1],
pool_state: accounts[2],
input_token_account: accounts[3],
output_token_account: accounts[4],
input_vault: accounts[5],
output_vault: accounts[6],
observation_state: accounts[7],
token_program: accounts[8],
tick_array: accounts[9],
remaining_accounts: accounts[10..].to_vec(),
}))
}
fn parse_swap_v2_instruction(
data: &[u8],
accounts: &[Pubkey],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if data.len() < 33 || accounts.len() < 13 {
return None;
}
let amount = read_u64_le(data, 0)?;
let other_amount_threshold = read_u64_le(data, 8)?;
let sqrt_price_limit_x64 = read_u128_le(data, 16)?;
let is_base_input = read_u8_le(data, 32)?;
Some(Box::new(RaydiumClmmSwapV2Event {
metadata,
amount,
other_amount_threshold,
sqrt_price_limit_x64,
is_base_input: is_base_input == 1,
payer: accounts[0],
amm_config: accounts[1],
pool_state: accounts[2],
input_token_account: accounts[3],
output_token_account: accounts[4],
input_vault: accounts[5],
output_vault: accounts[6],
observation_state: accounts[7],
token_program: accounts[8],
token_program2022: accounts[9],
memo_program: accounts[10],
input_vault_mint: accounts[11],
output_vault_mint: accounts[12],
remaining_accounts: accounts[13..].to_vec(),
}))
}
@@ -3,4 +3,3 @@ pub mod parser;
pub mod types;
pub use events::*;
pub use parser::RaydiumCpmmEventParser;
@@ -1,257 +1,234 @@
use solana_sdk::pubkey::Pubkey;
use crate::{
impl_event_parser_delegate,
streaming::event_parser::{
common::{read_u64_le, EventMetadata, EventType, ProtocolType},
core::traits::{GenericEventParseConfig, GenericEventParser, UnifiedEvent},
protocols::raydium_cpmm::{
discriminators, RaydiumCpmmDepositEvent, RaydiumCpmmInitializeEvent,
RaydiumCpmmSwapEvent, RaydiumCpmmWithdrawEvent,
},
use crate::streaming::event_parser::{
common::{read_u64_le, EventMetadata, EventType, ProtocolType},
core::event_parser::{EventParser, GenericEventParseConfig},
protocols::raydium_cpmm::{
discriminators, RaydiumCpmmDepositEvent, RaydiumCpmmInitializeEvent, RaydiumCpmmSwapEvent,
RaydiumCpmmWithdrawEvent,
},
UnifiedEvent,
};
/// Raydium CPMM程序ID
pub const RAYDIUM_CPMM_PROGRAM_ID: Pubkey =
solana_sdk::pubkey!("CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C");
/// Raydium CPMM事件解析器
pub struct RaydiumCpmmEventParser {
inner: GenericEventParser,
// 配置所有事件类型
pub const CONFIGS: &[GenericEventParseConfig] = &[
GenericEventParseConfig {
program_id: RAYDIUM_CPMM_PROGRAM_ID,
protocol_type: ProtocolType::RaydiumCpmm,
inner_instruction_discriminator: &[],
instruction_discriminator: discriminators::SWAP_BASE_IN,
event_type: EventType::RaydiumCpmmSwapBaseInput,
inner_instruction_parser: None,
instruction_parser: Some(parse_swap_base_input_instruction),
requires_inner_instruction: false,
},
GenericEventParseConfig {
program_id: RAYDIUM_CPMM_PROGRAM_ID,
protocol_type: ProtocolType::RaydiumCpmm,
inner_instruction_discriminator: &[],
instruction_discriminator: discriminators::SWAP_BASE_OUT,
event_type: EventType::RaydiumCpmmSwapBaseOutput,
inner_instruction_parser: None,
instruction_parser: Some(parse_swap_base_output_instruction),
requires_inner_instruction: false,
},
GenericEventParseConfig {
program_id: RAYDIUM_CPMM_PROGRAM_ID,
protocol_type: ProtocolType::RaydiumCpmm,
inner_instruction_discriminator: &[],
instruction_discriminator: discriminators::DEPOSIT,
event_type: EventType::RaydiumCpmmDeposit,
inner_instruction_parser: None,
instruction_parser: Some(parse_deposit_instruction),
requires_inner_instruction: false,
},
GenericEventParseConfig {
program_id: RAYDIUM_CPMM_PROGRAM_ID,
protocol_type: ProtocolType::RaydiumCpmm,
inner_instruction_discriminator: &[],
instruction_discriminator: discriminators::INITIALIZE,
event_type: EventType::RaydiumCpmmInitialize,
inner_instruction_parser: None,
instruction_parser: Some(parse_initialize_instruction),
requires_inner_instruction: false,
},
GenericEventParseConfig {
program_id: RAYDIUM_CPMM_PROGRAM_ID,
protocol_type: ProtocolType::RaydiumCpmm,
inner_instruction_discriminator: &[],
instruction_discriminator: discriminators::WITHDRAW,
event_type: EventType::RaydiumCpmmWithdraw,
inner_instruction_parser: None,
instruction_parser: Some(parse_withdraw_instruction),
requires_inner_instruction: false,
},
];
/// 解析提款指令事件
fn parse_withdraw_instruction(
data: &[u8],
accounts: &[Pubkey],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if data.len() < 24 || accounts.len() < 14 {
return None;
}
Some(Box::new(RaydiumCpmmWithdrawEvent {
metadata,
lp_token_amount: read_u64_le(data, 0)?,
minimum_token0_amount: read_u64_le(data, 8)?,
minimum_token1_amount: read_u64_le(data, 16)?,
owner: accounts[0],
authority: accounts[1],
pool_state: accounts[2],
owner_lp_token: accounts[3],
token0_account: accounts[4],
token1_account: accounts[5],
token0_vault: accounts[6],
token1_vault: accounts[7],
token_program: accounts[8],
token_program2022: accounts[9],
vault0_mint: accounts[10],
vault1_mint: accounts[11],
lp_mint: accounts[12],
memo_program: accounts[13],
}))
}
impl Default for RaydiumCpmmEventParser {
fn default() -> Self {
Self::new()
/// 解析初始化指令事件
fn parse_initialize_instruction(
data: &[u8],
accounts: &[Pubkey],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if data.len() < 24 || accounts.len() < 20 {
return None;
}
Some(Box::new(RaydiumCpmmInitializeEvent {
metadata,
init_amount0: read_u64_le(data, 0)?,
init_amount1: read_u64_le(data, 8)?,
open_time: read_u64_le(data, 16)?,
creator: accounts[0],
amm_config: accounts[1],
authority: accounts[2],
pool_state: accounts[3],
token0_mint: accounts[4],
token1_mint: accounts[5],
lp_mint: accounts[6],
creator_token0: accounts[7],
creator_token1: accounts[8],
creator_lp_token: accounts[9],
token0_vault: accounts[10],
token1_vault: accounts[11],
create_pool_fee: accounts[12],
observation_state: accounts[13],
token_program: accounts[14],
token0_program: accounts[15],
token1_program: accounts[16],
associated_token_program: accounts[17],
system_program: accounts[18],
rent: accounts[19],
}))
}
impl RaydiumCpmmEventParser {
pub fn new() -> Self {
// 配置所有事件类型
let configs = vec![
GenericEventParseConfig {
program_id: RAYDIUM_CPMM_PROGRAM_ID,
protocol_type: ProtocolType::RaydiumCpmm,
inner_instruction_discriminator: &[],
instruction_discriminator: discriminators::SWAP_BASE_IN,
event_type: EventType::RaydiumCpmmSwapBaseInput,
inner_instruction_parser: None,
instruction_parser: Some(Self::parse_swap_base_input_instruction),
requires_inner_instruction: false,
},
GenericEventParseConfig {
program_id: RAYDIUM_CPMM_PROGRAM_ID,
protocol_type: ProtocolType::RaydiumCpmm,
inner_instruction_discriminator: &[],
instruction_discriminator: discriminators::SWAP_BASE_OUT,
event_type: EventType::RaydiumCpmmSwapBaseOutput,
inner_instruction_parser: None,
instruction_parser: Some(Self::parse_swap_base_output_instruction),
requires_inner_instruction: false,
},
GenericEventParseConfig {
program_id: RAYDIUM_CPMM_PROGRAM_ID,
protocol_type: ProtocolType::RaydiumCpmm,
inner_instruction_discriminator: &[],
instruction_discriminator: discriminators::DEPOSIT,
event_type: EventType::RaydiumCpmmDeposit,
inner_instruction_parser: None,
instruction_parser: Some(Self::parse_deposit_instruction),
requires_inner_instruction: false,
},
GenericEventParseConfig {
program_id: RAYDIUM_CPMM_PROGRAM_ID,
protocol_type: ProtocolType::RaydiumCpmm,
inner_instruction_discriminator: &[],
instruction_discriminator: discriminators::INITIALIZE,
event_type: EventType::RaydiumCpmmInitialize,
inner_instruction_parser: None,
instruction_parser: Some(Self::parse_initialize_instruction),
requires_inner_instruction: false,
},
GenericEventParseConfig {
program_id: RAYDIUM_CPMM_PROGRAM_ID,
protocol_type: ProtocolType::RaydiumCpmm,
inner_instruction_discriminator: &[],
instruction_discriminator: discriminators::WITHDRAW,
event_type: EventType::RaydiumCpmmWithdraw,
inner_instruction_parser: None,
instruction_parser: Some(Self::parse_withdraw_instruction),
requires_inner_instruction: false,
},
];
let inner = GenericEventParser::new(vec![RAYDIUM_CPMM_PROGRAM_ID], configs);
Self { inner }
}
/// 解析提款指令事件
fn parse_withdraw_instruction(
data: &[u8],
accounts: &[Pubkey],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if data.len() < 24 || accounts.len() < 14 {
return None;
}
Some(Box::new(RaydiumCpmmWithdrawEvent {
metadata,
lp_token_amount: read_u64_le(data, 0)?,
minimum_token0_amount: read_u64_le(data, 8)?,
minimum_token1_amount: read_u64_le(data, 16)?,
owner: accounts[0],
authority: accounts[1],
pool_state: accounts[2],
owner_lp_token: accounts[3],
token0_account: accounts[4],
token1_account: accounts[5],
token0_vault: accounts[6],
token1_vault: accounts[7],
token_program: accounts[8],
token_program2022: accounts[9],
vault0_mint: accounts[10],
vault1_mint: accounts[11],
lp_mint: accounts[12],
memo_program: accounts[13],
}))
}
/// 解析初始化指令事件
fn parse_initialize_instruction(
data: &[u8],
accounts: &[Pubkey],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if data.len() < 24 || accounts.len() < 20 {
return None;
}
Some(Box::new(RaydiumCpmmInitializeEvent {
metadata,
init_amount0: read_u64_le(data, 0)?,
init_amount1: read_u64_le(data, 8)?,
open_time: read_u64_le(data, 16)?,
creator: accounts[0],
amm_config: accounts[1],
authority: accounts[2],
pool_state: accounts[3],
token0_mint: accounts[4],
token1_mint: accounts[5],
lp_mint: accounts[6],
creator_token0: accounts[7],
creator_token1: accounts[8],
creator_lp_token: accounts[9],
token0_vault: accounts[10],
token1_vault: accounts[11],
create_pool_fee: accounts[12],
observation_state: accounts[13],
token_program: accounts[14],
token0_program: accounts[15],
token1_program: accounts[16],
associated_token_program: accounts[17],
system_program: accounts[18],
rent: accounts[19],
}))
}
/// 解析存款指令事件
fn parse_deposit_instruction(
data: &[u8],
accounts: &[Pubkey],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if data.len() < 24 || accounts.len() < 13 {
return None;
}
Some(Box::new(RaydiumCpmmDepositEvent {
metadata,
lp_token_amount: read_u64_le(data, 0)?,
maximum_token0_amount: read_u64_le(data, 8)?,
maximum_token1_amount: read_u64_le(data, 16)?,
owner: accounts[0],
authority: accounts[1],
pool_state: accounts[2],
owner_lp_token: accounts[3],
token0_account: accounts[4],
token1_account: accounts[5],
token0_vault: accounts[6],
token1_vault: accounts[7],
token_program: accounts[8],
token_program2022: accounts[9],
vault0_mint: accounts[10],
vault1_mint: accounts[11],
lp_mint: accounts[12],
}))
}
/// 解析买入指令事件
fn parse_swap_base_input_instruction(
data: &[u8],
accounts: &[Pubkey],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if data.len() < 16 || accounts.len() < 13 {
return None;
}
let amount_in = read_u64_le(data, 0)?;
let minimum_amount_out = read_u64_le(data, 8)?;
Some(Box::new(RaydiumCpmmSwapEvent {
metadata,
amount_in,
minimum_amount_out,
payer: accounts[0],
authority: accounts[1],
amm_config: accounts[2],
pool_state: accounts[3],
input_token_account: accounts[4],
output_token_account: accounts[5],
input_vault: accounts[6],
output_vault: accounts[7],
input_token_program: accounts[8],
output_token_program: accounts[9],
input_token_mint: accounts[10],
output_token_mint: accounts[11],
observation_state: accounts[12],
..Default::default()
}))
}
fn parse_swap_base_output_instruction(
data: &[u8],
accounts: &[Pubkey],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if data.len() < 16 || accounts.len() < 13 {
return None;
}
let max_amount_in = read_u64_le(data, 0)?;
let amount_out = read_u64_le(data, 8)?;
Some(Box::new(RaydiumCpmmSwapEvent {
metadata,
max_amount_in,
amount_out,
payer: accounts[0],
authority: accounts[1],
amm_config: accounts[2],
pool_state: accounts[3],
input_token_account: accounts[4],
output_token_account: accounts[5],
input_vault: accounts[6],
output_vault: accounts[7],
input_token_program: accounts[8],
output_token_program: accounts[9],
input_token_mint: accounts[10],
output_token_mint: accounts[11],
observation_state: accounts[12],
..Default::default()
}))
/// 解析存款指令事件
fn parse_deposit_instruction(
data: &[u8],
accounts: &[Pubkey],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if data.len() < 24 || accounts.len() < 13 {
return None;
}
Some(Box::new(RaydiumCpmmDepositEvent {
metadata,
lp_token_amount: read_u64_le(data, 0)?,
maximum_token0_amount: read_u64_le(data, 8)?,
maximum_token1_amount: read_u64_le(data, 16)?,
owner: accounts[0],
authority: accounts[1],
pool_state: accounts[2],
owner_lp_token: accounts[3],
token0_account: accounts[4],
token1_account: accounts[5],
token0_vault: accounts[6],
token1_vault: accounts[7],
token_program: accounts[8],
token_program2022: accounts[9],
vault0_mint: accounts[10],
vault1_mint: accounts[11],
lp_mint: accounts[12],
}))
}
impl_event_parser_delegate!(RaydiumCpmmEventParser);
/// 解析买入指令事件
fn parse_swap_base_input_instruction(
data: &[u8],
accounts: &[Pubkey],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if data.len() < 16 || accounts.len() < 13 {
return None;
}
let amount_in = read_u64_le(data, 0)?;
let minimum_amount_out = read_u64_le(data, 8)?;
Some(Box::new(RaydiumCpmmSwapEvent {
metadata,
amount_in,
minimum_amount_out,
payer: accounts[0],
authority: accounts[1],
amm_config: accounts[2],
pool_state: accounts[3],
input_token_account: accounts[4],
output_token_account: accounts[5],
input_vault: accounts[6],
output_vault: accounts[7],
input_token_program: accounts[8],
output_token_program: accounts[9],
input_token_mint: accounts[10],
output_token_mint: accounts[11],
observation_state: accounts[12],
..Default::default()
}))
}
fn parse_swap_base_output_instruction(
data: &[u8],
accounts: &[Pubkey],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if data.len() < 16 || accounts.len() < 13 {
return None;
}
let max_amount_in = read_u64_le(data, 0)?;
let amount_out = read_u64_le(data, 8)?;
Some(Box::new(RaydiumCpmmSwapEvent {
metadata,
max_amount_in,
amount_out,
payer: accounts[0],
authority: accounts[1],
amm_config: accounts[2],
pool_state: accounts[3],
input_token_account: accounts[4],
output_token_account: accounts[5],
input_vault: accounts[6],
output_vault: accounts[7],
input_token_program: accounts[8],
output_token_program: accounts[9],
input_token_mint: accounts[10],
output_token_mint: accounts[11],
observation_state: accounts[12],
..Default::default()
}))
}
+60
View File
@@ -0,0 +1,60 @@
use crate::streaming::event_parser::protocols::{
bonk::parser::BONK_PROGRAM_ID, pumpfun::parser::PUMPFUN_PROGRAM_ID,
pumpswap::parser::PUMPSWAP_PROGRAM_ID, raydium_amm_v4::parser::RAYDIUM_AMM_V4_PROGRAM_ID,
raydium_clmm::parser::RAYDIUM_CLMM_PROGRAM_ID, raydium_cpmm::parser::RAYDIUM_CPMM_PROGRAM_ID,
};
use anyhow::{anyhow, Result};
use solana_sdk::pubkey::Pubkey;
/// 支持的协议
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Protocol {
PumpSwap,
PumpFun,
Bonk,
RaydiumCpmm,
RaydiumClmm,
RaydiumAmmV4,
}
impl Protocol {
pub fn get_program_id(&self) -> Vec<Pubkey> {
match self {
Protocol::PumpSwap => vec![PUMPSWAP_PROGRAM_ID],
Protocol::PumpFun => vec![PUMPFUN_PROGRAM_ID],
Protocol::Bonk => vec![BONK_PROGRAM_ID],
Protocol::RaydiumCpmm => vec![RAYDIUM_CPMM_PROGRAM_ID],
Protocol::RaydiumClmm => vec![RAYDIUM_CLMM_PROGRAM_ID],
Protocol::RaydiumAmmV4 => vec![RAYDIUM_AMM_V4_PROGRAM_ID],
}
}
}
impl std::fmt::Display for Protocol {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Protocol::PumpSwap => write!(f, "PumpSwap"),
Protocol::PumpFun => write!(f, "PumpFun"),
Protocol::Bonk => write!(f, "Bonk"),
Protocol::RaydiumCpmm => write!(f, "RaydiumCpmm"),
Protocol::RaydiumClmm => write!(f, "RaydiumClmm"),
Protocol::RaydiumAmmV4 => write!(f, "RaydiumAmmV4"),
}
}
}
impl std::str::FromStr for Protocol {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"pumpswap" => Ok(Protocol::PumpSwap),
"pumpfun" => Ok(Protocol::PumpFun),
"bonk" => Ok(Protocol::Bonk),
"raydiumcpmm" => Ok(Protocol::RaydiumCpmm),
"raydiumclmm" => Ok(Protocol::RaydiumClmm),
"raydiumammv4" => Ok(Protocol::RaydiumAmmV4),
_ => Err(anyhow!("Unsupported protocol: {}", s)),
}
}
}