feat: Add Raydium AMM V4 support and enhance protocol parsers

- Add complete Raydium AMM V4 protocol integration
- Enhance PumpFun, CLMM, CPMM event parsing
- Refactor parser architecture for better extensibility
- Update documentation and examples
This commit is contained in:
ysq
2025-08-11 22:02:09 +08:00
parent 97faba9406
commit 8e3d64a09c
19 changed files with 1778 additions and 128 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "solana-streamer-sdk"
version = "0.2.0"
version = "0.2.1"
edition = "2021"
authors = ["William <byteblock6@gmail.com>", "sgxiang <sgxiang@gmail.com>", "wei <1415121722@qq.com>"]
repository = "https://github.com/0xfnzero/solana-streamer"
+88 -15
View File
@@ -14,6 +14,7 @@ A lightweight Rust library for real-time event streaming from Solana DEX trading
- **Bonk**: Token launch platform events (letsbonk.fun)
- **Raydium CPMM**: Raydium's Concentrated Pool Market Maker events
- **Raydium CLMM**: Raydium's Concentrated Liquidity Market Maker events
- **Raydium AMM V4**: Raydium's Automated Market Maker V4 events
5. **Unified Event Interface**: Consistent event handling across all supported protocols
6. **Event Parsing System**: Automatic parsing and categorization of protocol-specific events
7. **High Performance**: Optimized for low-latency event processing
@@ -41,14 +42,14 @@ Add the dependency to your `Cargo.toml`:
```toml
# Add to your Cargo.toml
solana-streamer-sdk = { path = "./solana-streamer", version = "0.2.0" }
solana-streamer-sdk = { path = "./solana-streamer", version = "0.2.1" }
```
### Use crates.io
```toml
# Add to your Cargo.toml
solana-streamer-sdk = "0.2.0"
solana-streamer-sdk = "0.2.1"
```
## Usage Examples
@@ -63,7 +64,7 @@ cargo run --example parse_tx_events
This example demonstrates:
- How to parse transaction data from Solana mainnet using RPC
- Event parsing for multiple protocols (PumpFun, PumpSwap, Bonk, Raydium CPMM/CLMM)
- Event parsing for multiple protocols (PumpFun, PumpSwap, Bonk, Raydium CPMM/CLMM/AMM V4)
- Transaction details extraction including fees, logs, and compute units
The example uses a predefined transaction signature and shows how to extract protocol-specific events from the transaction data.
@@ -80,18 +81,36 @@ use solana_streamer_sdk::{
parser::BONK_PROGRAM_ID, BonkMigrateToAmmEvent, BonkMigrateToCpswapEvent,
BonkPoolCreateEvent, BonkTradeEvent,
},
pumpfun::{parser::PUMPFUN_PROGRAM_ID, PumpFunCreateTokenEvent, PumpFunTradeEvent},
pumpfun::{
parser::PUMPFUN_PROGRAM_ID, PumpFunCreateTokenEvent, PumpFunMigrateEvent,
PumpFunTradeEvent,
},
pumpswap::{
parser::PUMPSWAP_PROGRAM_ID, PumpSwapBuyEvent, PumpSwapCreatePoolEvent,
PumpSwapDepositEvent, PumpSwapSellEvent, PumpSwapWithdrawEvent,
},
raydium_clmm::{
parser::RAYDIUM_CLMM_PROGRAM_ID, RaydiumClmmSwapEvent, RaydiumClmmSwapV2Event,
raydium_amm_v4::{
RaydiumAmmV4DepositEvent, RaydiumAmmV4Initialize2Event, RaydiumAmmV4SwapEvent,
RaydiumAmmV4WithdrawEvent, RaydiumAmmV4WithdrawPnlEvent,
},
raydium_cpmm::{parser::RAYDIUM_CPMM_PROGRAM_ID, RaydiumCpmmSwapEvent}, BlockMetaEvent,
raydium_clmm::{
parser::RAYDIUM_CLMM_PROGRAM_ID, RaydiumClmmClosePositionEvent,
RaydiumClmmCreatePoolEvent, RaydiumClmmDecreaseLiquidityV2Event,
RaydiumClmmIncreaseLiquidityV2Event, RaydiumClmmOpenPositionV2Event,
RaydiumClmmOpenPositionWithToken22NftEvent, RaydiumClmmSwapEvent,
RaydiumClmmSwapV2Event,
},
raydium_cpmm::{
parser::RAYDIUM_CPMM_PROGRAM_ID, RaydiumCpmmDepositEvent,
RaydiumCpmmInitializeEvent, RaydiumCpmmSwapEvent, RaydiumCpmmWithdrawEvent,
},
BlockMetaEvent,
},
Protocol, UnifiedEvent,
}, grpc::ClientConfig, shred_stream::ShredClientConfig, ShredStreamGrpc, YellowstoneGrpc
},
grpc::ClientConfig,
shred_stream::ShredClientConfig,
ShredStreamGrpc, YellowstoneGrpc,
},
};
@@ -138,12 +157,11 @@ async fn test_grpc() -> Result<(), Box<dyn std::error::Error>> {
// Filter accounts
let account_include = vec![
PUMPFUN_PROGRAM_ID.to_string(), // Monitor pumpfun program ID
PUMPSWAP_PROGRAM_ID.to_string(), // Monitor pumpswap program ID
BONK_PROGRAM_ID.to_string(), // Monitor bonk program ID
RAYDIUM_CPMM_PROGRAM_ID.to_string(), // Monitor raydium_cpmm program ID
RAYDIUM_CLMM_PROGRAM_ID.to_string(), // Monitor raydium_clmm program ID
"xxxxxxxx".to_string(), // Monitor xxxxx account
PUMPFUN_PROGRAM_ID.to_string(), // Listen to pumpfun program ID
PUMPSWAP_PROGRAM_ID.to_string(), // Listen to pumpswap program ID
BONK_PROGRAM_ID.to_string(), // Listen to bonk program ID
RAYDIUM_CPMM_PROGRAM_ID.to_string(), // Listen to raydium_cpmm program ID
RAYDIUM_CLMM_PROGRAM_ID.to_string(), // Listen to raydium_clmm program ID
];
let account_exclude = vec![];
let account_required = vec![];
@@ -184,6 +202,7 @@ async fn test_shreds() -> Result<(), Box<dyn std::error::Error>> {
Protocol::Bonk,
Protocol::RaydiumCpmm,
Protocol::RaydiumClmm,
Protocol::RaydiumAmmV4,
];
println!("Listening for events, press Ctrl+C to stop...");
@@ -196,9 +215,11 @@ fn create_event_callback() -> impl Fn(Box<dyn UnifiedEvent>) {
|event: Box<dyn UnifiedEvent>| {
println!("🎉 Event received! Type: {:?}, ID: {}", event.event_type(), event.id());
match_event!(event, {
// block meta
BlockMetaEvent => |e: BlockMetaEvent| {
println!("BlockMetaEvent: {e:?}");
},
// bonk
BonkPoolCreateEvent => |e: BonkPoolCreateEvent| {
// When using grpc, you can get block_time from each event
println!("block_time: {:?}, block_time_ms: {:?}", e.metadata.block_time, e.metadata.block_time_ms);
@@ -213,12 +234,17 @@ fn create_event_callback() -> impl Fn(Box<dyn UnifiedEvent>) {
BonkMigrateToCpswapEvent => |e: BonkMigrateToCpswapEvent| {
println!("BonkMigrateToCpswapEvent: {e:?}");
},
// pumpfun
PumpFunTradeEvent => |e: PumpFunTradeEvent| {
println!("PumpFunTradeEvent: {e:?}");
},
PumpFunMigrateEvent => |e: PumpFunMigrateEvent| {
println!("PumpFunMigrateEvent: {e:?}");
},
PumpFunCreateTokenEvent => |e: PumpFunCreateTokenEvent| {
println!("PumpFunCreateTokenEvent: {e:?}");
},
// pumpswap
PumpSwapBuyEvent => |e: PumpSwapBuyEvent| {
println!("Buy event: {e:?}");
},
@@ -234,15 +260,60 @@ fn create_event_callback() -> impl Fn(Box<dyn UnifiedEvent>) {
PumpSwapWithdrawEvent => |e: PumpSwapWithdrawEvent| {
println!("Withdraw event: {e:?}");
},
// raydium_cpmm
RaydiumCpmmSwapEvent => |e: RaydiumCpmmSwapEvent| {
println!("RaydiumCpmmSwapEvent: {e:?}");
},
RaydiumCpmmDepositEvent => |e: RaydiumCpmmDepositEvent| {
println!("RaydiumCpmmDepositEvent: {e:?}");
},
RaydiumCpmmInitializeEvent => |e: RaydiumCpmmInitializeEvent| {
println!("RaydiumCpmmInitializeEvent: {e:?}");
},
RaydiumCpmmWithdrawEvent => |e: RaydiumCpmmWithdrawEvent| {
println!("RaydiumCpmmWithdrawEvent: {e:?}");
},
// raydium_clmm
RaydiumClmmSwapEvent => |e: RaydiumClmmSwapEvent| {
println!("RaydiumClmmSwapEvent: {e:?}");
},
RaydiumClmmSwapV2Event => |e: RaydiumClmmSwapV2Event| {
println!("RaydiumClmmSwapV2Event: {e:?}");
}
},
RaydiumClmmClosePositionEvent => |e: RaydiumClmmClosePositionEvent| {
println!("RaydiumClmmClosePositionEvent: {e:?}");
},
RaydiumClmmDecreaseLiquidityV2Event => |e: RaydiumClmmDecreaseLiquidityV2Event| {
println!("RaydiumClmmDecreaseLiquidityV2Event: {e:?}");
},
RaydiumClmmCreatePoolEvent => |e: RaydiumClmmCreatePoolEvent| {
println!("RaydiumClmmCreatePoolEvent: {e:?}");
},
RaydiumClmmIncreaseLiquidityV2Event => |e: RaydiumClmmIncreaseLiquidityV2Event| {
println!("RaydiumClmmIncreaseLiquidityV2Event: {e:?}");
},
RaydiumClmmOpenPositionWithToken22NftEvent => |e: RaydiumClmmOpenPositionWithToken22NftEvent| {
println!("RaydiumClmmOpenPositionWithToken22NftEvent: {e:?}");
},
RaydiumClmmOpenPositionV2Event => |e: RaydiumClmmOpenPositionV2Event| {
println!("RaydiumClmmOpenPositionV2Event: {e:?}");
},
// raydium_amm_v4
RaydiumAmmV4SwapEvent => |e: RaydiumAmmV4SwapEvent| {
println!("RaydiumAmmV4SwapEvent: {e:?}");
},
RaydiumAmmV4DepositEvent => |e: RaydiumAmmV4DepositEvent| {
println!("RaydiumAmmV4DepositEvent: {e:?}");
},
RaydiumAmmV4Initialize2Event => |e: RaydiumAmmV4Initialize2Event| {
println!("RaydiumAmmV4Initialize2Event: {e:?}");
},
RaydiumAmmV4WithdrawEvent => |e: RaydiumAmmV4WithdrawEvent| {
println!("RaydiumAmmV4WithdrawEvent: {e:?}");
},
RaydiumAmmV4WithdrawPnlEvent => |e: RaydiumAmmV4WithdrawPnlEvent| {
println!("RaydiumAmmV4WithdrawPnlEvent: {e:?}");
},
});
}
}
@@ -255,6 +326,7 @@ fn create_event_callback() -> impl Fn(Box<dyn UnifiedEvent>) {
- **Bonk**: Token launch platform (letsbonk.fun)
- **Raydium CPMM**: Raydium's Concentrated Pool Market Maker protocol
- **Raydium CLMM**: Raydium's Concentrated Liquidity Market Maker protocol
- **Raydium AMM V4**: Raydium's Automated Market Maker V4 protocol
## Event Streaming Services
@@ -295,6 +367,7 @@ src/
│ │ │ ├── bonk/ # Bonk event parsing
│ │ │ ├── pumpfun/ # PumpFun event parsing
│ │ │ ├── pumpswap/ # PumpSwap event parsing
│ │ │ ├── raydium_amm_v4/ # Raydium AMM V4 event parsing
│ │ │ ├── raydium_cpmm/ # Raydium CPMM event parsing
│ │ │ └── raydium_clmm/ # Raydium CLMM event parsing
│ │ └── factory.rs # Parser factory
+83 -10
View File
@@ -14,6 +14,7 @@
- **Bonk**: 代币发布平台事件 (letsbonk.fun)
- **Raydium CPMM**: Raydium 集中池做市商事件
- **Raydium CLMM**: Raydium 集中流动性做市商事件
- **Raydium AMM V4**: Raydium 自动做市商 V4 事件
5. **统一事件接口**: 在所有支持的协议中保持一致的事件处理
6. **事件解析系统**: 自动解析和分类协议特定事件
7. **高性能**: 针对低延迟事件处理进行优化
@@ -41,14 +42,14 @@ git clone https://github.com/0xfnzero/solana-streamer
```toml
# 添加到您的 Cargo.toml
solana-streamer-sdk = { path = "./solana-streamer", version = "0.2.0" }
solana-streamer-sdk = { path = "./solana-streamer", version = "0.2.1" }
```
### 使用 crates.io
```toml
# 添加到您的 Cargo.toml
solana-streamer-sdk = "0.2.0"
solana-streamer-sdk = "0.2.1"
```
## 使用示例
@@ -63,7 +64,7 @@ cargo run --example parse_tx_events
该示例演示了:
- 如何使用 RPC 从 Solana 主网解析交易数据
- 多协议事件解析(PumpFun、PumpSwap、Bonk、Raydium CPMM/CLMM
- 多协议事件解析(PumpFun、PumpSwap、Bonk、Raydium CPMM/CLMM/AMM V4
- 交易详情提取,包括费用、日志和计算单元
该示例使用预定义的交易签名,展示如何从交易数据中提取协议特定的事件。
@@ -80,18 +81,36 @@ use solana_streamer_sdk::{
parser::BONK_PROGRAM_ID, BonkMigrateToAmmEvent, BonkMigrateToCpswapEvent,
BonkPoolCreateEvent, BonkTradeEvent,
},
pumpfun::{parser::PUMPFUN_PROGRAM_ID, PumpFunCreateTokenEvent, PumpFunTradeEvent},
pumpfun::{
parser::PUMPFUN_PROGRAM_ID, PumpFunCreateTokenEvent, PumpFunMigrateEvent,
PumpFunTradeEvent,
},
pumpswap::{
parser::PUMPSWAP_PROGRAM_ID, PumpSwapBuyEvent, PumpSwapCreatePoolEvent,
PumpSwapDepositEvent, PumpSwapSellEvent, PumpSwapWithdrawEvent,
},
raydium_clmm::{
parser::RAYDIUM_CLMM_PROGRAM_ID, RaydiumClmmSwapEvent, RaydiumClmmSwapV2Event,
raydium_amm_v4::{
RaydiumAmmV4DepositEvent, RaydiumAmmV4Initialize2Event, RaydiumAmmV4SwapEvent,
RaydiumAmmV4WithdrawEvent, RaydiumAmmV4WithdrawPnlEvent,
},
raydium_cpmm::{parser::RAYDIUM_CPMM_PROGRAM_ID, RaydiumCpmmSwapEvent}, BlockMetaEvent,
raydium_clmm::{
parser::RAYDIUM_CLMM_PROGRAM_ID, RaydiumClmmClosePositionEvent,
RaydiumClmmCreatePoolEvent, RaydiumClmmDecreaseLiquidityV2Event,
RaydiumClmmIncreaseLiquidityV2Event, RaydiumClmmOpenPositionV2Event,
RaydiumClmmOpenPositionWithToken22NftEvent, RaydiumClmmSwapEvent,
RaydiumClmmSwapV2Event,
},
raydium_cpmm::{
parser::RAYDIUM_CPMM_PROGRAM_ID, RaydiumCpmmDepositEvent,
RaydiumCpmmInitializeEvent, RaydiumCpmmSwapEvent, RaydiumCpmmWithdrawEvent,
},
BlockMetaEvent,
},
Protocol, UnifiedEvent,
}, grpc::ClientConfig, shred_stream::ShredClientConfig, ShredStreamGrpc, YellowstoneGrpc
},
grpc::ClientConfig,
shred_stream::ShredClientConfig,
ShredStreamGrpc, YellowstoneGrpc,
},
};
@@ -138,7 +157,6 @@ async fn test_grpc() -> Result<(), Box<dyn std::error::Error>> {
BONK_PROGRAM_ID.to_string(), // 监听 bonk 程序ID
RAYDIUM_CPMM_PROGRAM_ID.to_string(), // 监听 raydium_cpmm 程序ID
RAYDIUM_CLMM_PROGRAM_ID.to_string(), // 监听 raydium_clmm 程序ID
"xxxxxxxx".to_string(), // 监听 xxxxx 账号
];
let account_exclude = vec![];
let account_required = vec![];
@@ -179,6 +197,7 @@ async fn test_shreds() -> Result<(), Box<dyn std::error::Error>> {
Protocol::Bonk,
Protocol::RaydiumCpmm,
Protocol::RaydiumClmm,
Protocol::RaydiumAmmV4,
];
println!("Listening for events, press Ctrl+C to stop...");
@@ -191,9 +210,11 @@ fn create_event_callback() -> impl Fn(Box<dyn UnifiedEvent>) {
|event: Box<dyn UnifiedEvent>| {
println!("🎉 Event received! Type: {:?}, ID: {}", event.event_type(), event.id());
match_event!(event, {
// block meta
BlockMetaEvent => |e: BlockMetaEvent| {
println!("BlockMetaEvent: {e:?}");
},
// bonk
BonkPoolCreateEvent => |e: BonkPoolCreateEvent| {
// 使用grpc的时候,可以从每个事件中获取到block_time
println!("block_time: {:?}, block_time_ms: {:?}", e.metadata.block_time, e.metadata.block_time_ms);
@@ -208,12 +229,17 @@ fn create_event_callback() -> impl Fn(Box<dyn UnifiedEvent>) {
BonkMigrateToCpswapEvent => |e: BonkMigrateToCpswapEvent| {
println!("BonkMigrateToCpswapEvent: {e:?}");
},
// pumpfun
PumpFunTradeEvent => |e: PumpFunTradeEvent| {
println!("PumpFunTradeEvent: {e:?}");
},
PumpFunMigrateEvent => |e: PumpFunMigrateEvent| {
println!("PumpFunMigrateEvent: {e:?}");
},
PumpFunCreateTokenEvent => |e: PumpFunCreateTokenEvent| {
println!("PumpFunCreateTokenEvent: {e:?}");
},
// pumpswap
PumpSwapBuyEvent => |e: PumpSwapBuyEvent| {
println!("Buy event: {e:?}");
},
@@ -229,15 +255,60 @@ fn create_event_callback() -> impl Fn(Box<dyn UnifiedEvent>) {
PumpSwapWithdrawEvent => |e: PumpSwapWithdrawEvent| {
println!("Withdraw event: {e:?}");
},
// raydium_cpmm
RaydiumCpmmSwapEvent => |e: RaydiumCpmmSwapEvent| {
println!("RaydiumCpmmSwapEvent: {e:?}");
},
RaydiumCpmmDepositEvent => |e: RaydiumCpmmDepositEvent| {
println!("RaydiumCpmmDepositEvent: {e:?}");
},
RaydiumCpmmInitializeEvent => |e: RaydiumCpmmInitializeEvent| {
println!("RaydiumCpmmInitializeEvent: {e:?}");
},
RaydiumCpmmWithdrawEvent => |e: RaydiumCpmmWithdrawEvent| {
println!("RaydiumCpmmWithdrawEvent: {e:?}");
},
// raydium_clmm
RaydiumClmmSwapEvent => |e: RaydiumClmmSwapEvent| {
println!("RaydiumClmmSwapEvent: {e:?}");
},
RaydiumClmmSwapV2Event => |e: RaydiumClmmSwapV2Event| {
println!("RaydiumClmmSwapV2Event: {e:?}");
}
},
RaydiumClmmClosePositionEvent => |e: RaydiumClmmClosePositionEvent| {
println!("RaydiumClmmClosePositionEvent: {e:?}");
},
RaydiumClmmDecreaseLiquidityV2Event => |e: RaydiumClmmDecreaseLiquidityV2Event| {
println!("RaydiumClmmDecreaseLiquidityV2Event: {e:?}");
},
RaydiumClmmCreatePoolEvent => |e: RaydiumClmmCreatePoolEvent| {
println!("RaydiumClmmCreatePoolEvent: {e:?}");
},
RaydiumClmmIncreaseLiquidityV2Event => |e: RaydiumClmmIncreaseLiquidityV2Event| {
println!("RaydiumClmmIncreaseLiquidityV2Event: {e:?}");
},
RaydiumClmmOpenPositionWithToken22NftEvent => |e: RaydiumClmmOpenPositionWithToken22NftEvent| {
println!("RaydiumClmmOpenPositionWithToken22NftEvent: {e:?}");
},
RaydiumClmmOpenPositionV2Event => |e: RaydiumClmmOpenPositionV2Event| {
println!("RaydiumClmmOpenPositionV2Event: {e:?}");
},
// raydium_amm_v4
RaydiumAmmV4SwapEvent => |e: RaydiumAmmV4SwapEvent| {
println!("RaydiumAmmV4SwapEvent: {e:?}");
},
RaydiumAmmV4DepositEvent => |e: RaydiumAmmV4DepositEvent| {
println!("RaydiumAmmV4DepositEvent: {e:?}");
},
RaydiumAmmV4Initialize2Event => |e: RaydiumAmmV4Initialize2Event| {
println!("RaydiumAmmV4Initialize2Event: {e:?}");
},
RaydiumAmmV4WithdrawEvent => |e: RaydiumAmmV4WithdrawEvent| {
println!("RaydiumAmmV4WithdrawEvent: {e:?}");
},
RaydiumAmmV4WithdrawPnlEvent => |e: RaydiumAmmV4WithdrawPnlEvent| {
println!("RaydiumAmmV4WithdrawPnlEvent: {e:?}");
},
});
}
}
@@ -250,6 +321,7 @@ fn create_event_callback() -> impl Fn(Box<dyn UnifiedEvent>) {
- **Bonk**: 代币发布平台 (letsbonk.fun)
- **Raydium CPMM**: Raydium 集中池做市商协议
- **Raydium CLMM**: Raydium 集中流动性做市商协议
- **Raydium AMM V4**: Raydium 自动做市商 V4 协议
## 事件流服务
@@ -290,6 +362,7 @@ src/
│ │ │ ├── bonk/ # Bonk 事件解析
│ │ │ ├── pumpfun/ # PumpFun 事件解析
│ │ │ ├── pumpswap/ # PumpSwap 事件解析
│ │ │ ├── raydium_amm_v4/ # Raydium AMM V4 事件解析
│ │ │ ├── raydium_cpmm/ # Raydium CPMM 事件解析
│ │ │ └── raydium_clmm/ # Raydium CLMM 事件解析
│ │ └── factory.rs # 解析器工厂
+2 -1
View File
@@ -10,7 +10,7 @@ use std::sync::Arc;
#[tokio::main]
async fn main() -> Result<()> {
let signatures = vec![
"tb6KxvMAZtctQw2yszKk6QGVuzbv1CAbS5mKK7JjVaeb2i5rCafNHfnqE1PhaWukx79sDYcsoZqbHXRJgPu93w4",
"42agNk1heHabNAVRzEKqQEt5adGkQzRYf9M1Q81uBJPCCHyP4cyCA1RNkgxXrtEAWeeGcytyh2TsnkBDgqnHeq4z",
];
// Validate signature format
let mut valid_signatures = Vec::new();
@@ -93,6 +93,7 @@ async fn get_single_transaction_details(signature_str: &str) -> Result<()> {
Protocol::PumpSwap,
Protocol::PumpFun,
Protocol::RaydiumCpmm,
Protocol::RaydiumAmmV4,
];
let parser: Arc<dyn EventParser> = Arc::new(MutilEventParser::new(protocols));
let start_time = std::time::Instant::now();
+77 -6
View File
@@ -7,18 +7,36 @@ use solana_streamer_sdk::{
parser::BONK_PROGRAM_ID, BonkMigrateToAmmEvent, BonkMigrateToCpswapEvent,
BonkPoolCreateEvent, BonkTradeEvent,
},
pumpfun::{parser::PUMPFUN_PROGRAM_ID, PumpFunCreateTokenEvent, PumpFunTradeEvent},
pumpfun::{
parser::PUMPFUN_PROGRAM_ID, PumpFunCreateTokenEvent, PumpFunMigrateEvent,
PumpFunTradeEvent,
},
pumpswap::{
parser::PUMPSWAP_PROGRAM_ID, PumpSwapBuyEvent, PumpSwapCreatePoolEvent,
PumpSwapDepositEvent, PumpSwapSellEvent, PumpSwapWithdrawEvent,
},
raydium_clmm::{
parser::RAYDIUM_CLMM_PROGRAM_ID, RaydiumClmmSwapEvent, RaydiumClmmSwapV2Event,
raydium_amm_v4::{
RaydiumAmmV4DepositEvent, RaydiumAmmV4Initialize2Event, RaydiumAmmV4SwapEvent,
RaydiumAmmV4WithdrawEvent, RaydiumAmmV4WithdrawPnlEvent,
},
raydium_cpmm::{parser::RAYDIUM_CPMM_PROGRAM_ID, RaydiumCpmmSwapEvent}, BlockMetaEvent,
raydium_clmm::{
parser::RAYDIUM_CLMM_PROGRAM_ID, RaydiumClmmClosePositionEvent,
RaydiumClmmCreatePoolEvent, RaydiumClmmDecreaseLiquidityV2Event,
RaydiumClmmIncreaseLiquidityV2Event, RaydiumClmmOpenPositionV2Event,
RaydiumClmmOpenPositionWithToken22NftEvent, RaydiumClmmSwapEvent,
RaydiumClmmSwapV2Event,
},
raydium_cpmm::{
parser::RAYDIUM_CPMM_PROGRAM_ID, RaydiumCpmmDepositEvent,
RaydiumCpmmInitializeEvent, RaydiumCpmmSwapEvent, RaydiumCpmmWithdrawEvent,
},
BlockMetaEvent,
},
Protocol, UnifiedEvent,
}, grpc::ClientConfig, shred_stream::ShredClientConfig, ShredStreamGrpc, YellowstoneGrpc
},
grpc::ClientConfig,
shred_stream::ShredClientConfig,
ShredStreamGrpc, YellowstoneGrpc,
},
};
@@ -105,6 +123,7 @@ async fn test_shreds() -> Result<(), Box<dyn std::error::Error>> {
Protocol::Bonk,
Protocol::RaydiumCpmm,
Protocol::RaydiumClmm,
Protocol::RaydiumAmmV4,
];
println!("Listening for events, press Ctrl+C to stop...");
@@ -117,9 +136,11 @@ fn create_event_callback() -> impl Fn(Box<dyn UnifiedEvent>) {
|event: Box<dyn UnifiedEvent>| {
println!("🎉 Event received! Type: {:?}, ID: {}", event.event_type(), event.id());
match_event!(event, {
// block meta
BlockMetaEvent => |e: BlockMetaEvent| {
println!("BlockMetaEvent: {e:?}");
},
// bonk
BonkPoolCreateEvent => |e: BonkPoolCreateEvent| {
// When using grpc, you can get block_time from each event
println!("block_time: {:?}, block_time_ms: {:?}", e.metadata.block_time, e.metadata.block_time_ms);
@@ -134,12 +155,17 @@ fn create_event_callback() -> impl Fn(Box<dyn UnifiedEvent>) {
BonkMigrateToCpswapEvent => |e: BonkMigrateToCpswapEvent| {
println!("BonkMigrateToCpswapEvent: {e:?}");
},
// pumpfun
PumpFunTradeEvent => |e: PumpFunTradeEvent| {
println!("PumpFunTradeEvent: {e:?}");
},
PumpFunMigrateEvent => |e: PumpFunMigrateEvent| {
println!("PumpFunMigrateEvent: {e:?}");
},
PumpFunCreateTokenEvent => |e: PumpFunCreateTokenEvent| {
println!("PumpFunCreateTokenEvent: {e:?}");
},
// pumpswap
PumpSwapBuyEvent => |e: PumpSwapBuyEvent| {
println!("Buy event: {e:?}");
},
@@ -155,15 +181,60 @@ fn create_event_callback() -> impl Fn(Box<dyn UnifiedEvent>) {
PumpSwapWithdrawEvent => |e: PumpSwapWithdrawEvent| {
println!("Withdraw event: {e:?}");
},
// raydium_cpmm
RaydiumCpmmSwapEvent => |e: RaydiumCpmmSwapEvent| {
println!("RaydiumCpmmSwapEvent: {e:?}");
},
RaydiumCpmmDepositEvent => |e: RaydiumCpmmDepositEvent| {
println!("RaydiumCpmmDepositEvent: {e:?}");
},
RaydiumCpmmInitializeEvent => |e: RaydiumCpmmInitializeEvent| {
println!("RaydiumCpmmInitializeEvent: {e:?}");
},
RaydiumCpmmWithdrawEvent => |e: RaydiumCpmmWithdrawEvent| {
println!("RaydiumCpmmWithdrawEvent: {e:?}");
},
// raydium_clmm
RaydiumClmmSwapEvent => |e: RaydiumClmmSwapEvent| {
println!("RaydiumClmmSwapEvent: {e:?}");
},
RaydiumClmmSwapV2Event => |e: RaydiumClmmSwapV2Event| {
println!("RaydiumClmmSwapV2Event: {e:?}");
}
},
RaydiumClmmClosePositionEvent => |e: RaydiumClmmClosePositionEvent| {
println!("RaydiumClmmClosePositionEvent: {e:?}");
},
RaydiumClmmDecreaseLiquidityV2Event => |e: RaydiumClmmDecreaseLiquidityV2Event| {
println!("RaydiumClmmDecreaseLiquidityV2Event: {e:?}");
},
RaydiumClmmCreatePoolEvent => |e: RaydiumClmmCreatePoolEvent| {
println!("RaydiumClmmCreatePoolEvent: {e:?}");
},
RaydiumClmmIncreaseLiquidityV2Event => |e: RaydiumClmmIncreaseLiquidityV2Event| {
println!("RaydiumClmmIncreaseLiquidityV2Event: {e:?}");
},
RaydiumClmmOpenPositionWithToken22NftEvent => |e: RaydiumClmmOpenPositionWithToken22NftEvent| {
println!("RaydiumClmmOpenPositionWithToken22NftEvent: {e:?}");
},
RaydiumClmmOpenPositionV2Event => |e: RaydiumClmmOpenPositionV2Event| {
println!("RaydiumClmmOpenPositionV2Event: {e:?}");
},
// raydium_amm_v4
RaydiumAmmV4SwapEvent => |e: RaydiumAmmV4SwapEvent| {
println!("RaydiumAmmV4SwapEvent: {e:?}");
},
RaydiumAmmV4DepositEvent => |e: RaydiumAmmV4DepositEvent| {
println!("RaydiumAmmV4DepositEvent: {e:?}");
},
RaydiumAmmV4Initialize2Event => |e: RaydiumAmmV4Initialize2Event| {
println!("RaydiumAmmV4Initialize2Event: {e:?}");
},
RaydiumAmmV4WithdrawEvent => |e: RaydiumAmmV4WithdrawEvent| {
println!("RaydiumAmmV4WithdrawEvent: {e:?}");
},
RaydiumAmmV4WithdrawPnlEvent => |e: RaydiumAmmV4WithdrawPnlEvent| {
println!("RaydiumAmmV4WithdrawPnlEvent: {e:?}");
},
});
}
}
+64 -26
View File
@@ -16,6 +16,7 @@ use crate::{
bonk::BonkTradeEvent,
pumpfun::PumpFunTradeEvent,
pumpswap::{PumpSwapBuyEvent, PumpSwapSellEvent},
raydium_amm_v4::RaydiumAmmV4SwapEvent,
raydium_clmm::{RaydiumClmmSwapEvent, RaydiumClmmSwapV2Event},
raydium_cpmm::RaydiumCpmmSwapEvent,
},
@@ -101,6 +102,7 @@ pub enum ProtocolType {
Bonk,
RaydiumCpmm,
RaydiumClmm,
RaydiumAmmV4,
Common,
}
@@ -121,6 +123,7 @@ pub enum EventType {
PumpFunCreateToken,
PumpFunBuy,
PumpFunSell,
PumpFunMigrate,
// Bonk events
BonkBuyExactIn,
@@ -134,10 +137,27 @@ pub enum EventType {
// Raydium CPMM events
RaydiumCpmmSwapBaseInput,
RaydiumCpmmSwapBaseOutput,
RaydiumCpmmDeposit,
RaydiumCpmmInitialize,
RaydiumCpmmWithdraw,
// Raydium CLMM events
RaydiumClmmSwap,
RaydiumClmmSwapV2,
RaydiumClmmClosePosition,
RaydiumClmmIncreaseLiquidityV2,
RaydiumClmmDecreaseLiquidityV2,
RaydiumClmmCreatePool,
RaydiumClmmOpenPositionWithToken22Nft,
RaydiumClmmOpenPositionV2,
// Raydium AMM V4 events
RaydiumAmmV4SwapBaseIn,
RaydiumAmmV4SwapBaseOut,
RaydiumAmmV4Deposit,
RaydiumAmmV4Initialize2,
RaydiumAmmV4Withdraw,
RaydiumAmmV4WithdrawPnl,
// Common events
BlockMeta,
@@ -156,6 +176,7 @@ impl EventType {
EventType::PumpFunCreateToken => "PumpFunCreateToken".to_string(),
EventType::PumpFunBuy => "PumpFunBuy".to_string(),
EventType::PumpFunSell => "PumpFunSell".to_string(),
EventType::PumpFunMigrate => "PumpFunMigrate".to_string(),
EventType::BonkBuyExactIn => "BonkBuyExactIn".to_string(),
EventType::BonkBuyExactOut => "BonkBuyExactOut".to_string(),
EventType::BonkSellExactIn => "BonkSellExactIn".to_string(),
@@ -165,8 +186,29 @@ impl EventType {
EventType::BonkMigrateToCpswap => "BonkMigrateToCpswap".to_string(),
EventType::RaydiumCpmmSwapBaseInput => "RaydiumCpmmSwapBaseInput".to_string(),
EventType::RaydiumCpmmSwapBaseOutput => "RaydiumCpmmSwapBaseOutput".to_string(),
EventType::RaydiumCpmmDeposit => "RaydiumCpmmDeposit".to_string(),
EventType::RaydiumCpmmInitialize => "RaydiumCpmmInitialize".to_string(),
EventType::RaydiumCpmmWithdraw => "RaydiumCpmmWithdraw".to_string(),
EventType::RaydiumClmmSwap => "RaydiumClmmSwap".to_string(),
EventType::RaydiumClmmSwapV2 => "RaydiumClmmSwapV2".to_string(),
EventType::RaydiumClmmClosePosition => "RaydiumClmmClosePosition".to_string(),
EventType::RaydiumClmmDecreaseLiquidityV2 => {
"RaydiumClmmDecreaseLiquidityV2".to_string()
}
EventType::RaydiumClmmCreatePool => "RaydiumClmmCreatePool".to_string(),
EventType::RaydiumClmmIncreaseLiquidityV2 => {
"RaydiumClmmIncreaseLiquidityV2".to_string()
}
EventType::RaydiumClmmOpenPositionWithToken22Nft => {
"RaydiumClmmOpenPositionWithToken22Nft".to_string()
}
EventType::RaydiumClmmOpenPositionV2 => "RaydiumClmmOpenPositionV2".to_string(),
EventType::RaydiumAmmV4SwapBaseIn => "RaydiumAmmV4SwapBaseIn".to_string(),
EventType::RaydiumAmmV4SwapBaseOut => "RaydiumAmmV4SwapBaseOut".to_string(),
EventType::RaydiumAmmV4Deposit => "RaydiumAmmV4Deposit".to_string(),
EventType::RaydiumAmmV4Initialize2 => "RaydiumAmmV4Initialize2".to_string(),
EventType::RaydiumAmmV4Withdraw => "RaydiumAmmV4Withdraw".to_string(),
EventType::RaydiumAmmV4WithdrawPnl => "RaydiumAmmV4WithdrawPnl".to_string(),
EventType::BlockMeta => "BlockMeta".to_string(),
EventType::Unknown => "Unknown".to_string(),
}
@@ -321,37 +363,25 @@ pub fn parse_transfer_datas_from_next_instructions(
inner_instruction: &solana_transaction_status::UiInnerInstructions,
current_index: i8,
accounts: &[Pubkey],
event_type: EventType,
) -> (Vec<TransferData>, Option<SwapData>) {
let take = match event_type {
EventType::PumpFunBuy => 4,
EventType::PumpFunSell => 1,
EventType::PumpSwapBuy => 3,
EventType::PumpSwapSell => 3,
EventType::BonkBuyExactIn
| EventType::BonkBuyExactOut
| EventType::BonkSellExactIn
| EventType::BonkSellExactOut => 3,
EventType::RaydiumCpmmSwapBaseInput
| EventType::RaydiumCpmmSwapBaseOutput
| EventType::RaydiumClmmSwap
| EventType::RaydiumClmmSwapV2 => 2,
_ => 0,
};
if take == 0 {
return (vec![], None);
}
let mut transfer_datas = vec![];
// Get the next two instructions after the current instruction
let next_instructions: Vec<&UiInstruction> = inner_instruction
.instructions
.iter()
.skip((current_index + 1) as usize)
.take(take)
.collect();
let next_instructions: Vec<&UiInstruction> =
inner_instruction.instructions.iter().skip((current_index + 1) as usize).collect();
let system_programs = vec![
// Token Program
Pubkey::from_str("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA").unwrap(),
// Token 2022 Program
Pubkey::from_str("TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb").unwrap(),
// System Program
Pubkey::from_str("11111111111111111111111111111111").unwrap(),
];
for instruction in next_instructions {
if let UiInstruction::Compiled(compiled) = instruction {
if !system_programs.contains(&accounts[compiled.program_id_index as usize]) {
break;
}
if let Ok(data) = bs58::decode(compiled.data.clone()).into_vec() {
// Token Program: transferChecked
// Token 2022 Program: transferChecked
@@ -495,7 +525,15 @@ pub fn parse_transfer_datas_from_next_instructions(
user_to_token = Some(e.output_token_account);
from_vault = Some(e.input_vault);
to_vault = Some(e.output_vault);
}
},
RaydiumAmmV4SwapEvent => |e: RaydiumAmmV4SwapEvent| {
user = Some(e.user_source_owner);
swap_data.description = Some("Unable to get from_mint and to_mint from RaydiumAmmV4SwapEvent".to_string());
user_from_token = Some(e.user_source_token_account);
user_to_token = Some(e.user_destination_token_account);
from_vault = Some(e.pool_pc_token_account);
to_vault = Some(e.pool_coin_token_account);
},
});
for transfer_data in transfer_datas.clone() {
+23 -4
View File
@@ -4,10 +4,7 @@ use std::time::{SystemTime, UNIX_EPOCH};
/// 获取当前时间戳
pub fn current_timestamp() -> i64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("Time went backwards")
.as_secs() as i64
SystemTime::now().duration_since(UNIX_EPOCH).expect("Time went backwards").as_secs() as i64
}
/// 从base64字符串解码数据
@@ -57,6 +54,14 @@ pub fn read_u64_le(data: &[u8], offset: usize) -> Option<u64> {
Some(u64::from_le_bytes(bytes))
}
pub fn read_i32_le(data: &[u8], offset: usize) -> Option<i32> {
if data.len() < offset + 4 {
return None;
}
let bytes: [u8; 4] = data[offset..offset + 4].try_into().ok()?;
Some(i32::from_le_bytes(bytes))
}
pub fn read_u128_le(data: &[u8], offset: usize) -> Option<u128> {
if data.len() < offset + 16 {
return None;
@@ -73,6 +78,20 @@ pub fn read_u8_le(data: &[u8], offset: usize) -> Option<u8> {
Some(u8::from_le_bytes(bytes))
}
pub fn read_option_bool(data: &[u8], offset: &mut usize) -> Option<Option<bool>> {
let has_value = data.get(*offset)?.clone();
*offset += 1;
if has_value == 0 {
return Some(None);
}
let value = data.get(*offset)?.clone();
*offset += 1;
Some(Some(value != 0))
}
/// 安全地从字节数组中读取u32
pub fn read_u32_le(data: &[u8], offset: usize) -> Option<u32> {
if data.len() < offset + 4 {
@@ -158,7 +158,6 @@ pub trait EventParser: Send + Sync {
inn,
-1_i8,
&accounts,
event.event_type(),
);
event.set_transfer_datas(transfer_datas, swap_data);
});
@@ -309,7 +308,6 @@ pub trait EventParser: Send + Sync {
&inner_instruction,
index as i8,
&accounts,
event.event_type(),
);
event.set_transfer_datas(transfer_datas, swap_data);
});
@@ -335,7 +333,6 @@ pub trait EventParser: Send + Sync {
&inner_instruction,
index as i8,
&accounts,
event.event_type(),
);
event.set_transfer_datas(transfer_datas, swap_data);
});
+7 -5
View File
@@ -3,10 +3,7 @@ use solana_sdk::pubkey::Pubkey;
use std::{collections::HashMap, sync::{Arc, LazyLock}};
use crate::streaming::event_parser::protocols::{
bonk::parser::BONK_PROGRAM_ID, pumpfun::parser::PUMPFUN_PROGRAM_ID,
pumpswap::parser::PUMPSWAP_PROGRAM_ID, raydium_cpmm::parser::RAYDIUM_CPMM_PROGRAM_ID,
raydium_clmm::parser::RAYDIUM_CLMM_PROGRAM_ID, BonkEventParser, RaydiumCpmmEventParser,
RaydiumClmmEventParser,
bonk::parser::BONK_PROGRAM_ID, pumpfun::parser::PUMPFUN_PROGRAM_ID, pumpswap::parser::PUMPSWAP_PROGRAM_ID, raydium_amm_v4::parser::RAYDIUM_AMM_V4_PROGRAM_ID, raydium_clmm::parser::RAYDIUM_CLMM_PROGRAM_ID, raydium_cpmm::parser::RAYDIUM_CPMM_PROGRAM_ID, BonkEventParser, RaydiumAmmV4EventParser, RaydiumClmmEventParser, RaydiumCpmmEventParser
};
use super::{
@@ -22,6 +19,7 @@ pub enum Protocol {
Bonk,
RaydiumCpmm,
RaydiumClmm,
RaydiumAmmV4,
}
impl Protocol {
@@ -32,6 +30,7 @@ impl Protocol {
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],
}
}
}
@@ -44,6 +43,7 @@ impl std::fmt::Display for Protocol {
Protocol::Bonk => write!(f, "Bonk"),
Protocol::RaydiumCpmm => write!(f, "RaydiumCpmm"),
Protocol::RaydiumClmm => write!(f, "RaydiumClmm"),
Protocol::RaydiumAmmV4 => write!(f, "RaydiumAmmV4"),
}
}
}
@@ -58,6 +58,7 @@ impl std::str::FromStr for Protocol {
"bonk" => Ok(Protocol::Bonk),
"raydiumcpmm" => Ok(Protocol::RaydiumCpmm),
"raydiumclmm" => Ok(Protocol::RaydiumClmm),
"raydiumammv4" => Ok(Protocol::RaydiumAmmV4),
_ => Err(anyhow!("Unsupported protocol: {}", s)),
}
}
@@ -65,12 +66,13 @@ impl std::str::FromStr for Protocol {
static EVENT_PARSERS: LazyLock<HashMap<Protocol, Arc<dyn EventParser>>> = LazyLock::new(|| {
// 预分配容量,避免动态扩容
let mut parsers: HashMap<Protocol, Arc<dyn EventParser>> = HashMap::with_capacity(5);
let mut parsers: HashMap<Protocol, Arc<dyn EventParser>> = HashMap::with_capacity(6);
parsers.insert(Protocol::PumpSwap, Arc::new(PumpSwapEventParser::new()));
parsers.insert(Protocol::PumpFun, Arc::new(PumpFunEventParser::new()));
parsers.insert(Protocol::Bonk, Arc::new(BonkEventParser::new()));
parsers.insert(Protocol::RaydiumCpmm, Arc::new(RaydiumCpmmEventParser::new()));
parsers.insert(Protocol::RaydiumClmm, Arc::new(RaydiumClmmEventParser::new()));
parsers.insert(Protocol::RaydiumAmmV4, Arc::new(RaydiumAmmV4EventParser::new()));
parsers
});
@@ -3,6 +3,7 @@ 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;
@@ -11,5 +12,6 @@ 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 use block::block_meta_event::BlockMetaEvent;
pub use mutil::MutilEventParser;
@@ -104,14 +104,84 @@ impl_unified_event!(
creator_fee
);
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)]
pub struct PumpFunMigrateEvent {
#[borsh(skip)]
pub metadata: EventMetadata,
pub user: Pubkey,
pub mint: Pubkey,
pub mint_amount: u64,
pub sol_amount: u64,
pub pool_migration_fee: u64,
pub bonding_curve: Pubkey,
pub timestamp: i64,
pub pool: Pubkey,
#[borsh(skip)]
pub global: Pubkey,
#[borsh(skip)]
pub withdraw_authority: Pubkey,
#[borsh(skip)]
pub associated_bonding_curve: Pubkey,
#[borsh(skip)]
pub system_program: Pubkey,
#[borsh(skip)]
pub token_program: Pubkey,
#[borsh(skip)]
pub pump_amm: Pubkey,
#[borsh(skip)]
pub pool_authority: Pubkey,
#[borsh(skip)]
pub pool_authority_mint_account: Pubkey,
#[borsh(skip)]
pub pool_authority_wsol_account: Pubkey,
#[borsh(skip)]
pub amm_global_config: Pubkey,
#[borsh(skip)]
pub wsol_mint: Pubkey,
#[borsh(skip)]
pub lp_mint: 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,
#[borsh(skip)]
pub token_2022_program: Pubkey,
#[borsh(skip)]
pub associated_token_program: Pubkey,
#[borsh(skip)]
pub pump_amm_event_authority: Pubkey,
#[borsh(skip)]
pub event_authority: Pubkey,
#[borsh(skip)]
pub program: Pubkey,
}
impl_unified_event!(
PumpFunMigrateEvent,
user,
mint,
mint_amount,
sol_amount,
pool_migration_fee,
bonding_curve,
timestamp,
pool
);
/// 事件鉴别器常量
pub mod discriminators {
// 事件鉴别器
pub const CREATE_TOKEN_EVENT: &str = "0xe445a52e51cb9a1d1b72a94ddeeb6376";
pub const TRADE_EVENT: &str = "0xe445a52e51cb9a1dbddb7fd34ee661ee";
pub const COMPLETE_PUMP_AMM_MIGRATION_EVENT: &str = "0xe445a52e51cb9a1dbde95db95c94ea94";
// 指令鉴别器
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];
pub const MIGRATE_IX: &[u8] = &[155, 234, 231, 146, 236, 158, 162, 30];
}
@@ -7,7 +7,9 @@ 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},
protocols::pumpfun::{
discriminators, PumpFunCreateTokenEvent, PumpFunMigrateEvent, PumpFunTradeEvent,
},
};
/// PumpFun程序ID
@@ -56,6 +58,15 @@ impl PumpFunEventParser {
inner_instruction_parser: Self::parse_trade_inner_instruction,
instruction_parser: Self::parse_sell_instruction,
},
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: Self::parse_migrate_inner_instruction,
instruction_parser: Self::parse_migrate_instruction,
},
];
let inner = GenericEventParser::new(vec![PUMPFUN_PROGRAM_ID], configs);
@@ -63,6 +74,20 @@ impl PumpFunEventParser {
Self { inner }
}
/// 解析迁移事件
fn parse_migrate_inner_instruction(
data: &[u8],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if let Ok(event) = borsh::from_slice::<PumpFunMigrateEvent>(data) {
let mut metadata = metadata;
metadata.set_id(format!("{}-{}-{}", metadata.signature, event.user, event.mint));
Some(Box::new(PumpFunMigrateEvent { metadata, ..event }))
} else {
None
}
}
/// 解析创建代币日志事件
fn parse_create_token_inner_instruction(
data: &[u8],
@@ -72,15 +97,9 @@ impl PumpFunEventParser {
let mut metadata = metadata;
metadata.set_id(format!(
"{}-{}-{}-{}",
metadata.signature,
event.name,
event.symbol,
event.mint
metadata.signature, event.name, event.symbol, event.mint
));
Some(Box::new(PumpFunCreateTokenEvent {
metadata,
..event
}))
Some(Box::new(PumpFunCreateTokenEvent { metadata, ..event }))
} else {
None
}
@@ -95,15 +114,9 @@ impl PumpFunEventParser {
let mut metadata = metadata;
metadata.set_id(format!(
"{}-{}-{}-{}",
metadata.signature,
event.mint,
event.user,
event.is_buy
metadata.signature, event.mint, event.user, event.is_buy
));
Some(Box::new(PumpFunTradeEvent {
metadata,
..event
}))
Some(Box::new(PumpFunTradeEvent { metadata, ..event }))
} else {
None
}
@@ -138,13 +151,7 @@ impl PumpFunEventParser {
};
let mut metadata = metadata;
metadata.set_id(format!(
"{}-{}-{}-{}",
metadata.signature,
name,
symbol,
accounts[0]
));
metadata.set_id(format!("{}-{}-{}-{}", metadata.signature, name, symbol, accounts[0]));
Some(Box::new(PumpFunCreateTokenEvent {
metadata,
@@ -173,13 +180,7 @@ impl PumpFunEventParser {
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],
accounts[6],
true
));
metadata.set_id(format!("{}-{}-{}-{}", metadata.signature, accounts[2], accounts[6], true));
Some(Box::new(PumpFunTradeEvent {
metadata,
fee_recipient: accounts[1],
@@ -210,13 +211,8 @@ impl PumpFunEventParser {
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],
accounts[6],
false
));
metadata
.set_id(format!("{}-{}-{}-{}", metadata.signature, accounts[2], accounts[6], false));
Some(Box::new(PumpFunTradeEvent {
metadata,
fee_recipient: accounts[1],
@@ -232,6 +228,47 @@ impl PumpFunEventParser {
..Default::default()
}))
}
/// 解析迁移指令事件
fn parse_migrate_instruction(
_data: &[u8],
accounts: &[Pubkey],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if accounts.len() < 24 {
return None;
}
let mut metadata = metadata;
metadata.set_id(format!("{}-{}-{}", metadata.signature, accounts[5], accounts[2]));
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()
}))
}
}
#[async_trait::async_trait]
@@ -0,0 +1,163 @@
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 RaydiumAmmV4SwapEvent {
pub metadata: EventMetadata,
// base in
pub amount_in: u64,
pub minimum_amount_out: u64,
// base out
pub max_amount_in: u64,
pub amount_out: u64,
pub token_program: Pubkey,
pub amm: Pubkey,
pub amm_authority: Pubkey,
pub amm_open_orders: Pubkey,
pub amm_target_orders: Option<Pubkey>,
pub pool_coin_token_account: Pubkey,
pub pool_pc_token_account: Pubkey,
pub serum_program: Pubkey,
pub serum_market: Pubkey,
pub serum_bids: Pubkey,
pub serum_asks: Pubkey,
pub serum_event_queue: Pubkey,
pub serum_coin_vault_account: Pubkey,
pub serum_pc_vault_account: Pubkey,
pub serum_vault_signer: Pubkey,
pub user_source_token_account: Pubkey,
pub user_destination_token_account: Pubkey,
pub user_source_owner: Pubkey,
}
impl_unified_event!(RaydiumAmmV4SwapEvent,);
/// 添加流动性
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)]
pub struct RaydiumAmmV4DepositEvent {
pub metadata: EventMetadata,
pub max_coin_amount: u64,
pub max_pc_amount: u64,
pub base_side: u64,
pub token_program: Pubkey,
pub amm: Pubkey,
pub amm_authority: Pubkey,
pub amm_open_orders: Pubkey,
pub amm_target_orders: Pubkey,
pub lp_mint_address: Pubkey,
pub pool_coin_token_account: Pubkey,
pub pool_pc_token_account: Pubkey,
pub serum_market: Pubkey,
pub user_coin_token_account: Pubkey,
pub user_pc_token_account: Pubkey,
pub user_lp_token_account: Pubkey,
pub user_owner: Pubkey,
pub serum_event_queue: Pubkey,
}
impl_unified_event!(RaydiumAmmV4DepositEvent,);
/// 初始化
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)]
pub struct RaydiumAmmV4Initialize2Event {
pub metadata: EventMetadata,
pub nonce: u8,
pub open_time: u64,
pub init_pc_amount: u64,
pub init_coin_amount: u64,
pub token_program: Pubkey,
pub spl_associated_token_account: Pubkey,
pub system_program: Pubkey,
pub rent: Pubkey,
pub amm: Pubkey,
pub amm_authority: Pubkey,
pub amm_open_orders: Pubkey,
pub lp_mint: Pubkey,
pub coin_mint: Pubkey,
pub pc_mint: Pubkey,
pub pool_coin_token_account: Pubkey,
pub pool_pc_token_account: Pubkey,
pub pool_withdraw_queue: Pubkey,
pub amm_target_orders: Pubkey,
pub pool_temp_lp: Pubkey,
pub serum_program: Pubkey,
pub serum_market: Pubkey,
pub user_wallet: Pubkey,
pub user_token_coin: Pubkey,
pub user_token_pc: Pubkey,
pub user_lp_token_account: Pubkey,
}
impl_unified_event!(RaydiumAmmV4Initialize2Event,);
/// 移除流动性
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)]
pub struct RaydiumAmmV4WithdrawEvent {
pub metadata: EventMetadata,
pub amount: u64,
pub token_program: Pubkey,
pub amm: Pubkey,
pub amm_authority: Pubkey,
pub amm_open_orders: Pubkey,
pub amm_target_orders: Pubkey,
pub lp_mint_address: Pubkey,
pub pool_coin_token_account: Pubkey,
pub pool_pc_token_account: Pubkey,
pub pool_withdraw_queue: Pubkey,
pub pool_temp_lp_token_account: Pubkey,
pub serum_program: Pubkey,
pub serum_market: Pubkey,
pub serum_coin_vault_account: Pubkey,
pub serum_pc_vault_account: Pubkey,
pub serum_vault_signer: Pubkey,
pub user_lp_token_account: Pubkey,
pub user_coin_token_account: Pubkey,
pub user_pc_token_account: Pubkey,
pub user_owner: Pubkey,
pub serum_event_queue: Pubkey,
pub serum_bids: Pubkey,
pub serum_asks: Pubkey,
}
impl_unified_event!(RaydiumAmmV4WithdrawEvent,);
/// 提现
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)]
pub struct RaydiumAmmV4WithdrawPnlEvent {
pub metadata: EventMetadata,
pub token_program: Pubkey,
pub amm: Pubkey,
pub amm_config: Pubkey,
pub amm_authority: Pubkey,
pub amm_open_orders: Pubkey,
pub pool_coin_token_account: Pubkey,
pub pool_pc_token_account: Pubkey,
pub coin_pnl_token_account: Pubkey,
pub pc_pnl_token_account: Pubkey,
pub pnl_owner_account: Pubkey,
pub amm_target_orders: Pubkey,
pub serum_program: Pubkey,
pub serum_market: Pubkey,
pub serum_event_queue: Pubkey,
pub serum_coin_vault_account: Pubkey,
pub serum_pc_vault_account: Pubkey,
pub serum_vault_signer: Pubkey,
}
impl_unified_event!(RaydiumAmmV4WithdrawPnlEvent,);
/// 事件鉴别器常量
pub mod discriminators {
// 指令鉴别器
pub const SWAP_BASE_IN: &[u8] = &[9];
pub const SWAP_BASE_OUT: &[u8] = &[11];
pub const DEPOSIT: &[u8] = &[03];
pub const INITIALIZE2: &[u8] = &[01];
pub const WITHDRAW: &[u8] = &[04];
pub const WITHDRAW_PNL: &[u8] = &[07];
}
@@ -0,0 +1,5 @@
pub mod events;
pub mod parser;
pub use events::*;
pub use parser::RaydiumAmmV4EventParser;
@@ -0,0 +1,439 @@
use std::collections::HashMap;
use prost_types::Timestamp;
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_amm_v4::{
discriminators, RaydiumAmmV4DepositEvent, RaydiumAmmV4Initialize2Event,
RaydiumAmmV4SwapEvent, RaydiumAmmV4WithdrawEvent, RaydiumAmmV4WithdrawPnlEvent,
},
};
/// Raydium CPMM程序ID
pub const RAYDIUM_AMM_V4_PROGRAM_ID: Pubkey =
solana_sdk::pubkey!("675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8");
/// Raydium CPMM事件解析器
pub struct RaydiumAmmV4EventParser {
inner: GenericEventParser,
}
impl Default for RaydiumAmmV4EventParser {
fn default() -> Self {
Self::new()
}
}
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: Self::empty_parse,
instruction_parser: Self::parse_swap_base_input_instruction,
},
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: Self::empty_parse,
instruction_parser: Self::parse_swap_base_output_instruction,
},
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: Self::empty_parse,
instruction_parser: Self::parse_deposit_instruction,
},
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: Self::empty_parse,
instruction_parser: Self::parse_initialize2_instruction,
},
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: Self::empty_parse,
instruction_parser: Self::parse_withdraw_instruction,
},
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: Self::empty_parse,
instruction_parser: Self::parse_withdraw_pnl_instruction,
},
];
let inner = GenericEventParser::new(vec![RAYDIUM_AMM_V4_PROGRAM_ID], configs);
Self { inner }
}
fn empty_parse(_data: &[u8], _metadata: EventMetadata) -> Option<Box<dyn UnifiedEvent>> {
None
}
/// 解析提现指令事件
fn parse_withdraw_pnl_instruction(
_data: &[u8],
accounts: &[Pubkey],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if accounts.len() < 17 {
return None;
}
let mut metadata = metadata;
metadata.set_id(format!(
"{}-{}-{}-{}",
metadata.signature, accounts[3], accounts[10], accounts[11]
));
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)?;
let mut metadata = metadata;
metadata.set_id(format!(
"{}-{}-{}-{}",
metadata.signature, accounts[3], accounts[10], accounts[11]
));
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)?;
let mut metadata = metadata;
metadata.set_id(format!(
"{}-{}-{}-{}",
metadata.signature, accounts[3], accounts[10], accounts[11]
));
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)?;
let mut metadata = metadata;
metadata.set_id(format!(
"{}-{}-{}-{}",
metadata.signature, accounts[3], accounts[10], accounts[11]
));
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 metadata = metadata;
metadata.set_id(format!(
"{}-{}-{}-{}",
metadata.signature, accounts[3], accounts[10], accounts[11]
));
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 metadata = metadata;
metadata.set_id(format!(
"{}-{}-{}-{}",
metadata.signature, accounts[3], accounts[10], accounts[11]
));
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()
}))
}
}
#[async_trait::async_trait]
impl EventParser for RaydiumAmmV4EventParser {
fn inner_instruction_configs(&self) -> HashMap<&'static str, Vec<GenericEventParseConfig>> {
self.inner.inner_instruction_configs()
}
fn instruction_configs(&self) -> HashMap<Vec<u8>, Vec<GenericEventParseConfig>> {
self.inner.instruction_configs()
}
fn parse_events_from_inner_instruction(
&self,
inner_instruction: &UiCompiledInstruction,
signature: &str,
slot: u64,
block_time: Option<Timestamp>,
program_received_time_ms: i64,
index: String,
) -> Vec<Box<dyn UnifiedEvent>> {
self.inner.parse_events_from_inner_instruction(
inner_instruction,
signature,
slot,
block_time,
program_received_time_ms,
index,
)
}
fn parse_events_from_instruction(
&self,
instruction: &CompiledInstruction,
accounts: &[Pubkey],
signature: &str,
slot: u64,
block_time: Option<Timestamp>,
program_received_time_ms: i64,
index: String,
) -> Vec<Box<dyn UnifiedEvent>> {
self.inner.parse_events_from_instruction(
instruction,
accounts,
signature,
slot,
block_time,
program_received_time_ms,
index,
)
}
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()
}
}
@@ -27,6 +27,7 @@ pub struct RaydiumClmmSwapEvent {
impl_unified_event!(RaydiumClmmSwapEvent,);
/// 交易v2
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct RaydiumClmmSwapV2Event {
pub metadata: EventMetadata,
@@ -51,9 +52,180 @@ pub struct RaydiumClmmSwapV2Event {
}
impl_unified_event!(RaydiumClmmSwapV2Event,);
/// 关闭仓位
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct RaydiumClmmClosePositionEvent {
pub metadata: EventMetadata,
pub nft_owner: Pubkey,
pub position_nft_mint: Pubkey,
pub position_nft_account: Pubkey,
pub personal_position: Pubkey,
pub system_program: Pubkey,
pub token_program: Pubkey,
}
impl_unified_event!(RaydiumClmmClosePositionEvent,);
/// 减少流动性v2
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct RaydiumClmmDecreaseLiquidityV2Event {
pub metadata: EventMetadata,
pub liquidity: u128,
pub amount0_min: u64,
pub amount1_min: u64,
pub nft_owner: Pubkey,
pub nft_account: Pubkey,
pub personal_position: Pubkey,
pub pool_state: Pubkey,
pub protocol_position: Pubkey,
pub token_vault0: Pubkey,
pub token_vault1: Pubkey,
pub tick_array_lower: Pubkey,
pub tick_array_upper: Pubkey,
pub recipient_token_account0: Pubkey,
pub recipient_token_account1: Pubkey,
pub token_program: Pubkey,
pub token_program2022: Pubkey,
pub memo_program: Pubkey,
pub vault0_mint: Pubkey,
pub vault1_mint: Pubkey,
pub remaining_accounts: Vec<Pubkey>,
}
impl_unified_event!(RaydiumClmmDecreaseLiquidityV2Event,);
/// 创建池
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct RaydiumClmmCreatePoolEvent {
pub metadata: EventMetadata,
pub sqrt_price_x64: u128,
pub open_time: u64,
pub pool_creator: Pubkey,
pub amm_config: Pubkey,
pub pool_state: Pubkey,
pub token_mint0: Pubkey,
pub token_mint1: Pubkey,
pub token_vault0: Pubkey,
pub token_vault1: Pubkey,
pub observation_state: Pubkey,
pub tick_array_bitmap: Pubkey,
pub token_program0: Pubkey,
pub token_program1: Pubkey,
pub system_program: Pubkey,
pub rent: Pubkey,
}
impl_unified_event!(RaydiumClmmCreatePoolEvent,);
/// 增加流动性v2
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct RaydiumClmmIncreaseLiquidityV2Event {
pub metadata: EventMetadata,
pub liquidity: u128,
pub amount0_max: u64,
pub amount1_max: u64,
pub base_flag: Option<bool>,
pub nft_owner: Pubkey,
pub nft_account: Pubkey,
pub pool_state: Pubkey,
pub protocol_position: Pubkey,
pub personal_position: Pubkey,
pub tick_array_lower: Pubkey,
pub tick_array_upper: Pubkey,
pub token_account0: Pubkey,
pub token_account1: Pubkey,
pub token_vault0: Pubkey,
pub token_vault1: Pubkey,
pub token_program: Pubkey,
pub token_program2022: Pubkey,
pub vault0_mint: Pubkey,
pub vault1_mint: Pubkey,
}
impl_unified_event!(RaydiumClmmIncreaseLiquidityV2Event,);
/// 打开仓位v2
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct RaydiumClmmOpenPositionWithToken22NftEvent {
pub metadata: EventMetadata,
pub tick_lower_index: i32,
pub tick_upper_index: i32,
pub tick_array_lower_start_index: i32,
pub tick_array_upper_start_index: i32,
pub liquidity: u128,
pub amount0_max: u64,
pub amount1_max: u64,
pub with_metadata: bool,
pub base_flag: Option<bool>,
pub payer: Pubkey,
pub position_nft_owner: Pubkey,
pub position_nft_mint: Pubkey,
pub position_nft_account: Pubkey,
pub pool_state: Pubkey,
pub protocol_position: Pubkey,
pub tick_array_lower: Pubkey,
pub tick_array_upper: Pubkey,
pub personal_position: Pubkey,
pub token_account0: Pubkey,
pub token_account1: Pubkey,
pub token_vault0: Pubkey,
pub token_vault1: Pubkey,
pub rent: Pubkey,
pub system_program: Pubkey,
pub token_program: Pubkey,
pub associated_token_program: Pubkey,
pub token_program2022: Pubkey,
pub vault0_mint: Pubkey,
pub vault1_mint: Pubkey,
}
impl_unified_event!(RaydiumClmmOpenPositionWithToken22NftEvent,);
/// 打开仓位V2
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct RaydiumClmmOpenPositionV2Event {
pub metadata: EventMetadata,
pub tick_lower_index: i32,
pub tick_upper_index: i32,
pub tick_array_lower_start_index: i32,
pub tick_array_upper_start_index: i32,
pub liquidity: u128,
pub amount0_max: u64,
pub amount1_max: u64,
pub with_metadata: bool,
pub base_flag: Option<bool>,
pub payer: Pubkey,
pub position_nft_owner: Pubkey,
pub position_nft_mint: Pubkey,
pub position_nft_account: Pubkey,
pub metadata_account: Pubkey,
pub pool_state: Pubkey,
pub protocol_position: Pubkey,
pub tick_array_lower: Pubkey,
pub tick_array_upper: Pubkey,
pub personal_position: Pubkey,
pub token_account0: Pubkey,
pub token_account1: Pubkey,
pub token_vault0: Pubkey,
pub token_vault1: Pubkey,
pub rent: Pubkey,
pub system_program: Pubkey,
pub token_program: Pubkey,
pub associated_token_program: Pubkey,
pub metadata_program: Pubkey,
pub token_program2022: Pubkey,
pub vault0_mint: Pubkey,
pub vault1_mint: Pubkey,
pub remaining_accounts: Vec<Pubkey>,
}
impl_unified_event!(RaydiumClmmOpenPositionV2Event,);
/// 事件鉴别器常量
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];
pub const CLOSE_POSITION: &[u8] = &[123, 134, 81, 0, 49, 68, 98, 98];
pub const INCREASE_LIQUIDITY_V2: &[u8] = &[133, 29, 89, 223, 69, 238, 176, 10];
pub const DECREASE_LIQUIDITY_V2: &[u8] = &[58, 127, 188, 62, 79, 82, 196, 96];
pub const CREATE_POOL: &[u8] = &[233, 146, 209, 142, 207, 104, 64, 188];
pub const OPEN_POSITION_WITH_TOKEN_22_NFT: &[u8] = &[77, 255, 174, 82, 125, 29, 201, 46];
pub const OPEN_POSITION_V2: &[u8] = &[77, 184, 74, 214, 112, 86, 241, 199];
}
@@ -5,9 +5,17 @@ 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},
common::{
read_i32_le, read_option_bool, read_u128_le, read_u64_le, read_u8_le, EventMetadata,
EventType, ProtocolType,
},
core::traits::{EventParser, GenericEventParseConfig, GenericEventParser, UnifiedEvent},
protocols::raydium_clmm::{discriminators, RaydiumClmmSwapEvent, RaydiumClmmSwapV2Event},
protocols::raydium_clmm::{
discriminators, RaydiumClmmClosePositionEvent, RaydiumClmmCreatePoolEvent,
RaydiumClmmDecreaseLiquidityV2Event, RaydiumClmmIncreaseLiquidityV2Event,
RaydiumClmmOpenPositionV2Event, RaydiumClmmOpenPositionWithToken22NftEvent,
RaydiumClmmSwapEvent, RaydiumClmmSwapV2Event,
},
};
/// Raydium CLMM程序ID
@@ -35,7 +43,7 @@ impl RaydiumClmmEventParser {
inner_instruction_discriminator: "",
instruction_discriminator: discriminators::SWAP,
event_type: EventType::RaydiumClmmSwap,
inner_instruction_parser: Self::parse_trade_inner_instruction,
inner_instruction_parser: Self::empty_parse,
instruction_parser: Self::parse_swap_instruction,
},
GenericEventParseConfig {
@@ -44,9 +52,63 @@ impl RaydiumClmmEventParser {
inner_instruction_discriminator: "",
instruction_discriminator: discriminators::SWAP_V2,
event_type: EventType::RaydiumClmmSwapV2,
inner_instruction_parser: Self::parse_trade_inner_instruction,
inner_instruction_parser: Self::empty_parse,
instruction_parser: Self::parse_swap_v2_instruction,
},
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: Self::empty_parse,
instruction_parser: Self::parse_close_position_instruction,
},
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: Self::empty_parse,
instruction_parser: Self::parse_decrease_liquidity_v2_instruction,
},
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: Self::empty_parse,
instruction_parser: Self::parse_create_pool_instruction,
},
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: Self::empty_parse,
instruction_parser: Self::parse_increase_liquidity_v2_instruction,
},
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: Self::empty_parse,
instruction_parser: Self::parse_open_position_with_token_22_nft_instruction,
},
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: Self::empty_parse,
instruction_parser: Self::parse_open_position_v2_instruction,
},
];
let inner = GenericEventParser::new(vec![RAYDIUM_CLMM_PROGRAM_ID], configs);
@@ -54,21 +116,234 @@ impl RaydiumClmmEventParser {
Self { inner }
}
/// 解析交易事件
fn parse_trade_inner_instruction(
_data: &[u8],
_metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
fn empty_parse(_data: &[u8], _metadata: EventMetadata) -> Option<Box<dyn UnifiedEvent>> {
None
}
/// 解析打开仓位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;
}
let mut metadata = metadata;
metadata.set_id(format!("{}-{}-{}", metadata.signature, accounts[0], accounts[1]));
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;
}
let mut metadata = metadata;
metadata.set_id(format!("{}-{}-{}", metadata.signature, accounts[0], accounts[1]));
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;
}
let mut metadata = metadata;
metadata.set_id(format!("{}-{}-{}", metadata.signature, accounts[0], accounts[1]));
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;
}
let mut metadata = metadata;
metadata.set_id(format!("{}-{}-{}", metadata.signature, accounts[0], accounts[1]));
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;
}
let mut metadata = metadata;
metadata.set_id(format!("{}-{}-{}", metadata.signature, accounts[0], accounts[1]));
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;
}
let mut metadata = metadata;
metadata.set_id(format!("{}-{}-{}", metadata.signature, accounts[0], accounts[1]));
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() < 16 || accounts.len() < 10 {
if data.len() < 33 || accounts.len() < 10 {
return None;
}
@@ -108,7 +383,7 @@ impl RaydiumClmmEventParser {
accounts: &[Pubkey],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if data.len() < 16 || accounts.len() < 13 {
if data.len() < 33 || accounts.len() < 13 {
return None;
}
@@ -27,9 +27,92 @@ pub struct RaydiumCpmmSwapEvent {
impl_unified_event!(RaydiumCpmmSwapEvent,);
/// 存款
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)]
pub struct RaydiumCpmmDepositEvent {
pub metadata: EventMetadata,
pub lp_token_amount: u64,
pub maximum_token0_amount: u64,
pub maximum_token1_amount: u64,
pub owner: Pubkey,
pub authority: Pubkey,
pub pool_state: Pubkey,
pub owner_lp_token: Pubkey,
pub token0_account: Pubkey,
pub token1_account: Pubkey,
pub token0_vault: Pubkey,
pub token1_vault: Pubkey,
pub token_program: Pubkey,
pub token_program2022: Pubkey,
pub vault0_mint: Pubkey,
pub vault1_mint: Pubkey,
pub lp_mint: Pubkey,
}
impl_unified_event!(RaydiumCpmmDepositEvent,);
/// 初始化
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)]
pub struct RaydiumCpmmInitializeEvent {
pub metadata: EventMetadata,
pub init_amount0: u64,
pub init_amount1: u64,
pub open_time: u64,
pub creator: Pubkey,
pub amm_config: Pubkey,
pub authority: Pubkey,
pub pool_state: Pubkey,
pub token0_mint: Pubkey,
pub token1_mint: Pubkey,
pub lp_mint: Pubkey,
pub creator_token0: Pubkey,
pub creator_token1: Pubkey,
pub creator_lp_token: Pubkey,
pub token0_vault: Pubkey,
pub token1_vault: Pubkey,
pub create_pool_fee: Pubkey,
pub observation_state: Pubkey,
pub token_program: Pubkey,
pub token0_program: Pubkey,
pub token1_program: Pubkey,
pub associated_token_program: Pubkey,
pub system_program: Pubkey,
pub rent: Pubkey,
}
impl_unified_event!(RaydiumCpmmInitializeEvent,);
/// 提款
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)]
pub struct RaydiumCpmmWithdrawEvent {
pub metadata: EventMetadata,
pub lp_token_amount: u64,
pub minimum_token0_amount: u64,
pub minimum_token1_amount: u64,
pub owner: Pubkey,
pub authority: Pubkey,
pub pool_state: Pubkey,
pub owner_lp_token: Pubkey,
pub token0_account: Pubkey,
pub token1_account: Pubkey,
pub token0_vault: Pubkey,
pub token1_vault: Pubkey,
pub token_program: Pubkey,
pub token_program2022: Pubkey,
pub vault0_mint: Pubkey,
pub vault1_mint: Pubkey,
pub lp_mint: Pubkey,
pub memo_program: Pubkey,
}
impl_unified_event!(RaydiumCpmmWithdrawEvent,);
/// 事件鉴别器常量
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];
pub const DEPOSIT: &[u8] = &[242, 35, 198, 137, 82, 225, 242, 182];
pub const INITIALIZE: &[u8] = &[175, 175, 109, 31, 13, 152, 155, 237];
pub const WITHDRAW: &[u8] = &[183, 18, 70, 156, 148, 109, 161, 34];
}
@@ -7,7 +7,10 @@ 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},
protocols::raydium_cpmm::{
discriminators, RaydiumCpmmDepositEvent, RaydiumCpmmInitializeEvent, RaydiumCpmmSwapEvent,
RaydiumCpmmWithdrawEvent,
},
};
/// Raydium CPMM程序ID
@@ -35,7 +38,7 @@ impl RaydiumCpmmEventParser {
inner_instruction_discriminator: "",
instruction_discriminator: discriminators::SWAP_BASE_IN,
event_type: EventType::RaydiumCpmmSwapBaseInput,
inner_instruction_parser: Self::parse_trade_inner_instruction,
inner_instruction_parser: Self::empty_parse,
instruction_parser: Self::parse_swap_base_input_instruction,
},
GenericEventParseConfig {
@@ -44,9 +47,36 @@ impl RaydiumCpmmEventParser {
inner_instruction_discriminator: "",
instruction_discriminator: discriminators::SWAP_BASE_OUT,
event_type: EventType::RaydiumCpmmSwapBaseOutput,
inner_instruction_parser: Self::parse_trade_inner_instruction,
inner_instruction_parser: Self::empty_parse,
instruction_parser: Self::parse_swap_base_output_instruction,
},
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: Self::empty_parse,
instruction_parser: Self::parse_deposit_instruction,
},
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: Self::empty_parse,
instruction_parser: Self::parse_initialize_instruction,
},
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: Self::empty_parse,
instruction_parser: Self::parse_withdraw_instruction,
},
];
let inner = GenericEventParser::new(vec![RAYDIUM_CPMM_PROGRAM_ID], configs);
@@ -54,14 +84,114 @@ impl RaydiumCpmmEventParser {
Self { inner }
}
/// 解析交易事件
fn parse_trade_inner_instruction(
_data: &[u8],
_metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
fn empty_parse(_data: &[u8], _metadata: EventMetadata) -> Option<Box<dyn UnifiedEvent>> {
None
}
/// 解析提款指令事件
fn parse_withdraw_instruction(
data: &[u8],
accounts: &[Pubkey],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if data.len() < 24 || accounts.len() < 14 {
return None;
}
let mut metadata = metadata;
metadata.set_id(format!("{}-{}-{}", metadata.signature, accounts[0], accounts[1]));
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;
}
let mut metadata = metadata;
metadata.set_id(format!("{}-{}-{}", metadata.signature, accounts[0], accounts[1]));
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;
}
let mut metadata = metadata;
metadata.set_id(format!("{}-{}-{}", metadata.signature, accounts[0], accounts[1]));
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],