From 8e3d64a09cdf656f711d12980f5b981c1fc993f2 Mon Sep 17 00:00:00 2001 From: ysq Date: Mon, 11 Aug 2025 22:02:09 +0800 Subject: [PATCH] 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 --- Cargo.toml | 2 +- README.md | 103 +++- README_CN.md | 93 +++- examples/parse_tx_events.rs | 3 +- src/main.rs | 83 +++- src/streaming/event_parser/common/types.rs | 90 ++-- src/streaming/event_parser/common/utils.rs | 27 +- src/streaming/event_parser/core/traits.rs | 3 - src/streaming/event_parser/factory.rs | 12 +- src/streaming/event_parser/protocols/mod.rs | 2 + .../event_parser/protocols/pumpfun/events.rs | 70 +++ .../event_parser/protocols/pumpfun/parser.rs | 113 +++-- .../protocols/raydium_amm_v4/events.rs | 163 +++++++ .../protocols/raydium_amm_v4/mod.rs | 5 + .../protocols/raydium_amm_v4/parser.rs | 439 ++++++++++++++++++ .../protocols/raydium_clmm/events.rs | 172 +++++++ .../protocols/raydium_clmm/parser.rs | 297 +++++++++++- .../protocols/raydium_cpmm/events.rs | 83 ++++ .../protocols/raydium_cpmm/parser.rs | 146 +++++- 19 files changed, 1778 insertions(+), 128 deletions(-) create mode 100755 src/streaming/event_parser/protocols/raydium_amm_v4/events.rs create mode 100755 src/streaming/event_parser/protocols/raydium_amm_v4/mod.rs create mode 100755 src/streaming/event_parser/protocols/raydium_amm_v4/parser.rs diff --git a/Cargo.toml b/Cargo.toml index a03c355..4c27725 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "solana-streamer-sdk" -version = "0.2.0" +version = "0.2.1" edition = "2021" authors = ["William ", "sgxiang ", "wei <1415121722@qq.com>"] repository = "https://github.com/0xfnzero/solana-streamer" diff --git a/README.md b/README.md index 4cdf257..74e75ae 100755 --- a/README.md +++ b/README.md @@ -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> { // 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> { 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) { |event: Box| { 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) { 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) { 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) { - **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 diff --git a/README_CN.md b/README_CN.md index fe2ab0d..29464c6 100644 --- a/README_CN.md +++ b/README_CN.md @@ -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> { 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> { 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) { |event: Box| { 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) { 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) { 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) { - **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 # 解析器工厂 diff --git a/examples/parse_tx_events.rs b/examples/parse_tx_events.rs index c1693f6..222d260 100644 --- a/examples/parse_tx_events.rs +++ b/examples/parse_tx_events.rs @@ -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 = Arc::new(MutilEventParser::new(protocols)); let start_time = std::time::Instant::now(); diff --git a/src/main.rs b/src/main.rs index 8daf70f..19821f7 100755 --- a/src/main.rs +++ b/src/main.rs @@ -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> { 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) { |event: Box| { 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) { 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) { 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:?}"); + }, }); } } diff --git a/src/streaming/event_parser/common/types.rs b/src/streaming/event_parser/common/types.rs index 4b72619..c22b16f 100755 --- a/src/streaming/event_parser/common/types.rs +++ b/src/streaming/event_parser/common/types.rs @@ -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, Option) { - 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() { diff --git a/src/streaming/event_parser/common/utils.rs b/src/streaming/event_parser/common/utils.rs index aac5a4f..2d372b2 100755 --- a/src/streaming/event_parser/common/utils.rs +++ b/src/streaming/event_parser/common/utils.rs @@ -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 { Some(u64::from_le_bytes(bytes)) } +pub fn read_i32_le(data: &[u8], offset: usize) -> Option { + 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 { if data.len() < offset + 16 { return None; @@ -73,6 +78,20 @@ pub fn read_u8_le(data: &[u8], offset: usize) -> Option { Some(u8::from_le_bytes(bytes)) } +pub fn read_option_bool(data: &[u8], offset: &mut usize) -> Option> { + 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 { if data.len() < offset + 4 { diff --git a/src/streaming/event_parser/core/traits.rs b/src/streaming/event_parser/core/traits.rs index 7439735..d6c537a 100755 --- a/src/streaming/event_parser/core/traits.rs +++ b/src/streaming/event_parser/core/traits.rs @@ -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); }); diff --git a/src/streaming/event_parser/factory.rs b/src/streaming/event_parser/factory.rs index d997ebd..48de9c5 100755 --- a/src/streaming/event_parser/factory.rs +++ b/src/streaming/event_parser/factory.rs @@ -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>> = LazyLock::new(|| { // 预分配容量,避免动态扩容 - let mut parsers: HashMap> = HashMap::with_capacity(5); + let mut parsers: HashMap> = 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 }); diff --git a/src/streaming/event_parser/protocols/mod.rs b/src/streaming/event_parser/protocols/mod.rs index ece390b..a082b31 100755 --- a/src/streaming/event_parser/protocols/mod.rs +++ b/src/streaming/event_parser/protocols/mod.rs @@ -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; \ No newline at end of file diff --git a/src/streaming/event_parser/protocols/pumpfun/events.rs b/src/streaming/event_parser/protocols/pumpfun/events.rs index ebbaabf..f7f073e 100755 --- a/src/streaming/event_parser/protocols/pumpfun/events.rs +++ b/src/streaming/event_parser/protocols/pumpfun/events.rs @@ -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]; } diff --git a/src/streaming/event_parser/protocols/pumpfun/parser.rs b/src/streaming/event_parser/protocols/pumpfun/parser.rs index 5ebfefb..02ae8cc 100755 --- a/src/streaming/event_parser/protocols/pumpfun/parser.rs +++ b/src/streaming/event_parser/protocols/pumpfun/parser.rs @@ -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> { + if let Ok(event) = borsh::from_slice::(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> { + 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] diff --git a/src/streaming/event_parser/protocols/raydium_amm_v4/events.rs b/src/streaming/event_parser/protocols/raydium_amm_v4/events.rs new file mode 100755 index 0000000..d68f495 --- /dev/null +++ b/src/streaming/event_parser/protocols/raydium_amm_v4/events.rs @@ -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, + 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]; +} diff --git a/src/streaming/event_parser/protocols/raydium_amm_v4/mod.rs b/src/streaming/event_parser/protocols/raydium_amm_v4/mod.rs new file mode 100755 index 0000000..552d4af --- /dev/null +++ b/src/streaming/event_parser/protocols/raydium_amm_v4/mod.rs @@ -0,0 +1,5 @@ +pub mod events; +pub mod parser; + +pub use events::*; +pub use parser::RaydiumAmmV4EventParser; diff --git a/src/streaming/event_parser/protocols/raydium_amm_v4/parser.rs b/src/streaming/event_parser/protocols/raydium_amm_v4/parser.rs new file mode 100755 index 0000000..adbe808 --- /dev/null +++ b/src/streaming/event_parser/protocols/raydium_amm_v4/parser.rs @@ -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> { + None + } + + /// 解析提现指令事件 + fn parse_withdraw_pnl_instruction( + _data: &[u8], + accounts: &[Pubkey], + metadata: EventMetadata, + ) -> Option> { + 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> { + 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> { + 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> { + 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> { + 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> { + 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> { + self.inner.inner_instruction_configs() + } + fn instruction_configs(&self) -> HashMap, Vec> { + self.inner.instruction_configs() + } + fn parse_events_from_inner_instruction( + &self, + inner_instruction: &UiCompiledInstruction, + signature: &str, + slot: u64, + block_time: Option, + program_received_time_ms: i64, + index: String, + ) -> Vec> { + 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, + program_received_time_ms: i64, + index: String, + ) -> Vec> { + 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 { + self.inner.supported_program_ids() + } +} diff --git a/src/streaming/event_parser/protocols/raydium_clmm/events.rs b/src/streaming/event_parser/protocols/raydium_clmm/events.rs index cd0c0b1..6add233 100755 --- a/src/streaming/event_parser/protocols/raydium_clmm/events.rs +++ b/src/streaming/event_parser/protocols/raydium_clmm/events.rs @@ -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, +} +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, + 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, + + 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, + + 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, +} +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]; } diff --git a/src/streaming/event_parser/protocols/raydium_clmm/parser.rs b/src/streaming/event_parser/protocols/raydium_clmm/parser.rs index c73d5d7..e5777ce 100755 --- a/src/streaming/event_parser/protocols/raydium_clmm/parser.rs +++ b/src/streaming/event_parser/protocols/raydium_clmm/parser.rs @@ -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> { + fn empty_parse(_data: &[u8], _metadata: EventMetadata) -> Option> { None } + /// 解析打开仓位V2指令事件 + fn parse_open_position_v2_instruction( + data: &[u8], + accounts: &[Pubkey], + metadata: EventMetadata, + ) -> Option> { + 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> { + 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> { + 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> { + 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> { + 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> { + 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> { - 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> { - if data.len() < 16 || accounts.len() < 13 { + if data.len() < 33 || accounts.len() < 13 { return None; } diff --git a/src/streaming/event_parser/protocols/raydium_cpmm/events.rs b/src/streaming/event_parser/protocols/raydium_cpmm/events.rs index 2c3f770..92cc101 100755 --- a/src/streaming/event_parser/protocols/raydium_cpmm/events.rs +++ b/src/streaming/event_parser/protocols/raydium_cpmm/events.rs @@ -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]; } diff --git a/src/streaming/event_parser/protocols/raydium_cpmm/parser.rs b/src/streaming/event_parser/protocols/raydium_cpmm/parser.rs index 420f020..a543685 100755 --- a/src/streaming/event_parser/protocols/raydium_cpmm/parser.rs +++ b/src/streaming/event_parser/protocols/raydium_cpmm/parser.rs @@ -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> { + fn empty_parse(_data: &[u8], _metadata: EventMetadata) -> Option> { None } + /// 解析提款指令事件 + fn parse_withdraw_instruction( + data: &[u8], + accounts: &[Pubkey], + metadata: EventMetadata, + ) -> Option> { + 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> { + 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> { + 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],