mirror of
https://github.com/0xfnzero/solana-streamer.git
synced 2026-08-13 08:58:05 +00:00
feat: refactor to multi-protocol event streaming system
Major architectural refactor, upgrading from simple logging system to comprehensive multi-protocol Solana DEX event streaming system: ✨ New Features: - Support for 5 DEX protocols: PumpFun, PumpSwap, Bonk, Raydium CPMM, Raydium CLMM - Implement unified event interface (UnifiedEvent trait) and event factory pattern - Add dual streaming support: Yellowstone gRPC and ShredStream - Add Chinese documentation (README_CN.md) 🏗️ Architectural Improvements: - Refactor event parsing system with modular design - Implement protocol-specific parsers and event types - Optimize dependency management, update Cargo.toml - Remove legacy logging modules, clean up redundant code 📊 Statistics: - Added 46 files, 4511 lines of code - Removed 1381 lines of legacy code - Net addition of 3130 lines of code Tech Stack: - Rust async/await for asynchronous processing - Protocol Buffers support - Multi-protocol event parsing - High-performance event stream subscription
This commit is contained in:
+126
@@ -0,0 +1,126 @@
|
||||
use crate::streaming::event_parser::protocols::bonk::types::{
|
||||
CurveParams, MintParams, PoolStatus, TradeDirection, VestingParams,
|
||||
};
|
||||
use crate::streaming::event_parser::common::EventMetadata;
|
||||
use crate::impl_unified_event;
|
||||
use borsh::BorshDeserialize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
|
||||
/// 买入事件
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)]
|
||||
pub struct BonkTradeEvent {
|
||||
#[borsh(skip)]
|
||||
pub metadata: EventMetadata,
|
||||
pub pool_state: Pubkey,
|
||||
pub total_base_sell: u64,
|
||||
pub virtual_base: u64,
|
||||
pub virtual_quote: u64,
|
||||
pub real_base_before: u64,
|
||||
pub real_quote_before: u64,
|
||||
pub real_base_after: u64,
|
||||
pub real_quote_after: u64,
|
||||
pub amount_in: u64,
|
||||
pub amount_out: u64,
|
||||
pub protocol_fee: u64,
|
||||
pub platform_fee: u64,
|
||||
pub share_fee: u64,
|
||||
pub trade_direction: TradeDirection,
|
||||
pub pool_status: PoolStatus,
|
||||
#[borsh(skip)]
|
||||
pub minimum_amount_out: u64,
|
||||
#[borsh(skip)]
|
||||
pub maximum_amount_in: u64,
|
||||
#[borsh(skip)]
|
||||
pub share_fee_rate: u64,
|
||||
#[borsh(skip)]
|
||||
pub payer: Pubkey,
|
||||
#[borsh(skip)]
|
||||
pub user_base_token: Pubkey,
|
||||
#[borsh(skip)]
|
||||
pub user_quote_token: Pubkey,
|
||||
#[borsh(skip)]
|
||||
pub base_vault: Pubkey,
|
||||
#[borsh(skip)]
|
||||
pub quote_vault: Pubkey,
|
||||
#[borsh(skip)]
|
||||
pub base_token_mint: Pubkey,
|
||||
#[borsh(skip)]
|
||||
pub quote_token_mint: Pubkey,
|
||||
#[borsh(skip)]
|
||||
pub is_dev_create_token_trade: bool,
|
||||
#[borsh(skip)]
|
||||
pub is_bot: bool,
|
||||
}
|
||||
|
||||
// 使用宏生成UnifiedEvent实现,指定需要合并的字段
|
||||
impl_unified_event!(
|
||||
BonkTradeEvent,
|
||||
pool_state,
|
||||
total_base_sell,
|
||||
virtual_base,
|
||||
virtual_quote,
|
||||
real_base_before,
|
||||
real_quote_before,
|
||||
real_base_after,
|
||||
real_quote_after,
|
||||
amount_in,
|
||||
amount_out,
|
||||
protocol_fee,
|
||||
platform_fee,
|
||||
share_fee,
|
||||
trade_direction,
|
||||
pool_status
|
||||
);
|
||||
|
||||
/// 创建池事件
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)]
|
||||
pub struct BonkPoolCreateEvent {
|
||||
#[borsh(skip)]
|
||||
pub metadata: EventMetadata,
|
||||
pub pool_state: Pubkey,
|
||||
pub creator: Pubkey,
|
||||
pub config: Pubkey,
|
||||
pub base_mint_param: MintParams,
|
||||
pub curve_param: CurveParams,
|
||||
pub vesting_param: VestingParams,
|
||||
#[borsh(skip)]
|
||||
pub payer: Pubkey,
|
||||
#[borsh(skip)]
|
||||
pub base_mint: Pubkey,
|
||||
#[borsh(skip)]
|
||||
pub quote_mint: Pubkey,
|
||||
#[borsh(skip)]
|
||||
pub base_vault: Pubkey,
|
||||
#[borsh(skip)]
|
||||
pub quote_vault: Pubkey,
|
||||
#[borsh(skip)]
|
||||
pub global_config: Pubkey,
|
||||
#[borsh(skip)]
|
||||
pub platform_config: Pubkey,
|
||||
}
|
||||
|
||||
// 使用宏生成UnifiedEvent实现,指定需要合并的字段
|
||||
impl_unified_event!(
|
||||
BonkPoolCreateEvent,
|
||||
pool_state,
|
||||
creator,
|
||||
config,
|
||||
base_mint_param,
|
||||
curve_param,
|
||||
vesting_param
|
||||
);
|
||||
|
||||
/// 事件鉴别器常量
|
||||
pub mod discriminators {
|
||||
// 事件鉴别器
|
||||
pub const TRADE_EVENT: &str = "0xe445a52e51cb9a1dbddb7fd34ee661ee";
|
||||
pub const POOL_CREATE_EVENT: &str = "0xe445a52e51cb9a1d97d7e20976a173ae";
|
||||
|
||||
// 指令鉴别器
|
||||
pub const BUY_EXACT_IN: &[u8] = &[250, 234, 13, 123, 213, 156, 19, 236];
|
||||
pub const BUY_EXACT_OUT: &[u8] = &[24, 211, 116, 40, 105, 3, 153, 56];
|
||||
pub const SELL_EXACT_IN: &[u8] = &[149, 39, 222, 155, 211, 124, 152, 26];
|
||||
pub const SELL_EXACT_OUT: &[u8] = &[95, 200, 71, 34, 8, 9, 11, 166];
|
||||
pub const INITIALIZE: &[u8] = &[175, 175, 109, 31, 13, 152, 155, 237];
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
pub mod events;
|
||||
pub mod parser;
|
||||
pub mod types;
|
||||
|
||||
pub use events::*;
|
||||
pub use parser::BonkEventParser;
|
||||
pub use types::*;
|
||||
+445
@@ -0,0 +1,445 @@
|
||||
use solana_sdk::{instruction::CompiledInstruction, pubkey::Pubkey};
|
||||
use solana_transaction_status::UiCompiledInstruction;
|
||||
|
||||
use crate::streaming::event_parser::{
|
||||
common::{utils::*, EventMetadata, EventType, ProtocolType},
|
||||
core::traits::{EventParser, GenericEventParseConfig, GenericEventParser, UnifiedEvent},
|
||||
protocols::bonk::{
|
||||
discriminators, BonkPoolCreateEvent, BonkTradeEvent, ConstantCurve, CurveParams,
|
||||
FixedCurve, LinearCurve, MintParams, TradeDirection, VestingParams,
|
||||
},
|
||||
};
|
||||
|
||||
/// Bonk程序ID
|
||||
pub const BONK_PROGRAM_ID: Pubkey =
|
||||
solana_sdk::pubkey!("LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj");
|
||||
|
||||
/// Bonk事件解析器
|
||||
pub struct BonkEventParser {
|
||||
inner: GenericEventParser,
|
||||
}
|
||||
|
||||
impl BonkEventParser {
|
||||
pub fn new() -> Self {
|
||||
// 配置所有事件类型
|
||||
let configs = vec![
|
||||
GenericEventParseConfig {
|
||||
inner_instruction_discriminator: discriminators::TRADE_EVENT,
|
||||
instruction_discriminator: discriminators::BUY_EXACT_IN,
|
||||
event_type: EventType::BonkBuyExactIn,
|
||||
inner_instruction_parser: Self::parse_trade_inner_instruction,
|
||||
instruction_parser: Self::parse_buy_exact_in_instruction,
|
||||
},
|
||||
GenericEventParseConfig {
|
||||
inner_instruction_discriminator: discriminators::TRADE_EVENT,
|
||||
instruction_discriminator: discriminators::BUY_EXACT_OUT,
|
||||
event_type: EventType::BonkBuyExactOut,
|
||||
inner_instruction_parser: Self::parse_trade_inner_instruction,
|
||||
instruction_parser: Self::parse_buy_exact_out_instruction,
|
||||
},
|
||||
GenericEventParseConfig {
|
||||
inner_instruction_discriminator: discriminators::TRADE_EVENT,
|
||||
instruction_discriminator: discriminators::SELL_EXACT_IN,
|
||||
event_type: EventType::BonkSellExactIn,
|
||||
inner_instruction_parser: Self::parse_trade_inner_instruction,
|
||||
instruction_parser: Self::parse_sell_exact_in_instruction,
|
||||
},
|
||||
GenericEventParseConfig {
|
||||
inner_instruction_discriminator: discriminators::TRADE_EVENT,
|
||||
instruction_discriminator: discriminators::SELL_EXACT_OUT,
|
||||
event_type: EventType::BonkSellExactOut,
|
||||
inner_instruction_parser: Self::parse_trade_inner_instruction,
|
||||
instruction_parser: Self::parse_sell_exact_out_instruction,
|
||||
},
|
||||
GenericEventParseConfig {
|
||||
inner_instruction_discriminator: discriminators::POOL_CREATE_EVENT,
|
||||
instruction_discriminator: discriminators::INITIALIZE,
|
||||
event_type: EventType::BonkInitialize,
|
||||
inner_instruction_parser: Self::parse_pool_create_inner_instruction,
|
||||
instruction_parser: Self::parse_initialize_instruction,
|
||||
},
|
||||
];
|
||||
|
||||
let inner = GenericEventParser::new(BONK_PROGRAM_ID, ProtocolType::Bonk, configs);
|
||||
|
||||
Self { inner }
|
||||
}
|
||||
|
||||
/// 解析创建池事件
|
||||
fn parse_pool_create_inner_instruction(
|
||||
data: &[u8],
|
||||
metadata: EventMetadata,
|
||||
) -> Option<Box<dyn UnifiedEvent>> {
|
||||
if let Ok(event) = borsh::from_slice::<BonkPoolCreateEvent>(data) {
|
||||
let mut metadata = metadata;
|
||||
metadata.set_id(format!("{}", metadata.signature,));
|
||||
Some(Box::new(BonkPoolCreateEvent {
|
||||
metadata: metadata,
|
||||
..event
|
||||
}))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// 解析交易事件
|
||||
fn parse_trade_inner_instruction(
|
||||
data: &[u8],
|
||||
metadata: EventMetadata,
|
||||
) -> Option<Box<dyn UnifiedEvent>> {
|
||||
if let Ok(event) = borsh::from_slice::<BonkTradeEvent>(data) {
|
||||
let mut metadata = metadata;
|
||||
metadata.set_id(format!(
|
||||
"{}-{}",
|
||||
metadata.signature,
|
||||
event.pool_state.to_string()
|
||||
));
|
||||
if metadata.event_type == EventType::BonkBuyExactIn
|
||||
|| metadata.event_type == EventType::BonkBuyExactOut
|
||||
{
|
||||
if event.trade_direction != TradeDirection::Buy {
|
||||
return None;
|
||||
}
|
||||
} else if metadata.event_type == EventType::BonkSellExactIn
|
||||
|| metadata.event_type == EventType::BonkSellExactOut
|
||||
{
|
||||
if event.trade_direction != TradeDirection::Sell {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
Some(Box::new(BonkTradeEvent {
|
||||
metadata: metadata,
|
||||
..event
|
||||
}))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// 解析买入指令事件
|
||||
fn parse_buy_exact_in_instruction(
|
||||
data: &[u8],
|
||||
accounts: &[Pubkey],
|
||||
metadata: EventMetadata,
|
||||
) -> Option<Box<dyn UnifiedEvent>> {
|
||||
if data.len() < 16 || accounts.len() < 11 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let amount_in = read_u64_le(data, 0)?;
|
||||
let minimum_amount_out = read_u64_le(data, 8)?;
|
||||
let share_fee_rate = read_u64_le(data, 16)?;
|
||||
|
||||
let mut metadata = metadata;
|
||||
metadata.set_id(format!("{}-{}", metadata.signature, accounts[4]));
|
||||
|
||||
Some(Box::new(BonkTradeEvent {
|
||||
metadata,
|
||||
amount_in,
|
||||
minimum_amount_out,
|
||||
share_fee_rate,
|
||||
payer: accounts[0],
|
||||
pool_state: accounts[4],
|
||||
user_base_token: accounts[5],
|
||||
user_quote_token: accounts[6],
|
||||
base_vault: accounts[7],
|
||||
quote_vault: accounts[8],
|
||||
base_token_mint: accounts[9],
|
||||
quote_token_mint: accounts[10],
|
||||
trade_direction: TradeDirection::Buy,
|
||||
..Default::default()
|
||||
}))
|
||||
}
|
||||
|
||||
fn parse_buy_exact_out_instruction(
|
||||
data: &[u8],
|
||||
accounts: &[Pubkey],
|
||||
metadata: EventMetadata,
|
||||
) -> Option<Box<dyn UnifiedEvent>> {
|
||||
if data.len() < 16 || accounts.len() < 11 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let amount_out = read_u64_le(data, 0)?;
|
||||
let maximum_amount_in = read_u64_le(data, 8)?;
|
||||
let share_fee_rate = read_u64_le(data, 16)?;
|
||||
|
||||
let mut metadata = metadata;
|
||||
metadata.set_id(format!("{}-{}", metadata.signature, accounts[4]));
|
||||
|
||||
Some(Box::new(BonkTradeEvent {
|
||||
metadata,
|
||||
amount_out,
|
||||
maximum_amount_in,
|
||||
share_fee_rate,
|
||||
payer: accounts[0],
|
||||
pool_state: accounts[4],
|
||||
user_base_token: accounts[5],
|
||||
user_quote_token: accounts[6],
|
||||
base_vault: accounts[7],
|
||||
quote_vault: accounts[8],
|
||||
base_token_mint: accounts[9],
|
||||
quote_token_mint: accounts[10],
|
||||
trade_direction: TradeDirection::Buy,
|
||||
..Default::default()
|
||||
}))
|
||||
}
|
||||
|
||||
fn parse_sell_exact_in_instruction(
|
||||
data: &[u8],
|
||||
accounts: &[Pubkey],
|
||||
metadata: EventMetadata,
|
||||
) -> Option<Box<dyn UnifiedEvent>> {
|
||||
if data.len() < 16 || accounts.len() < 11 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let amount_in = read_u64_le(data, 0)?;
|
||||
let minimum_amount_out = read_u64_le(data, 8)?;
|
||||
let share_fee_rate = read_u64_le(data, 16)?;
|
||||
|
||||
let mut metadata = metadata;
|
||||
metadata.set_id(format!("{}-{}", metadata.signature, accounts[4]));
|
||||
|
||||
Some(Box::new(BonkTradeEvent {
|
||||
metadata,
|
||||
amount_in,
|
||||
minimum_amount_out,
|
||||
share_fee_rate,
|
||||
payer: accounts[0],
|
||||
pool_state: accounts[4],
|
||||
user_base_token: accounts[5],
|
||||
user_quote_token: accounts[6],
|
||||
base_vault: accounts[7],
|
||||
quote_vault: accounts[8],
|
||||
base_token_mint: accounts[9],
|
||||
quote_token_mint: accounts[10],
|
||||
trade_direction: TradeDirection::Sell,
|
||||
..Default::default()
|
||||
}))
|
||||
}
|
||||
|
||||
fn parse_sell_exact_out_instruction(
|
||||
data: &[u8],
|
||||
accounts: &[Pubkey],
|
||||
metadata: EventMetadata,
|
||||
) -> Option<Box<dyn UnifiedEvent>> {
|
||||
if data.len() < 16 || accounts.len() < 11 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let amount_out = read_u64_le(data, 0)?;
|
||||
let maximum_amount_in = read_u64_le(data, 8)?;
|
||||
let share_fee_rate = read_u64_le(data, 16)?;
|
||||
|
||||
let mut metadata = metadata;
|
||||
metadata.set_id(format!("{}-{}", metadata.signature, accounts[4]));
|
||||
|
||||
Some(Box::new(BonkTradeEvent {
|
||||
metadata,
|
||||
amount_out,
|
||||
maximum_amount_in,
|
||||
share_fee_rate,
|
||||
payer: accounts[0],
|
||||
pool_state: accounts[4],
|
||||
user_base_token: accounts[5],
|
||||
user_quote_token: accounts[6],
|
||||
base_vault: accounts[7],
|
||||
quote_vault: accounts[8],
|
||||
base_token_mint: accounts[9],
|
||||
quote_token_mint: accounts[10],
|
||||
trade_direction: TradeDirection::Sell,
|
||||
..Default::default()
|
||||
}))
|
||||
}
|
||||
|
||||
/// 解析初始化事件
|
||||
fn parse_initialize_instruction(
|
||||
data: &[u8],
|
||||
accounts: &[Pubkey],
|
||||
metadata: EventMetadata,
|
||||
) -> Option<Box<dyn UnifiedEvent>> {
|
||||
if data.len() < 24 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut offset = 0;
|
||||
let base_mint_param = Self::parse_mint_params(data, &mut offset)?;
|
||||
let curve_param = Self::parse_curve_params(data, &mut offset)?;
|
||||
let vesting_param = Self::parse_vesting_params(data, &mut offset)?;
|
||||
|
||||
let mut metadata = metadata;
|
||||
metadata.set_id(format!("{}", metadata.signature));
|
||||
|
||||
Some(Box::new(BonkPoolCreateEvent {
|
||||
metadata,
|
||||
payer: accounts[0],
|
||||
creator: accounts[1],
|
||||
global_config: accounts[2],
|
||||
platform_config: accounts[3],
|
||||
pool_state: accounts[5],
|
||||
base_mint: accounts[6],
|
||||
quote_mint: accounts[7],
|
||||
base_vault: accounts[8],
|
||||
quote_vault: accounts[9],
|
||||
base_mint_param,
|
||||
curve_param,
|
||||
vesting_param,
|
||||
..Default::default()
|
||||
}))
|
||||
}
|
||||
|
||||
/// 解析 MintParams 结构
|
||||
fn parse_mint_params(data: &[u8], offset: &mut usize) -> Option<MintParams> {
|
||||
// 读取decimals (1字节)
|
||||
let decimals = read_u8(data, *offset)?;
|
||||
*offset += 1;
|
||||
|
||||
// 读取name字符串长度和内容
|
||||
let name_len = read_u32_le(data, *offset)? as usize;
|
||||
*offset += 4;
|
||||
if data.len() < *offset + name_len {
|
||||
return None;
|
||||
}
|
||||
let name = String::from_utf8(data[*offset..*offset + name_len].to_vec()).ok()?;
|
||||
*offset += name_len;
|
||||
|
||||
// 读取symbol字符串长度和内容
|
||||
let symbol_len = read_u32_le(data, *offset)? as usize;
|
||||
*offset += 4;
|
||||
if data.len() < *offset + symbol_len {
|
||||
return None;
|
||||
}
|
||||
let symbol = String::from_utf8(data[*offset..*offset + symbol_len].to_vec()).ok()?;
|
||||
*offset += symbol_len;
|
||||
|
||||
// 读取uri字符串长度和内容
|
||||
let uri_len = read_u32_le(data, *offset)? as usize;
|
||||
*offset += 4;
|
||||
if data.len() < *offset + uri_len {
|
||||
return None;
|
||||
}
|
||||
let uri = String::from_utf8(data[*offset..*offset + uri_len].to_vec()).ok()?;
|
||||
*offset += uri_len;
|
||||
|
||||
Some(MintParams {
|
||||
decimals,
|
||||
name,
|
||||
symbol,
|
||||
uri,
|
||||
})
|
||||
}
|
||||
|
||||
/// 解析 CurveParams 结构
|
||||
fn parse_curve_params(data: &[u8], offset: &mut usize) -> Option<CurveParams> {
|
||||
// 读取curve类型标识符 (1字节)
|
||||
let curve_type = read_u8(data, *offset)?;
|
||||
*offset += 1;
|
||||
|
||||
match curve_type {
|
||||
0 => {
|
||||
// Constant curve
|
||||
let supply = read_u64_le(data, *offset)?;
|
||||
*offset += 8;
|
||||
let total_base_sell = read_u64_le(data, *offset)?;
|
||||
*offset += 8;
|
||||
let total_quote_fund_raising = read_u64_le(data, *offset)?;
|
||||
*offset += 8;
|
||||
let migrate_type = read_u8(data, *offset)?;
|
||||
*offset += 1;
|
||||
|
||||
Some(CurveParams::Constant {
|
||||
data: ConstantCurve {
|
||||
supply,
|
||||
total_base_sell,
|
||||
total_quote_fund_raising,
|
||||
migrate_type,
|
||||
},
|
||||
})
|
||||
}
|
||||
1 => {
|
||||
// Fixed curve
|
||||
let supply = read_u64_le(data, *offset)?;
|
||||
*offset += 8;
|
||||
let total_quote_fund_raising = read_u64_le(data, *offset)?;
|
||||
*offset += 8;
|
||||
let migrate_type = read_u8(data, *offset)?;
|
||||
*offset += 1;
|
||||
|
||||
Some(CurveParams::Fixed {
|
||||
data: FixedCurve {
|
||||
supply,
|
||||
total_quote_fund_raising,
|
||||
migrate_type,
|
||||
},
|
||||
})
|
||||
}
|
||||
2 => {
|
||||
// Linear curve
|
||||
let supply = read_u64_le(data, *offset)?;
|
||||
*offset += 8;
|
||||
let total_quote_fund_raising = read_u64_le(data, *offset)?;
|
||||
*offset += 8;
|
||||
let migrate_type = read_u8(data, *offset)?;
|
||||
*offset += 1;
|
||||
|
||||
Some(CurveParams::Linear {
|
||||
data: LinearCurve {
|
||||
supply,
|
||||
total_quote_fund_raising,
|
||||
migrate_type,
|
||||
},
|
||||
})
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// 解析 VestingParams 结构
|
||||
fn parse_vesting_params(data: &[u8], offset: &mut usize) -> Option<VestingParams> {
|
||||
let total_locked_amount = read_u64_le(data, *offset)?;
|
||||
*offset += 8;
|
||||
let cliff_period = read_u64_le(data, *offset)?;
|
||||
*offset += 8;
|
||||
let unlock_period = read_u64_le(data, *offset)?;
|
||||
*offset += 8;
|
||||
|
||||
Some(VestingParams {
|
||||
total_locked_amount,
|
||||
cliff_period,
|
||||
unlock_period,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl EventParser for BonkEventParser {
|
||||
fn parse_events_from_inner_instruction(
|
||||
&self,
|
||||
inner_instruction: &UiCompiledInstruction,
|
||||
signature: &str,
|
||||
slot: u64,
|
||||
) -> Vec<Box<dyn UnifiedEvent>> {
|
||||
self.inner
|
||||
.parse_events_from_inner_instruction(inner_instruction, signature, slot)
|
||||
}
|
||||
|
||||
fn parse_events_from_instruction(
|
||||
&self,
|
||||
instruction: &CompiledInstruction,
|
||||
accounts: &[Pubkey],
|
||||
signature: &str,
|
||||
slot: u64,
|
||||
) -> Vec<Box<dyn UnifiedEvent>> {
|
||||
self.inner
|
||||
.parse_events_from_instruction(instruction, accounts, signature, slot)
|
||||
}
|
||||
|
||||
fn should_handle(&self, program_id: &Pubkey) -> bool {
|
||||
self.inner.should_handle(program_id)
|
||||
}
|
||||
|
||||
fn supported_program_ids(&self) -> Vec<Pubkey> {
|
||||
self.inner.supported_program_ids()
|
||||
}
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
use borsh::BorshDeserialize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)]
|
||||
pub enum TradeDirection {
|
||||
#[default]
|
||||
Buy,
|
||||
Sell,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)]
|
||||
pub enum PoolStatus {
|
||||
#[default]
|
||||
Fund,
|
||||
Migrate,
|
||||
Trade,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)]
|
||||
pub struct MintParams {
|
||||
pub decimals: u8,
|
||||
pub name: String,
|
||||
pub symbol: String,
|
||||
pub uri: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)]
|
||||
pub struct VestingParams {
|
||||
pub total_locked_amount: u64,
|
||||
pub cliff_period: u64,
|
||||
pub unlock_period: u64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)]
|
||||
pub struct ConstantCurve {
|
||||
pub supply: u64,
|
||||
pub total_base_sell: u64,
|
||||
pub total_quote_fund_raising: u64,
|
||||
pub migrate_type: u8,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)]
|
||||
pub struct FixedCurve {
|
||||
pub supply: u64,
|
||||
pub total_quote_fund_raising: u64,
|
||||
pub migrate_type: u8,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)]
|
||||
pub struct LinearCurve {
|
||||
pub supply: u64,
|
||||
pub total_quote_fund_raising: u64,
|
||||
pub migrate_type: u8,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)]
|
||||
pub enum CurveParams {
|
||||
Constant { data: ConstantCurve },
|
||||
Fixed { data: FixedCurve },
|
||||
Linear { data: LinearCurve },
|
||||
}
|
||||
|
||||
impl Default for CurveParams {
|
||||
fn default() -> Self {
|
||||
Self::Constant {
|
||||
data: ConstantCurve::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+11
@@ -0,0 +1,11 @@
|
||||
pub mod pumpfun;
|
||||
pub mod pumpswap;
|
||||
pub mod bonk;
|
||||
pub mod raydium_cpmm;
|
||||
pub mod raydium_clmm;
|
||||
|
||||
pub use pumpfun::PumpFunEventParser;
|
||||
pub use pumpswap::PumpSwapEventParser;
|
||||
pub use bonk::BonkEventParser;
|
||||
pub use raydium_cpmm::RaydiumCpmmEventParser;
|
||||
pub use raydium_clmm::RaydiumClmmEventParser;
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
use borsh::BorshDeserialize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
|
||||
use crate::streaming::event_parser::common::EventMetadata;
|
||||
use crate::impl_unified_event;
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)]
|
||||
pub struct PumpFunCreateTokenEvent {
|
||||
#[borsh(skip)]
|
||||
pub metadata: EventMetadata,
|
||||
pub name: String,
|
||||
pub symbol: String,
|
||||
pub uri: String,
|
||||
pub mint: Pubkey,
|
||||
pub bonding_curve: Pubkey,
|
||||
pub user: Pubkey,
|
||||
pub creator: Pubkey,
|
||||
pub timestamp: i64,
|
||||
pub virtual_token_reserves: u64,
|
||||
pub virtual_sol_reserves: u64,
|
||||
pub real_token_reserves: u64,
|
||||
pub token_total_supply: u64,
|
||||
#[borsh(skip)]
|
||||
pub mint_authority: Pubkey,
|
||||
#[borsh(skip)]
|
||||
pub associated_bonding_curve: Pubkey,
|
||||
}
|
||||
|
||||
impl_unified_event!(
|
||||
PumpFunCreateTokenEvent,
|
||||
mint,
|
||||
bonding_curve,
|
||||
user,
|
||||
creator,
|
||||
timestamp,
|
||||
virtual_token_reserves,
|
||||
virtual_sol_reserves,
|
||||
real_token_reserves,
|
||||
token_total_supply
|
||||
);
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)]
|
||||
pub struct PumpFunTradeEvent {
|
||||
#[borsh(skip)]
|
||||
pub metadata: EventMetadata,
|
||||
pub mint: Pubkey,
|
||||
pub sol_amount: u64,
|
||||
pub token_amount: u64,
|
||||
pub is_buy: bool,
|
||||
pub user: Pubkey,
|
||||
pub timestamp: i64,
|
||||
pub virtual_sol_reserves: u64,
|
||||
pub virtual_token_reserves: u64,
|
||||
pub real_sol_reserves: u64,
|
||||
pub real_token_reserves: u64,
|
||||
pub fee_recipient: Pubkey,
|
||||
pub fee_basis_points: u64,
|
||||
pub fee: u64,
|
||||
pub creator: Pubkey,
|
||||
pub creator_fee_basis_points: u64,
|
||||
pub creator_fee: u64,
|
||||
#[borsh(skip)]
|
||||
pub bonding_curve: Pubkey,
|
||||
#[borsh(skip)]
|
||||
pub associated_bonding_curve: Pubkey,
|
||||
#[borsh(skip)]
|
||||
pub associated_user: Pubkey,
|
||||
#[borsh(skip)]
|
||||
pub creator_vault: Pubkey,
|
||||
#[borsh(skip)]
|
||||
pub max_sol_cost: u64,
|
||||
#[borsh(skip)]
|
||||
pub min_sol_output: u64,
|
||||
#[borsh(skip)]
|
||||
pub amount: u64,
|
||||
#[borsh(skip)]
|
||||
pub is_bot: bool,
|
||||
#[borsh(skip)]
|
||||
pub is_dev_create_token_trade: bool, // 是否是dev创建token的交易
|
||||
}
|
||||
|
||||
impl_unified_event!(
|
||||
PumpFunTradeEvent,
|
||||
mint,
|
||||
sol_amount,
|
||||
token_amount,
|
||||
is_buy,
|
||||
user,
|
||||
timestamp,
|
||||
virtual_sol_reserves,
|
||||
virtual_token_reserves,
|
||||
real_sol_reserves,
|
||||
real_token_reserves,
|
||||
fee_recipient,
|
||||
fee_basis_points,
|
||||
fee,
|
||||
creator,
|
||||
creator_fee_basis_points,
|
||||
creator_fee
|
||||
);
|
||||
|
||||
/// 事件鉴别器常量
|
||||
pub mod discriminators {
|
||||
// 事件鉴别器
|
||||
pub const CREATE_TOKEN_EVENT: &str = "0xe445a52e51cb9a1d1b72a94ddeeb6376";
|
||||
pub const TRADE_EVENT: &str = "0xe445a52e51cb9a1dbddb7fd34ee661ee";
|
||||
|
||||
// 指令鉴别器
|
||||
pub const CREATE_TOKEN_IX: &[u8] = &[24, 30, 200, 40, 5, 28, 7, 119];
|
||||
pub const BUY_IX: &[u8] = &[102, 6, 61, 18, 1, 218, 235, 234];
|
||||
pub const SELL_IX: &[u8] = &[51, 230, 133, 164, 1, 127, 131, 173];
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
pub mod events;
|
||||
pub mod parser;
|
||||
|
||||
pub use events::*;
|
||||
pub use parser::PumpFunEventParser;
|
||||
+250
@@ -0,0 +1,250 @@
|
||||
use solana_sdk::{instruction::CompiledInstruction, pubkey::Pubkey};
|
||||
use solana_transaction_status::UiCompiledInstruction;
|
||||
|
||||
use crate::streaming::event_parser::{
|
||||
common::{EventMetadata, EventType, ProtocolType},
|
||||
core::traits::{EventParser, GenericEventParseConfig, GenericEventParser, UnifiedEvent},
|
||||
protocols::pumpfun::{discriminators, PumpFunCreateTokenEvent, PumpFunTradeEvent},
|
||||
};
|
||||
|
||||
/// PumpFun程序ID
|
||||
pub const PUMPFUN_PROGRAM_ID: Pubkey =
|
||||
solana_sdk::pubkey!("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P");
|
||||
|
||||
/// PumpFun事件解析器
|
||||
pub struct PumpFunEventParser {
|
||||
inner: GenericEventParser,
|
||||
}
|
||||
|
||||
impl PumpFunEventParser {
|
||||
pub fn new() -> Self {
|
||||
// 配置所有事件类型
|
||||
let configs = vec![
|
||||
GenericEventParseConfig {
|
||||
inner_instruction_discriminator: discriminators::CREATE_TOKEN_EVENT,
|
||||
instruction_discriminator: discriminators::CREATE_TOKEN_IX,
|
||||
event_type: EventType::PumpFunCreateToken,
|
||||
inner_instruction_parser: Self::parse_create_token_inner_instruction,
|
||||
instruction_parser: Self::parse_create_token_instruction,
|
||||
},
|
||||
GenericEventParseConfig {
|
||||
inner_instruction_discriminator: discriminators::TRADE_EVENT,
|
||||
instruction_discriminator: discriminators::BUY_IX,
|
||||
event_type: EventType::PumpFunBuy,
|
||||
inner_instruction_parser: Self::parse_trade_inner_instruction,
|
||||
instruction_parser: Self::parse_buy_instruction,
|
||||
},
|
||||
GenericEventParseConfig {
|
||||
inner_instruction_discriminator: discriminators::TRADE_EVENT,
|
||||
instruction_discriminator: discriminators::SELL_IX,
|
||||
event_type: EventType::PumpFunSell,
|
||||
inner_instruction_parser: Self::parse_trade_inner_instruction,
|
||||
instruction_parser: Self::parse_sell_instruction,
|
||||
},
|
||||
];
|
||||
|
||||
let inner = GenericEventParser::new(PUMPFUN_PROGRAM_ID, ProtocolType::PumpFun, configs);
|
||||
|
||||
Self { inner }
|
||||
}
|
||||
|
||||
/// 解析创建代币日志事件
|
||||
fn parse_create_token_inner_instruction(
|
||||
data: &[u8],
|
||||
metadata: EventMetadata,
|
||||
) -> Option<Box<dyn UnifiedEvent>> {
|
||||
if let Ok(event) = borsh::from_slice::<PumpFunCreateTokenEvent>(data) {
|
||||
let mut metadata = metadata;
|
||||
metadata.set_id(format!(
|
||||
"{}-{}-{}-{}",
|
||||
metadata.signature,
|
||||
event.name,
|
||||
event.symbol,
|
||||
event.mint.to_string()
|
||||
));
|
||||
Some(Box::new(PumpFunCreateTokenEvent {
|
||||
metadata: metadata,
|
||||
..event
|
||||
}))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// 解析交易事件
|
||||
fn parse_trade_inner_instruction(
|
||||
data: &[u8],
|
||||
metadata: EventMetadata,
|
||||
) -> Option<Box<dyn UnifiedEvent>> {
|
||||
if let Ok(event) = borsh::from_slice::<PumpFunTradeEvent>(data) {
|
||||
let mut metadata = metadata;
|
||||
metadata.set_id(format!(
|
||||
"{}-{}-{}-{}",
|
||||
metadata.signature,
|
||||
event.mint.to_string(),
|
||||
event.user.to_string(),
|
||||
event.is_buy.to_string()
|
||||
));
|
||||
Some(Box::new(PumpFunTradeEvent {
|
||||
metadata: 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()
|
||||
};
|
||||
|
||||
let mut metadata = metadata;
|
||||
metadata.set_id(format!(
|
||||
"{}-{}-{}-{}",
|
||||
metadata.signature,
|
||||
name,
|
||||
symbol,
|
||||
accounts[0].to_string()
|
||||
));
|
||||
|
||||
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() < 11 {
|
||||
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());
|
||||
let mut metadata = metadata;
|
||||
metadata.set_id(format!(
|
||||
"{}-{}-{}-{}",
|
||||
metadata.signature,
|
||||
accounts[2].to_string(),
|
||||
accounts[6].to_string(),
|
||||
true.to_string()
|
||||
));
|
||||
Some(Box::new(PumpFunTradeEvent {
|
||||
metadata,
|
||||
fee_recipient: accounts[1],
|
||||
mint: accounts[2],
|
||||
bonding_curve: accounts[3],
|
||||
associated_bonding_curve: accounts[4],
|
||||
associated_user: accounts[5],
|
||||
user: accounts[6],
|
||||
creator_vault: accounts[8],
|
||||
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());
|
||||
let mut metadata = metadata;
|
||||
metadata.set_id(format!(
|
||||
"{}-{}-{}-{}",
|
||||
metadata.signature,
|
||||
accounts[2].to_string(),
|
||||
accounts[6].to_string(),
|
||||
false.to_string()
|
||||
));
|
||||
Some(Box::new(PumpFunTradeEvent {
|
||||
metadata,
|
||||
fee_recipient: accounts[1],
|
||||
mint: accounts[2],
|
||||
bonding_curve: accounts[3],
|
||||
associated_bonding_curve: accounts[4],
|
||||
associated_user: accounts[5],
|
||||
user: accounts[6],
|
||||
creator_vault: accounts[8],
|
||||
min_sol_output,
|
||||
amount,
|
||||
is_buy: false,
|
||||
..Default::default()
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl EventParser for PumpFunEventParser {
|
||||
fn parse_events_from_inner_instruction(
|
||||
&self,
|
||||
inner_instruction: &UiCompiledInstruction,
|
||||
signature: &str,
|
||||
slot: u64,
|
||||
) -> Vec<Box<dyn UnifiedEvent>> {
|
||||
self.inner
|
||||
.parse_events_from_inner_instruction(inner_instruction, signature, slot)
|
||||
}
|
||||
|
||||
fn parse_events_from_instruction(
|
||||
&self,
|
||||
instruction: &CompiledInstruction,
|
||||
accounts: &[Pubkey],
|
||||
signature: &str,
|
||||
slot: u64,
|
||||
) -> Vec<Box<dyn UnifiedEvent>> {
|
||||
self.inner
|
||||
.parse_events_from_instruction(instruction, accounts, signature, slot)
|
||||
}
|
||||
|
||||
fn should_handle(&self, program_id: &Pubkey) -> bool {
|
||||
self.inner.should_handle(program_id)
|
||||
}
|
||||
|
||||
fn supported_program_ids(&self) -> Vec<Pubkey> {
|
||||
self.inner.supported_program_ids()
|
||||
}
|
||||
}
|
||||
+322
@@ -0,0 +1,322 @@
|
||||
use borsh::BorshDeserialize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
|
||||
use crate::streaming::event_parser::common::EventMetadata;
|
||||
use crate::impl_unified_event;
|
||||
|
||||
/// 买入事件
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)]
|
||||
pub struct PumpSwapBuyEvent {
|
||||
#[borsh(skip)]
|
||||
pub metadata: EventMetadata,
|
||||
pub timestamp: i64,
|
||||
pub base_amount_out: u64,
|
||||
pub max_quote_amount_in: u64,
|
||||
pub user_base_token_reserves: u64,
|
||||
pub user_quote_token_reserves: u64,
|
||||
pub pool_base_token_reserves: u64,
|
||||
pub pool_quote_token_reserves: u64,
|
||||
pub quote_amount_in: u64,
|
||||
pub lp_fee_basis_points: u64,
|
||||
pub lp_fee: u64,
|
||||
pub protocol_fee_basis_points: u64,
|
||||
pub protocol_fee: u64,
|
||||
pub quote_amount_in_with_lp_fee: u64,
|
||||
pub user_quote_amount_in: u64,
|
||||
pub pool: Pubkey,
|
||||
pub user: Pubkey,
|
||||
pub user_base_token_account: Pubkey,
|
||||
pub user_quote_token_account: Pubkey,
|
||||
pub protocol_fee_recipient: Pubkey,
|
||||
pub protocol_fee_recipient_token_account: Pubkey,
|
||||
pub coin_creator: Pubkey,
|
||||
pub coin_creator_fee_basis_points: u64,
|
||||
pub coin_creator_fee: u64,
|
||||
#[borsh(skip)]
|
||||
pub base_mint: Pubkey,
|
||||
#[borsh(skip)]
|
||||
pub quote_mint: Pubkey,
|
||||
#[borsh(skip)]
|
||||
pub pool_base_token_account: Pubkey,
|
||||
#[borsh(skip)]
|
||||
pub pool_quote_token_account: Pubkey,
|
||||
#[borsh(skip)]
|
||||
pub coin_creator_vault_ata: Pubkey,
|
||||
#[borsh(skip)]
|
||||
pub coin_creator_vault_authority: Pubkey,
|
||||
}
|
||||
|
||||
// 使用宏生成UnifiedEvent实现,指定需要合并的字段
|
||||
impl_unified_event!(
|
||||
PumpSwapBuyEvent,
|
||||
timestamp,
|
||||
base_amount_out,
|
||||
max_quote_amount_in,
|
||||
user_base_token_reserves,
|
||||
user_quote_token_reserves,
|
||||
pool_base_token_reserves,
|
||||
pool_quote_token_reserves,
|
||||
quote_amount_in,
|
||||
lp_fee_basis_points,
|
||||
lp_fee,
|
||||
protocol_fee_basis_points,
|
||||
protocol_fee,
|
||||
quote_amount_in_with_lp_fee,
|
||||
user_quote_amount_in,
|
||||
pool,
|
||||
user,
|
||||
user_base_token_account,
|
||||
user_quote_token_account,
|
||||
protocol_fee_recipient,
|
||||
protocol_fee_recipient_token_account,
|
||||
coin_creator,
|
||||
coin_creator_fee_basis_points,
|
||||
coin_creator_fee
|
||||
);
|
||||
|
||||
/// 卖出事件
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)]
|
||||
pub struct PumpSwapSellEvent {
|
||||
#[borsh(skip)]
|
||||
pub metadata: EventMetadata,
|
||||
pub timestamp: i64,
|
||||
pub base_amount_in: u64,
|
||||
pub min_quote_amount_out: u64,
|
||||
pub user_base_token_reserves: u64,
|
||||
pub user_quote_token_reserves: u64,
|
||||
pub pool_base_token_reserves: u64,
|
||||
pub pool_quote_token_reserves: u64,
|
||||
pub quote_amount_out: u64,
|
||||
pub lp_fee_basis_points: u64,
|
||||
pub lp_fee: u64,
|
||||
pub protocol_fee_basis_points: u64,
|
||||
pub protocol_fee: u64,
|
||||
pub quote_amount_out_without_lp_fee: u64,
|
||||
pub user_quote_amount_out: u64,
|
||||
pub pool: Pubkey,
|
||||
pub user: Pubkey,
|
||||
pub user_base_token_account: Pubkey,
|
||||
pub user_quote_token_account: Pubkey,
|
||||
pub protocol_fee_recipient: Pubkey,
|
||||
pub protocol_fee_recipient_token_account: Pubkey,
|
||||
pub coin_creator: Pubkey,
|
||||
pub coin_creator_fee_basis_points: u64,
|
||||
pub coin_creator_fee: u64,
|
||||
#[borsh(skip)]
|
||||
pub base_mint: Pubkey,
|
||||
#[borsh(skip)]
|
||||
pub quote_mint: Pubkey,
|
||||
#[borsh(skip)]
|
||||
pub pool_base_token_account: Pubkey,
|
||||
#[borsh(skip)]
|
||||
pub pool_quote_token_account: Pubkey,
|
||||
#[borsh(skip)]
|
||||
pub coin_creator_vault_ata: Pubkey,
|
||||
#[borsh(skip)]
|
||||
pub coin_creator_vault_authority: Pubkey,
|
||||
}
|
||||
|
||||
// 使用宏生成UnifiedEvent实现,指定需要合并的字段
|
||||
impl_unified_event!(
|
||||
PumpSwapSellEvent,
|
||||
timestamp,
|
||||
base_amount_in,
|
||||
min_quote_amount_out,
|
||||
user_base_token_reserves,
|
||||
user_quote_token_reserves,
|
||||
pool_base_token_reserves,
|
||||
pool_quote_token_reserves,
|
||||
quote_amount_out,
|
||||
lp_fee_basis_points,
|
||||
lp_fee,
|
||||
protocol_fee_basis_points,
|
||||
protocol_fee,
|
||||
quote_amount_out_without_lp_fee,
|
||||
user_quote_amount_out,
|
||||
pool,
|
||||
user,
|
||||
user_base_token_account,
|
||||
user_quote_token_account,
|
||||
protocol_fee_recipient,
|
||||
protocol_fee_recipient_token_account,
|
||||
coin_creator,
|
||||
coin_creator_fee_basis_points,
|
||||
coin_creator_fee
|
||||
);
|
||||
|
||||
/// 创建池子事件
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)]
|
||||
pub struct PumpSwapCreatePoolEvent {
|
||||
#[borsh(skip)]
|
||||
pub metadata: EventMetadata,
|
||||
pub timestamp: i64,
|
||||
pub index: u16,
|
||||
pub creator: Pubkey,
|
||||
pub base_mint: Pubkey,
|
||||
pub quote_mint: Pubkey,
|
||||
pub base_mint_decimals: u8,
|
||||
pub quote_mint_decimals: u8,
|
||||
pub base_amount_in: u64,
|
||||
pub quote_amount_in: u64,
|
||||
pub pool_base_amount: u64,
|
||||
pub pool_quote_amount: u64,
|
||||
pub minimum_liquidity: u64,
|
||||
pub initial_liquidity: u64,
|
||||
pub lp_token_amount_out: u64,
|
||||
pub pool_bump: u8,
|
||||
pub pool: Pubkey,
|
||||
pub lp_mint: Pubkey,
|
||||
pub user_base_token_account: Pubkey,
|
||||
pub user_quote_token_account: Pubkey,
|
||||
pub coin_creator: Pubkey,
|
||||
#[borsh(skip)]
|
||||
pub user_pool_token_account: Pubkey,
|
||||
#[borsh(skip)]
|
||||
pub pool_base_token_account: Pubkey,
|
||||
#[borsh(skip)]
|
||||
pub pool_quote_token_account: Pubkey,
|
||||
}
|
||||
|
||||
impl_unified_event!(
|
||||
PumpSwapCreatePoolEvent,
|
||||
timestamp,
|
||||
index,
|
||||
creator,
|
||||
base_mint,
|
||||
quote_mint,
|
||||
base_mint_decimals,
|
||||
quote_mint_decimals,
|
||||
base_amount_in,
|
||||
quote_amount_in,
|
||||
pool_base_amount,
|
||||
pool_quote_amount,
|
||||
minimum_liquidity,
|
||||
initial_liquidity,
|
||||
lp_token_amount_out,
|
||||
pool_bump,
|
||||
pool,
|
||||
lp_mint,
|
||||
user_base_token_account,
|
||||
user_quote_token_account,
|
||||
coin_creator
|
||||
);
|
||||
|
||||
/// 存款事件
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)]
|
||||
pub struct PumpSwapDepositEvent {
|
||||
#[borsh(skip)]
|
||||
pub metadata: EventMetadata,
|
||||
pub timestamp: i64,
|
||||
pub lp_token_amount_out: u64,
|
||||
pub max_base_amount_in: u64,
|
||||
pub max_quote_amount_in: u64,
|
||||
pub user_base_token_reserves: u64,
|
||||
pub user_quote_token_reserves: u64,
|
||||
pub pool_base_token_reserves: u64,
|
||||
pub pool_quote_token_reserves: u64,
|
||||
pub base_amount_in: u64,
|
||||
pub quote_amount_in: u64,
|
||||
pub lp_mint_supply: u64,
|
||||
pub pool: Pubkey,
|
||||
pub user: Pubkey,
|
||||
pub user_base_token_account: Pubkey,
|
||||
pub user_quote_token_account: Pubkey,
|
||||
pub user_pool_token_account: Pubkey,
|
||||
#[borsh(skip)]
|
||||
pub base_mint: Pubkey,
|
||||
#[borsh(skip)]
|
||||
pub quote_mint: Pubkey,
|
||||
#[borsh(skip)]
|
||||
pub pool_base_token_account: Pubkey,
|
||||
#[borsh(skip)]
|
||||
pub pool_quote_token_account: Pubkey,
|
||||
}
|
||||
|
||||
impl_unified_event!(
|
||||
PumpSwapDepositEvent,
|
||||
timestamp,
|
||||
lp_token_amount_out,
|
||||
max_base_amount_in,
|
||||
max_quote_amount_in,
|
||||
user_base_token_reserves,
|
||||
user_quote_token_reserves,
|
||||
pool_base_token_reserves,
|
||||
pool_quote_token_reserves,
|
||||
base_amount_in,
|
||||
quote_amount_in,
|
||||
lp_mint_supply,
|
||||
pool,
|
||||
user,
|
||||
user_base_token_account,
|
||||
user_quote_token_account,
|
||||
user_pool_token_account
|
||||
);
|
||||
|
||||
/// 提款事件
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)]
|
||||
pub struct PumpSwapWithdrawEvent {
|
||||
#[borsh(skip)]
|
||||
pub metadata: EventMetadata,
|
||||
pub timestamp: i64,
|
||||
pub lp_token_amount_in: u64,
|
||||
pub min_base_amount_out: u64,
|
||||
pub min_quote_amount_out: u64,
|
||||
pub user_base_token_reserves: u64,
|
||||
pub user_quote_token_reserves: u64,
|
||||
pub pool_base_token_reserves: u64,
|
||||
pub pool_quote_token_reserves: u64,
|
||||
pub base_amount_out: u64,
|
||||
pub quote_amount_out: u64,
|
||||
pub lp_mint_supply: u64,
|
||||
pub pool: Pubkey,
|
||||
pub user: Pubkey,
|
||||
pub user_base_token_account: Pubkey,
|
||||
pub user_quote_token_account: Pubkey,
|
||||
pub user_pool_token_account: Pubkey,
|
||||
#[borsh(skip)]
|
||||
pub base_mint: Pubkey,
|
||||
#[borsh(skip)]
|
||||
pub quote_mint: Pubkey,
|
||||
#[borsh(skip)]
|
||||
pub pool_base_token_account: Pubkey,
|
||||
#[borsh(skip)]
|
||||
pub pool_quote_token_account: Pubkey,
|
||||
}
|
||||
|
||||
impl_unified_event!(
|
||||
PumpSwapWithdrawEvent,
|
||||
timestamp,
|
||||
lp_token_amount_in,
|
||||
min_base_amount_out,
|
||||
min_quote_amount_out,
|
||||
user_base_token_reserves,
|
||||
user_quote_token_reserves,
|
||||
pool_base_token_reserves,
|
||||
pool_quote_token_reserves,
|
||||
base_amount_out,
|
||||
quote_amount_out,
|
||||
lp_mint_supply,
|
||||
pool,
|
||||
user,
|
||||
user_base_token_account,
|
||||
user_quote_token_account,
|
||||
user_pool_token_account
|
||||
);
|
||||
|
||||
/// 事件鉴别器常量
|
||||
pub mod discriminators {
|
||||
// 事件鉴别器
|
||||
pub const BUY_EVENT: &str = "0xe445a52e51cb9a1d67f4521f2cf57777";
|
||||
pub const SELL_EVENT: &str = "0xe445a52e51cb9a1d3e2f370aa503dc2a";
|
||||
pub const CREATE_POOL_EVENT: &str = "0xe445a52e51cb9a1db1310cd2a076a774";
|
||||
pub const DEPOSIT_EVENT: &str = "0xe445a52e51cb9a1d78f83d531f8e6b90";
|
||||
pub const WITHDRAW_EVENT: &str = "0xe445a52e51cb9a1d1609851aa02c47c0";
|
||||
|
||||
// 指令鉴别器
|
||||
pub const BUY_IX: &[u8] = &[102, 6, 61, 18, 1, 218, 235, 234];
|
||||
pub const SELL_IX: &[u8] = &[51, 230, 133, 164, 1, 127, 131, 173];
|
||||
pub const CREATE_POOL_IX: &[u8] = &[233, 146, 209, 142, 207, 104, 64, 188];
|
||||
pub const DEPOSIT_IX: &[u8] = &[242, 35, 198, 137, 82, 225, 242, 182];
|
||||
pub const WITHDRAW_IX: &[u8] = &[183, 18, 70, 156, 148, 109, 161, 34];
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
pub mod events;
|
||||
pub mod parser;
|
||||
|
||||
pub use events::*;
|
||||
pub use parser::PumpSwapEventParser;
|
||||
+386
@@ -0,0 +1,386 @@
|
||||
use solana_sdk::{instruction::CompiledInstruction, pubkey::Pubkey};
|
||||
use solana_transaction_status::UiCompiledInstruction;
|
||||
|
||||
use crate::streaming::event_parser::{
|
||||
common::{EventMetadata, EventType, ProtocolType, read_u64_le},
|
||||
core::traits::{EventParser, GenericEventParseConfig, GenericEventParser, UnifiedEvent},
|
||||
protocols::pumpswap::{
|
||||
discriminators, PumpSwapBuyEvent, PumpSwapCreatePoolEvent, PumpSwapDepositEvent,
|
||||
PumpSwapSellEvent, PumpSwapWithdrawEvent,
|
||||
},
|
||||
};
|
||||
|
||||
/// PumpSwap程序ID
|
||||
pub const PUMPSWAP_PROGRAM_ID: Pubkey =
|
||||
solana_sdk::pubkey!("pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA");
|
||||
|
||||
/// PumpSwap事件解析器
|
||||
pub struct PumpSwapEventParser {
|
||||
inner: GenericEventParser,
|
||||
}
|
||||
|
||||
impl PumpSwapEventParser {
|
||||
pub fn new() -> Self {
|
||||
// 配置所有事件类型
|
||||
let configs = vec![
|
||||
GenericEventParseConfig {
|
||||
inner_instruction_discriminator: discriminators::BUY_EVENT,
|
||||
instruction_discriminator: discriminators::BUY_IX,
|
||||
event_type: EventType::PumpSwapBuy,
|
||||
inner_instruction_parser: Self::parse_buy_inner_instruction,
|
||||
instruction_parser: Self::parse_buy_instruction,
|
||||
},
|
||||
GenericEventParseConfig {
|
||||
inner_instruction_discriminator: discriminators::SELL_EVENT,
|
||||
instruction_discriminator: discriminators::SELL_IX,
|
||||
event_type: EventType::PumpSwapSell,
|
||||
inner_instruction_parser: Self::parse_sell_inner_instruction,
|
||||
instruction_parser: Self::parse_sell_instruction,
|
||||
},
|
||||
GenericEventParseConfig {
|
||||
inner_instruction_discriminator: discriminators::CREATE_POOL_EVENT,
|
||||
instruction_discriminator: discriminators::CREATE_POOL_IX,
|
||||
event_type: EventType::PumpSwapCreatePool,
|
||||
inner_instruction_parser: Self::parse_create_pool_inner_instruction,
|
||||
instruction_parser: Self::parse_create_pool_instruction,
|
||||
},
|
||||
GenericEventParseConfig {
|
||||
inner_instruction_discriminator: discriminators::DEPOSIT_EVENT,
|
||||
instruction_discriminator: discriminators::DEPOSIT_IX,
|
||||
event_type: EventType::PumpSwapDeposit,
|
||||
inner_instruction_parser: Self::parse_deposit_inner_instruction,
|
||||
instruction_parser: Self::parse_deposit_instruction,
|
||||
},
|
||||
GenericEventParseConfig {
|
||||
inner_instruction_discriminator: discriminators::WITHDRAW_EVENT,
|
||||
instruction_discriminator: discriminators::WITHDRAW_IX,
|
||||
event_type: EventType::PumpSwapWithdraw,
|
||||
inner_instruction_parser: Self::parse_withdraw_inner_instruction,
|
||||
instruction_parser: Self::parse_withdraw_instruction,
|
||||
},
|
||||
];
|
||||
|
||||
let inner = GenericEventParser::new(PUMPSWAP_PROGRAM_ID, ProtocolType::PumpSwap, configs);
|
||||
|
||||
Self { inner }
|
||||
}
|
||||
|
||||
/// 解析买入日志事件
|
||||
fn parse_buy_inner_instruction(data: &[u8], metadata: EventMetadata) -> Option<Box<dyn UnifiedEvent>> {
|
||||
if let Ok(event) = borsh::from_slice::<PumpSwapBuyEvent>(data) {
|
||||
let mut metadata = metadata;
|
||||
metadata.set_id(format!(
|
||||
"{}-{}-{}-{}",
|
||||
metadata.signature, event.user, event.pool, event.base_amount_out
|
||||
));
|
||||
Some(Box::new(PumpSwapBuyEvent {
|
||||
metadata: metadata,
|
||||
..event
|
||||
}))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// 解析卖出日志事件
|
||||
fn parse_sell_inner_instruction(data: &[u8], metadata: EventMetadata) -> Option<Box<dyn UnifiedEvent>> {
|
||||
if let Ok(event) = borsh::from_slice::<PumpSwapSellEvent>(data) {
|
||||
let mut metadata = metadata;
|
||||
metadata.set_id(format!(
|
||||
"{}-{}-{}-{}",
|
||||
metadata.signature, event.user, event.pool, event.base_amount_in
|
||||
));
|
||||
Some(Box::new(PumpSwapSellEvent {
|
||||
metadata: metadata,
|
||||
..event
|
||||
}))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// 解析创建池子日志事件
|
||||
fn parse_create_pool_inner_instruction(
|
||||
data: &[u8],
|
||||
metadata: EventMetadata,
|
||||
) -> Option<Box<dyn UnifiedEvent>> {
|
||||
if let Ok(event) = borsh::from_slice::<PumpSwapCreatePoolEvent>(data) {
|
||||
let mut metadata = metadata;
|
||||
metadata.set_id(format!(
|
||||
"{}-{}-{}-{}",
|
||||
metadata.signature, event.pool, event.creator, event.base_amount_in
|
||||
));
|
||||
Some(Box::new(PumpSwapCreatePoolEvent {
|
||||
metadata: metadata,
|
||||
..event
|
||||
}))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// 解析存款日志事件
|
||||
fn parse_deposit_inner_instruction(data: &[u8], metadata: EventMetadata) -> Option<Box<dyn UnifiedEvent>> {
|
||||
if let Ok(event) = borsh::from_slice::<PumpSwapDepositEvent>(data) {
|
||||
let mut metadata = metadata;
|
||||
metadata.set_id(format!(
|
||||
"{}-{}-{}-{}",
|
||||
metadata.signature, event.pool, event.user, event.lp_token_amount_out
|
||||
));
|
||||
Some(Box::new(PumpSwapDepositEvent {
|
||||
metadata: metadata,
|
||||
..event
|
||||
}))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// 解析提款日志事件
|
||||
fn parse_withdraw_inner_instruction(data: &[u8], metadata: EventMetadata) -> Option<Box<dyn UnifiedEvent>> {
|
||||
if let Ok(event) = borsh::from_slice::<PumpSwapWithdrawEvent>(data) {
|
||||
let mut metadata = metadata;
|
||||
metadata.set_id(format!(
|
||||
"{}-{}-{}-{}",
|
||||
metadata.signature, event.pool, event.user, event.lp_token_amount_in
|
||||
));
|
||||
Some(Box::new(PumpSwapWithdrawEvent {
|
||||
metadata: 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)?;
|
||||
|
||||
let mut metadata = metadata;
|
||||
metadata.set_id(format!(
|
||||
"{}-{}-{}-{}",
|
||||
metadata.signature, accounts[1], accounts[0], base_amount_out
|
||||
));
|
||||
|
||||
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],
|
||||
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)?;
|
||||
|
||||
let mut metadata = metadata;
|
||||
metadata.set_id(format!(
|
||||
"{}-{}-{}-{}",
|
||||
metadata.signature, accounts[1], accounts[0], base_amount_in
|
||||
));
|
||||
|
||||
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],
|
||||
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()
|
||||
};
|
||||
|
||||
let mut metadata = metadata;
|
||||
metadata.set_id(format!(
|
||||
"{}-{}-{}-{}",
|
||||
metadata.signature, accounts[0], accounts[2], base_amount_in
|
||||
));
|
||||
|
||||
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()?);
|
||||
|
||||
let mut metadata = metadata;
|
||||
metadata.set_id(format!(
|
||||
"{}-{}-{}-{}",
|
||||
metadata.signature, accounts[0], accounts[2], lp_token_amount_out
|
||||
));
|
||||
|
||||
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()?);
|
||||
|
||||
let mut metadata = metadata;
|
||||
metadata.set_id(format!(
|
||||
"{}-{}-{}-{}",
|
||||
metadata.signature, accounts[0], accounts[2], lp_token_amount_in
|
||||
));
|
||||
|
||||
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()
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl EventParser for PumpSwapEventParser {
|
||||
fn parse_events_from_inner_instruction(
|
||||
&self,
|
||||
inner_instruction: &UiCompiledInstruction,
|
||||
signature: &str,
|
||||
slot: u64,
|
||||
) -> Vec<Box<dyn UnifiedEvent>> {
|
||||
self.inner
|
||||
.parse_events_from_inner_instruction(inner_instruction, signature, slot)
|
||||
}
|
||||
|
||||
fn parse_events_from_instruction(
|
||||
&self,
|
||||
instruction: &CompiledInstruction,
|
||||
accounts: &[Pubkey],
|
||||
signature: &str,
|
||||
slot: u64,
|
||||
) -> Vec<Box<dyn UnifiedEvent>> {
|
||||
self.inner
|
||||
.parse_events_from_instruction(instruction, accounts, signature, slot)
|
||||
}
|
||||
|
||||
fn should_handle(&self, program_id: &Pubkey) -> bool {
|
||||
self.inner.should_handle(program_id)
|
||||
}
|
||||
|
||||
fn supported_program_ids(&self) -> Vec<Pubkey> {
|
||||
self.inner.supported_program_ids()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
use crate::impl_unified_event;
|
||||
use crate::streaming::event_parser::common::EventMetadata;
|
||||
// use borsh::BorshDeserialize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
|
||||
/// 交易
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct RaydiumClmmSwapEvent {
|
||||
pub metadata: EventMetadata,
|
||||
pub amount: u64,
|
||||
pub other_amount_threshold: u64,
|
||||
pub sqrt_price_limit_x64: u128,
|
||||
pub is_base_input: bool,
|
||||
pub payer: Pubkey,
|
||||
pub amm_config: Pubkey,
|
||||
pub pool_state: Pubkey,
|
||||
pub input_token_account: Pubkey,
|
||||
pub output_token_account: Pubkey,
|
||||
pub input_vault: Pubkey,
|
||||
pub output_vault: Pubkey,
|
||||
pub observation_state: Pubkey,
|
||||
pub token_program: Pubkey,
|
||||
pub tick_array: Pubkey,
|
||||
pub remaining_accounts: Vec<Pubkey>,
|
||||
}
|
||||
|
||||
impl_unified_event!(RaydiumClmmSwapEvent,);
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct RaydiumClmmSwapV2Event {
|
||||
pub metadata: EventMetadata,
|
||||
pub amount: u64,
|
||||
pub other_amount_threshold: u64,
|
||||
pub sqrt_price_limit_x64: u128,
|
||||
pub is_base_input: bool,
|
||||
pub payer: Pubkey,
|
||||
pub amm_config: Pubkey,
|
||||
pub pool_state: Pubkey,
|
||||
pub input_token_account: Pubkey,
|
||||
pub output_token_account: Pubkey,
|
||||
pub input_vault: Pubkey,
|
||||
pub output_vault: Pubkey,
|
||||
pub observation_state: Pubkey,
|
||||
pub token_program: Pubkey,
|
||||
pub token_program2022: Pubkey,
|
||||
pub memo_program: Pubkey,
|
||||
pub input_vault_mint: Pubkey,
|
||||
pub output_vault_mint: Pubkey,
|
||||
pub remaining_accounts: Vec<Pubkey>,
|
||||
}
|
||||
impl_unified_event!(RaydiumClmmSwapV2Event,);
|
||||
|
||||
/// 事件鉴别器常量
|
||||
pub mod discriminators {
|
||||
// 指令鉴别器
|
||||
pub const SWAP: &[u8] = &[248, 198, 158, 145, 225, 117, 135, 200];
|
||||
pub const SWAP_V2: &[u8] = &[43, 4, 237, 11, 26, 201, 30, 98];
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
pub mod events;
|
||||
pub mod parser;
|
||||
|
||||
pub use events::*;
|
||||
pub use parser::RaydiumClmmEventParser;
|
||||
+170
@@ -0,0 +1,170 @@
|
||||
use solana_sdk::{instruction::CompiledInstruction, pubkey::Pubkey};
|
||||
use solana_transaction_status::UiCompiledInstruction;
|
||||
|
||||
use crate::streaming::event_parser::{
|
||||
common::{read_u128_le, read_u64_le, read_u8_le, EventMetadata, EventType, ProtocolType},
|
||||
core::traits::{EventParser, GenericEventParseConfig, GenericEventParser, UnifiedEvent},
|
||||
protocols::raydium_clmm::{discriminators, RaydiumClmmSwapEvent, RaydiumClmmSwapV2Event},
|
||||
};
|
||||
|
||||
/// Raydium CLMM程序ID
|
||||
pub const RAYDIUM_CLMM_PROGRAM_ID: Pubkey =
|
||||
solana_sdk::pubkey!("CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK");
|
||||
|
||||
/// Raydium CLMM事件解析器
|
||||
pub struct RaydiumClmmEventParser {
|
||||
inner: GenericEventParser,
|
||||
}
|
||||
|
||||
impl RaydiumClmmEventParser {
|
||||
pub fn new() -> Self {
|
||||
// 配置所有事件类型
|
||||
let configs = vec![
|
||||
GenericEventParseConfig {
|
||||
inner_instruction_discriminator: "",
|
||||
instruction_discriminator: discriminators::SWAP,
|
||||
event_type: EventType::RaydiumClmmSwap,
|
||||
inner_instruction_parser: Self::parse_trade_inner_instruction,
|
||||
instruction_parser: Self::parse_swap_instruction,
|
||||
},
|
||||
GenericEventParseConfig {
|
||||
inner_instruction_discriminator: "",
|
||||
instruction_discriminator: discriminators::SWAP_V2,
|
||||
event_type: EventType::RaydiumClmmSwapV2,
|
||||
inner_instruction_parser: Self::parse_trade_inner_instruction,
|
||||
instruction_parser: Self::parse_swap_v2_instruction,
|
||||
},
|
||||
];
|
||||
|
||||
let inner =
|
||||
GenericEventParser::new(RAYDIUM_CLMM_PROGRAM_ID, ProtocolType::RaydiumClmm, configs);
|
||||
|
||||
Self { inner }
|
||||
}
|
||||
|
||||
/// 解析交易事件
|
||||
fn parse_trade_inner_instruction(
|
||||
_data: &[u8],
|
||||
_metadata: EventMetadata,
|
||||
) -> Option<Box<dyn UnifiedEvent>> {
|
||||
None
|
||||
}
|
||||
|
||||
/// 解析交易指令事件
|
||||
fn parse_swap_instruction(
|
||||
data: &[u8],
|
||||
accounts: &[Pubkey],
|
||||
metadata: EventMetadata,
|
||||
) -> Option<Box<dyn UnifiedEvent>> {
|
||||
if data.len() < 16 || 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)?;
|
||||
|
||||
let mut metadata = metadata;
|
||||
metadata.set_id(format!(
|
||||
"{}-{}-{}-{}",
|
||||
metadata.signature, accounts[2], accounts[3], accounts[4]
|
||||
));
|
||||
|
||||
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(),
|
||||
..Default::default()
|
||||
}))
|
||||
}
|
||||
|
||||
fn parse_swap_v2_instruction(
|
||||
data: &[u8],
|
||||
accounts: &[Pubkey],
|
||||
metadata: EventMetadata,
|
||||
) -> Option<Box<dyn UnifiedEvent>> {
|
||||
if data.len() < 16 || 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)?;
|
||||
|
||||
let mut metadata = metadata;
|
||||
metadata.set_id(format!(
|
||||
"{}-{}-{}-{}",
|
||||
metadata.signature, accounts[2], accounts[3], accounts[4]
|
||||
));
|
||||
|
||||
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(),
|
||||
..Default::default()
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl EventParser for RaydiumClmmEventParser {
|
||||
fn parse_events_from_inner_instruction(
|
||||
&self,
|
||||
inner_instruction: &UiCompiledInstruction,
|
||||
signature: &str,
|
||||
slot: u64,
|
||||
) -> Vec<Box<dyn UnifiedEvent>> {
|
||||
self.inner
|
||||
.parse_events_from_inner_instruction(inner_instruction, signature, slot)
|
||||
}
|
||||
|
||||
fn parse_events_from_instruction(
|
||||
&self,
|
||||
instruction: &CompiledInstruction,
|
||||
accounts: &[Pubkey],
|
||||
signature: &str,
|
||||
slot: u64,
|
||||
) -> Vec<Box<dyn UnifiedEvent>> {
|
||||
self.inner
|
||||
.parse_events_from_instruction(instruction, accounts, signature, slot)
|
||||
}
|
||||
|
||||
fn should_handle(&self, program_id: &Pubkey) -> bool {
|
||||
self.inner.should_handle(program_id)
|
||||
}
|
||||
|
||||
fn supported_program_ids(&self) -> Vec<Pubkey> {
|
||||
self.inner.supported_program_ids()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
use crate::impl_unified_event;
|
||||
use crate::streaming::event_parser::common::EventMetadata;
|
||||
use borsh::BorshDeserialize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
|
||||
/// 交易
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)]
|
||||
pub struct RaydiumCpmmSwapEvent {
|
||||
pub metadata: EventMetadata,
|
||||
pub amount_in: u64,
|
||||
pub minimum_amount_out: u64,
|
||||
pub max_amount_in: u64,
|
||||
pub amount_out: u64,
|
||||
pub payer: Pubkey,
|
||||
pub authority: Pubkey,
|
||||
pub amm_config: Pubkey,
|
||||
pub pool_state: Pubkey,
|
||||
pub input_token_account: Pubkey,
|
||||
pub output_token_account: Pubkey,
|
||||
pub input_vault: Pubkey,
|
||||
pub output_vault: Pubkey,
|
||||
pub input_token_mint: Pubkey,
|
||||
pub output_token_mint: Pubkey,
|
||||
pub observation_state: Pubkey,
|
||||
}
|
||||
|
||||
impl_unified_event!(RaydiumCpmmSwapEvent,);
|
||||
|
||||
/// 事件鉴别器常量
|
||||
pub mod discriminators {
|
||||
// 指令鉴别器
|
||||
pub const SWAP_BASE_IN: &[u8] = &[143, 190, 90, 218, 196, 30, 51, 222];
|
||||
pub const SWAP_BASE_OUT: &[u8] = &[55, 217, 98, 86, 163, 74, 180, 173];
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
pub mod events;
|
||||
pub mod parser;
|
||||
|
||||
pub use events::*;
|
||||
pub use parser::RaydiumCpmmEventParser;
|
||||
+159
@@ -0,0 +1,159 @@
|
||||
use solana_sdk::{instruction::CompiledInstruction, pubkey::Pubkey};
|
||||
use solana_transaction_status::UiCompiledInstruction;
|
||||
|
||||
use crate::streaming::event_parser::{
|
||||
common::{read_u64_le, EventMetadata, EventType, ProtocolType},
|
||||
core::traits::{EventParser, GenericEventParseConfig, GenericEventParser, UnifiedEvent},
|
||||
protocols::raydium_cpmm::{discriminators, RaydiumCpmmSwapEvent},
|
||||
};
|
||||
|
||||
/// Raydium CPMM程序ID
|
||||
pub const RAYDIUM_CPMM_PROGRAM_ID: Pubkey =
|
||||
solana_sdk::pubkey!("CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C");
|
||||
|
||||
/// Raydium CPMM事件解析器
|
||||
pub struct RaydiumCpmmEventParser {
|
||||
inner: GenericEventParser,
|
||||
}
|
||||
|
||||
impl RaydiumCpmmEventParser {
|
||||
pub fn new() -> Self {
|
||||
// 配置所有事件类型
|
||||
let configs = vec![
|
||||
GenericEventParseConfig {
|
||||
inner_instruction_discriminator: "",
|
||||
instruction_discriminator: discriminators::SWAP_BASE_IN,
|
||||
event_type: EventType::RaydiumCpmmSwapBaseInput,
|
||||
inner_instruction_parser: Self::parse_trade_inner_instruction,
|
||||
instruction_parser: Self::parse_swap_base_input_instruction,
|
||||
},
|
||||
GenericEventParseConfig {
|
||||
inner_instruction_discriminator: "",
|
||||
instruction_discriminator: discriminators::SWAP_BASE_OUT,
|
||||
event_type: EventType::RaydiumCpmmSwapBaseOutput,
|
||||
inner_instruction_parser: Self::parse_trade_inner_instruction,
|
||||
instruction_parser: Self::parse_swap_base_output_instruction,
|
||||
},
|
||||
];
|
||||
|
||||
let inner =
|
||||
GenericEventParser::new(RAYDIUM_CPMM_PROGRAM_ID, ProtocolType::RaydiumCpmm, configs);
|
||||
|
||||
Self { inner }
|
||||
}
|
||||
|
||||
/// 解析交易事件
|
||||
fn parse_trade_inner_instruction(
|
||||
_data: &[u8],
|
||||
_metadata: EventMetadata,
|
||||
) -> Option<Box<dyn UnifiedEvent>> {
|
||||
None
|
||||
}
|
||||
|
||||
/// 解析买入指令事件
|
||||
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)?;
|
||||
|
||||
let mut metadata = metadata;
|
||||
metadata.set_id(format!(
|
||||
"{}-{}-{}-{}",
|
||||
metadata.signature, accounts[3], accounts[10], accounts[11]
|
||||
));
|
||||
|
||||
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_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)?;
|
||||
|
||||
let mut metadata = metadata;
|
||||
metadata.set_id(format!(
|
||||
"{}-{}-{}-{}",
|
||||
metadata.signature, accounts[3], accounts[10], accounts[11]
|
||||
));
|
||||
|
||||
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_mint: accounts[10],
|
||||
output_token_mint: accounts[11],
|
||||
observation_state: accounts[12],
|
||||
..Default::default()
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl EventParser for RaydiumCpmmEventParser {
|
||||
fn parse_events_from_inner_instruction(
|
||||
&self,
|
||||
inner_instruction: &UiCompiledInstruction,
|
||||
signature: &str,
|
||||
slot: u64,
|
||||
) -> Vec<Box<dyn UnifiedEvent>> {
|
||||
self.inner
|
||||
.parse_events_from_inner_instruction(inner_instruction, signature, slot)
|
||||
}
|
||||
|
||||
fn parse_events_from_instruction(
|
||||
&self,
|
||||
instruction: &CompiledInstruction,
|
||||
accounts: &[Pubkey],
|
||||
signature: &str,
|
||||
slot: u64,
|
||||
) -> Vec<Box<dyn UnifiedEvent>> {
|
||||
self.inner
|
||||
.parse_events_from_instruction(instruction, accounts, signature, slot)
|
||||
}
|
||||
|
||||
fn should_handle(&self, program_id: &Pubkey) -> bool {
|
||||
self.inner.should_handle(program_id)
|
||||
}
|
||||
|
||||
fn supported_program_ids(&self) -> Vec<Pubkey> {
|
||||
self.inner.supported_program_ids()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user