From 92bac16fd45241a21f19a2199622c81f49529dc1 Mon Sep 17 00:00:00 2001 From: 0xfnzero <0xfnzero@users.noreply.github.com> Date: Sat, 16 May 2026 06:45:35 +0800 Subject: [PATCH] Release solana-streamer-sdk v1.4.3 Route gRPC, ShredStream, and account parsing through sol-parser-sdk. Remove local protocol parsing implementations. Depend on sol-parser-sdk v0.4.8 from crates.io. --- Cargo.toml | 8 +- src/streaming/common/event_processor.rs | 43 +- src/streaming/event_parser/common/filter.rs | 447 +++++++++++-- src/streaming/event_parser/common/mod.rs | 2 - src/streaming/event_parser/common/types.rs | 258 +------- src/streaming/event_parser/common/utils.rs | 119 ---- .../event_parser/core/account_event_parser.rs | 202 +----- .../event_parser/core/common_event_parser.rs | 44 -- src/streaming/event_parser/core/dispatcher.rs | 376 ++++------- .../compiled_path/compiled_instruction.rs | 195 ------ .../compiled_path/compiled_transaction.rs | 87 --- .../core/event_parser/compiled_path/mod.rs | 11 - .../grpc_path/grpc_instruction.rs | 171 ----- .../event_parser/grpc_path/grpc_ix_mode.rs | 9 - .../grpc_path/grpc_transaction.rs | 265 -------- .../core/event_parser/grpc_path/mod.rs | 13 - .../event_parser/core/event_parser/helpers.rs | 21 +- .../event_parser/core/event_parser/mod.rs | 112 ++-- src/streaming/event_parser/mod.rs | 5 +- .../event_parser/protocols/bonk/events.rs | 14 - .../event_parser/protocols/bonk/parser.rs | 590 +----------------- .../event_parser/protocols/bonk/types.rs | 103 --- .../protocols/meteora_damm_v2/events.rs | 14 - .../protocols/meteora_damm_v2/parser.rs | 440 +------------ .../event_parser/protocols/pumpfun/events.rs | 180 ------ .../event_parser/protocols/pumpfun/parser.rs | 435 +------------ .../event_parser/protocols/pumpfun/types.rs | 68 -- .../event_parser/protocols/pumpswap/events.rs | 184 ------ .../event_parser/protocols/pumpswap/parser.rs | 354 +---------- .../event_parser/protocols/pumpswap/types.rs | 145 ----- .../protocols/raydium_amm_v4/parser.rs | 321 +--------- .../protocols/raydium_amm_v4/types.rs | 44 -- .../protocols/raydium_clmm/parser.rs | 382 +----------- .../protocols/raydium_clmm/types.rs | 103 --- .../protocols/raydium_cpmm/parser.rs | 246 +------- .../protocols/raydium_cpmm/types.rs | 67 -- .../protocols/sol_parser_forward/mod.rs | 13 +- .../protocols/sol_parser_forward/native.rs | 92 +-- src/streaming/event_parser/protocols/types.rs | 31 +- src/streaming/parser_sdk_bridge/convert.rs | 32 +- src/streaming/parser_sdk_bridge/filter.rs | 18 +- .../parser_sdk_bridge/program_ids.rs | 25 +- src/streaming/shred_stream.rs | 8 +- 43 files changed, 837 insertions(+), 5460 deletions(-) delete mode 100755 src/streaming/event_parser/common/utils.rs delete mode 100644 src/streaming/event_parser/core/event_parser/compiled_path/compiled_instruction.rs delete mode 100644 src/streaming/event_parser/core/event_parser/compiled_path/compiled_transaction.rs delete mode 100644 src/streaming/event_parser/core/event_parser/compiled_path/mod.rs delete mode 100644 src/streaming/event_parser/core/event_parser/grpc_path/grpc_instruction.rs delete mode 100644 src/streaming/event_parser/core/event_parser/grpc_path/grpc_ix_mode.rs delete mode 100644 src/streaming/event_parser/core/event_parser/grpc_path/grpc_transaction.rs delete mode 100644 src/streaming/event_parser/core/event_parser/grpc_path/mod.rs mode change 100755 => 100644 src/streaming/event_parser/protocols/bonk/parser.rs mode change 100755 => 100644 src/streaming/event_parser/protocols/pumpfun/parser.rs mode change 100755 => 100644 src/streaming/event_parser/protocols/pumpswap/parser.rs mode change 100755 => 100644 src/streaming/event_parser/protocols/raydium_amm_v4/parser.rs mode change 100755 => 100644 src/streaming/event_parser/protocols/raydium_clmm/parser.rs mode change 100755 => 100644 src/streaming/event_parser/protocols/raydium_cpmm/parser.rs diff --git a/Cargo.toml b/Cargo.toml index 9e5d404..de61125 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "solana-streamer-sdk" -version = "1.4.2" +version = "1.4.3" edition = "2021" authors = ["William ", "sgxiang ", "wei <1415121722@qq.com>"] repository = "https://github.com/0xfnzero/solana-streamer" @@ -15,14 +15,14 @@ crate-type = ["cdylib", "rlib"] [features] default = ["sdk-parse-borsh"] sdk-parse-borsh = ["sol-parser-sdk/parse-borsh"] -# If both parser backend features are enabled, sol-parser-sdk 0.4.4+ uses zero-copy. +# If both parser backend features are enabled, sol-parser-sdk 0.4.8+ uses zero-copy. sdk-parse-zero-copy = ["sol-parser-sdk/parse-zero-copy"] sdk-perf-stats = ["sol-parser-sdk/perf-stats"] sdk-ultra-perf = ["sol-parser-sdk/ultra-perf"] [dependencies] -# Keep the streamer on the crates.io SDK while allowing local workspace patches by semver. -sol-parser-sdk = { version = "0.4.5", default-features = false } +# Keep the streamer facade in lockstep with the sibling SDK while both crates evolve together. +sol-parser-sdk = { version = "0.4.8", default-features = false } solana-sdk = "3.0.0" solana-client = "3.1.12" solana-transaction-status = "3.1.12" diff --git a/src/streaming/common/event_processor.rs b/src/streaming/common/event_processor.rs index cb692d4..3aa7e40 100644 --- a/src/streaming/common/event_processor.rs +++ b/src/streaming/common/event_processor.rs @@ -1,15 +1,16 @@ use crate::common::AnyResult; use crate::streaming::common::MetricsEventType; use crate::streaming::event_parser::common::filter::{ - build_sdk_parse_event_filter, passes_event_type_filter, EventTypeFilter, + build_sdk_parse_event_filter, build_sdk_shred_parse_event_filter, passes_event_type_filter, + EventTypeFilter, }; use crate::streaming::event_parser::common::high_performance_clock::elapsed_micros_since; use crate::streaming::event_parser::core::common_event_parser::CommonEventParser; -use crate::streaming::event_parser::core::event_parser::EventParser; use crate::streaming::event_parser::{core::traits::DexEvent, Protocol}; use crate::streaming::grpc::{EventPretty, MetricsManager}; use crate::streaming::parser_sdk_bridge::{ - adapt_parser_events_list, parse_account_event_for_streamer, AccountParseResult, + adapt_parser_event, adapt_parser_events_list, parse_account_event_for_streamer, + AccountParseResult, }; use crate::streaming::shred::TransactionWithSlot; use sol_parser_sdk::grpc::parse_subscribe_update_transaction_low_latency; @@ -156,25 +157,29 @@ pub async fn process_shred_transaction( let recv_us = transaction_with_slot.recv_us; let adapter_callback = create_metrics_callback(callback); - // Shred only exposes static account keys and no inner instructions; see - // docs/SHREDSTREAM_LIMITATIONS.md for the expected parser limits. - let accounts = tx.message.static_account_keys(); - - EventParser::parse_instruction_events_from_versioned_transaction( - protocols, - event_type_filter, + let sdk_parse_filter = build_sdk_shred_parse_event_filter(protocols, event_type_filter); + let mut sdk_events = Vec::with_capacity(4); + sol_parser_sdk::shredstream::parse_transaction_dex_events_with_filter( &tx, signature, - Some(slot), - None, // shred has no block_time + slot, + tx_index.unwrap_or(0), recv_us, - accounts, - &[], - bot_wallet, - tx_index, - adapter_callback, - ) - .await?; + sdk_parse_filter.as_ref(), + &mut sdk_events, + ); + + for sdk_event in sdk_events { + if let Some(mut event) = + adapt_parser_event(sdk_event, None, recv_us, protocols, event_type_filter) + { + event.metadata_mut().handle_us = elapsed_micros_since(recv_us); + event = crate::streaming::event_parser::core::event_parser::helpers::process_event( + event, bot_wallet, + ); + adapter_callback(event); + } + } Ok(()) } diff --git a/src/streaming/event_parser/common/filter.rs b/src/streaming/event_parser/common/filter.rs index 3a99d52..ee943f5 100644 --- a/src/streaming/event_parser/common/filter.rs +++ b/src/streaming/event_parser/common/filter.rs @@ -1,7 +1,7 @@ use crate::streaming::event_parser::common::{ types::EventType, ACCOUNT_EVENT_TYPES, BLOCK_EVENT_TYPES, }; -use crate::streaming::event_parser::DexEvent; +use crate::streaming::event_parser::{DexEvent, Protocol}; use sol_parser_sdk::grpc::types::EventType as SdkGrpcEventType; use sol_parser_sdk::grpc::types::EventTypeFilter as SdkGrpcEventTypeFilter; @@ -95,27 +95,6 @@ pub(crate) fn passes_event_type_filter(filter: Option<&EventTypeFilter>, ev: &De } } -/// Whether the local pass should parse ComputeBudget instructions. -#[inline] -pub(crate) fn filter_includes_compute_budget_types(filter: Option<&EventTypeFilter>) -> bool { - match filter { - None => true, - Some(f) => { - let limit_excluded = f.exclude.contains(&EventType::SetComputeUnitLimit); - let price_excluded = f.exclude.contains(&EventType::SetComputeUnitPrice); - if limit_excluded && price_excluded { - return false; - } - if f.include.is_empty() { - return true; - } - f.include.iter().any(|t| { - matches!(t, EventType::SetComputeUnitLimit | EventType::SetComputeUnitPrice) - }) - } - } -} - /// Map the streamer filter to the SDK gRPC event-type filter used by /// [`sol_parser_sdk::grpc::parse_subscribe_update_transaction_low_latency`]. /// @@ -129,9 +108,9 @@ pub(crate) fn build_sdk_parse_event_filter( let f = filter?; if !f.exclude.is_empty() && f.include.is_empty() { - let mut raw: Vec = Vec::new(); + let mut raw: Vec = Vec::with_capacity(f.exclude.len()); for et in &f.exclude { - raw.extend(streamer_event_to_sdk_grpc_types(et)); + push_streamer_event_sdk_grpc_types(et, &mut raw); } dedup_sdk_grpc_event_types(&mut raw); return (!raw.is_empty()).then(|| SdkGrpcEventTypeFilter::exclude_types(raw)); @@ -140,18 +119,44 @@ pub(crate) fn build_sdk_parse_event_filter( if f.include.is_empty() { return None; } - let mut raw: Vec = Vec::new(); + let mut raw: Vec = Vec::with_capacity(f.include.len()); for et in &f.include { - let mapped = streamer_event_to_sdk_grpc_types(et); - if mapped.is_empty() { + if !push_streamer_event_sdk_grpc_types(et, &mut raw) { return None; } - raw.extend(mapped); } dedup_sdk_grpc_event_types(&mut raw); Some(SdkGrpcEventTypeFilter::include_only(raw)) } +/// Build the SDK ShredStream hot-path filter. Unlike Yellowstone, ShredStream +/// subscription itself has no program filter, so protocol narrowing must be +/// pushed into the SDK parser as an event-type include list. +pub(crate) fn build_sdk_shred_parse_event_filter( + protocols: &[Protocol], + filter: Option<&EventTypeFilter>, +) -> Option { + if protocols.is_empty() { + return build_sdk_parse_event_filter(filter); + } + + if let Some(f) = filter.filter(|f| !f.include.is_empty()) { + if let Some(exact) = build_sdk_parse_event_filter(Some(f)) { + return Some(exact); + } + if !f.include_transaction_event() { + return Some(SdkGrpcEventTypeFilter::include_only(Vec::new())); + } + } + + let mut raw = Vec::with_capacity(protocols.len() * 8); + for protocol in protocols { + push_protocol_sdk_grpc_event_types(protocol, &mut raw); + } + dedup_sdk_grpc_event_types(&mut raw); + (!raw.is_empty()).then(|| SdkGrpcEventTypeFilter::include_only(raw)) +} + fn dedup_sdk_grpc_event_types(v: &mut Vec) { let mut i = 0; while i < v.len() { @@ -163,51 +168,199 @@ fn dedup_sdk_grpc_event_types(v: &mut Vec) { } } -fn streamer_event_to_sdk_grpc_types(t: &EventType) -> Vec { +fn push_streamer_event_sdk_grpc_types(t: &EventType, out: &mut Vec) -> bool { use EventType as St; use SdkGrpcEventType as Sdk; match t { - St::BlockMeta => vec![Sdk::BlockMeta], - St::PumpFunCreateToken => vec![Sdk::PumpFunCreate], - St::PumpFunCreateV2Token => vec![Sdk::PumpFunCreateV2], - St::PumpFunBuy => vec![Sdk::PumpFunBuy, Sdk::PumpFunBuyExactSolIn], - St::PumpFunBuyExactSolIn => vec![Sdk::PumpFunBuyExactSolIn], - St::PumpFunSell => vec![Sdk::PumpFunSell], - St::PumpFunMigrate => vec![Sdk::PumpFunMigrate], - St::PumpFeesCreateFeeSharingConfig => vec![Sdk::PumpFeesCreateFeeSharingConfig], - St::PumpFeesInitializeFeeConfig => vec![Sdk::PumpFeesInitializeFeeConfig], - St::PumpFeesResetFeeSharingConfig => vec![Sdk::PumpFeesResetFeeSharingConfig], - St::PumpFeesRevokeFeeSharingAuthority => vec![Sdk::PumpFeesRevokeFeeSharingAuthority], - St::PumpFeesTransferFeeSharingAuthority => vec![Sdk::PumpFeesTransferFeeSharingAuthority], - St::PumpFeesUpdateAdmin => vec![Sdk::PumpFeesUpdateAdmin], - St::PumpFeesUpdateFeeConfig => vec![Sdk::PumpFeesUpdateFeeConfig], - St::PumpFeesUpdateFeeShares => vec![Sdk::PumpFeesUpdateFeeShares], - St::PumpFeesUpsertFeeTiers => vec![Sdk::PumpFeesUpsertFeeTiers], - St::PumpFunMigrateBondingCurveCreator => vec![Sdk::PumpFunMigrateBondingCurveCreator], - St::PumpSwapBuy => vec![Sdk::PumpSwapBuy], - St::PumpSwapSell => vec![Sdk::PumpSwapSell], - St::PumpSwapCreatePool => vec![Sdk::PumpSwapCreatePool], - St::PumpSwapDeposit => vec![Sdk::PumpSwapLiquidityAdded], - St::PumpSwapWithdraw => vec![Sdk::PumpSwapLiquidityRemoved], + St::BlockMeta => out.push(Sdk::BlockMeta), + St::PumpFunCreateToken => out.push(Sdk::PumpFunCreate), + St::PumpFunCreateV2Token => out.push(Sdk::PumpFunCreateV2), + St::PumpFunBuy => { + out.push(Sdk::PumpFunBuy); + out.push(Sdk::PumpFunBuyExactSolIn); + } + St::PumpFunBuyExactSolIn => out.push(Sdk::PumpFunBuyExactSolIn), + St::PumpFunSell => out.push(Sdk::PumpFunSell), + St::PumpFunMigrate => out.push(Sdk::PumpFunMigrate), + St::PumpFeesCreateFeeSharingConfig => out.push(Sdk::PumpFeesCreateFeeSharingConfig), + St::PumpFeesInitializeFeeConfig => out.push(Sdk::PumpFeesInitializeFeeConfig), + St::PumpFeesResetFeeSharingConfig => out.push(Sdk::PumpFeesResetFeeSharingConfig), + St::PumpFeesRevokeFeeSharingAuthority => out.push(Sdk::PumpFeesRevokeFeeSharingAuthority), + St::PumpFeesTransferFeeSharingAuthority => { + out.push(Sdk::PumpFeesTransferFeeSharingAuthority) + } + St::PumpFeesUpdateAdmin => out.push(Sdk::PumpFeesUpdateAdmin), + St::PumpFeesUpdateFeeConfig => out.push(Sdk::PumpFeesUpdateFeeConfig), + St::PumpFeesUpdateFeeShares => out.push(Sdk::PumpFeesUpdateFeeShares), + St::PumpFeesUpsertFeeTiers => out.push(Sdk::PumpFeesUpsertFeeTiers), + St::PumpFunMigrateBondingCurveCreator => out.push(Sdk::PumpFunMigrateBondingCurveCreator), + St::PumpSwapBuy => out.push(Sdk::PumpSwapBuy), + St::PumpSwapSell => out.push(Sdk::PumpSwapSell), + St::PumpSwapCreatePool => out.push(Sdk::PumpSwapCreatePool), + St::PumpSwapDeposit => out.push(Sdk::PumpSwapLiquidityAdded), + St::PumpSwapWithdraw => out.push(Sdk::PumpSwapLiquidityRemoved), St::BonkBuyExactIn | St::BonkBuyExactOut | St::BonkSellExactIn | St::BonkSellExactOut => { - vec![Sdk::BonkTrade] + out.push(Sdk::BonkTrade) } St::BonkInitialize | St::BonkInitializeV2 | St::BonkInitializeWithToken2022 => { - vec![Sdk::BonkPoolCreate] + out.push(Sdk::BonkPoolCreate) } - St::BonkMigrateToAmm => vec![Sdk::BonkMigrateAmm], - St::MeteoraDammV2Swap | St::MeteoraDammV2Swap2 => vec![Sdk::MeteoraDammV2Swap], - St::MeteoraDammV2AddLiquidity => vec![Sdk::MeteoraDammV2AddLiquidity], - St::MeteoraDammV2RemoveLiquidity => vec![Sdk::MeteoraDammV2RemoveLiquidity], - St::MeteoraDammV2CreatePosition => vec![Sdk::MeteoraDammV2CreatePosition], - St::MeteoraDammV2ClosePosition => vec![Sdk::MeteoraDammV2ClosePosition], - St::TokenAccount => vec![Sdk::TokenAccount], - St::TokenInfo => vec![Sdk::TokenAccount], - St::NonceAccount => vec![Sdk::NonceAccount], - St::AccountPumpFunGlobal => vec![Sdk::AccountPumpFunGlobal], - St::AccountPumpSwapGlobalConfig => vec![Sdk::AccountPumpSwapGlobalConfig], - St::AccountPumpSwapPool => vec![Sdk::AccountPumpSwapPool], - _ => vec![], + St::BonkMigrateToAmm => out.push(Sdk::BonkMigrateAmm), + St::RaydiumCpmmSwapBaseInput | St::RaydiumCpmmSwapBaseOutput => { + out.push(Sdk::RaydiumCpmmSwap) + } + St::RaydiumCpmmDeposit => out.push(Sdk::RaydiumCpmmDeposit), + St::RaydiumCpmmInitialize => out.push(Sdk::RaydiumCpmmInitialize), + St::RaydiumCpmmWithdraw => out.push(Sdk::RaydiumCpmmWithdraw), + St::RaydiumClmmSwap | St::RaydiumClmmSwapV2 => out.push(Sdk::RaydiumClmmSwap), + St::RaydiumClmmClosePosition => out.push(Sdk::RaydiumClmmClosePosition), + St::RaydiumClmmIncreaseLiquidityV2 => out.push(Sdk::RaydiumClmmIncreaseLiquidity), + St::RaydiumClmmDecreaseLiquidityV2 => out.push(Sdk::RaydiumClmmDecreaseLiquidity), + St::RaydiumClmmCreatePool => out.push(Sdk::RaydiumClmmCreatePool), + St::RaydiumClmmOpenPositionWithToken22Nft => { + out.push(Sdk::RaydiumClmmOpenPositionWithTokenExtNft) + } + St::RaydiumClmmOpenPositionV2 => out.push(Sdk::RaydiumClmmOpenPosition), + St::RaydiumClmmCollectFee => out.push(Sdk::RaydiumClmmCollectFee), + St::RaydiumAmmV4SwapBaseIn | St::RaydiumAmmV4SwapBaseOut => out.push(Sdk::RaydiumAmmV4Swap), + St::RaydiumAmmV4Deposit => out.push(Sdk::RaydiumAmmV4Deposit), + St::RaydiumAmmV4Initialize2 => out.push(Sdk::RaydiumAmmV4Initialize2), + St::RaydiumAmmV4Withdraw => out.push(Sdk::RaydiumAmmV4Withdraw), + St::RaydiumAmmV4WithdrawPnl => out.push(Sdk::RaydiumAmmV4WithdrawPnl), + St::OrcaWhirlpoolSwap => out.push(Sdk::OrcaWhirlpoolSwap), + St::OrcaWhirlpoolLiquidityIncreased => out.push(Sdk::OrcaWhirlpoolLiquidityIncreased), + St::OrcaWhirlpoolLiquidityDecreased => out.push(Sdk::OrcaWhirlpoolLiquidityDecreased), + St::OrcaWhirlpoolPoolInitialized => out.push(Sdk::OrcaWhirlpoolPoolInitialized), + St::MeteoraPoolsSwap => out.push(Sdk::MeteoraPoolsSwap), + St::MeteoraPoolsAddLiquidity => out.push(Sdk::MeteoraPoolsAddLiquidity), + St::MeteoraPoolsRemoveLiquidity => out.push(Sdk::MeteoraPoolsRemoveLiquidity), + St::MeteoraPoolsBootstrapLiquidity => out.push(Sdk::MeteoraPoolsBootstrapLiquidity), + St::MeteoraPoolsPoolCreated => out.push(Sdk::MeteoraPoolsPoolCreated), + St::MeteoraPoolsSetPoolFees => out.push(Sdk::MeteoraPoolsSetPoolFees), + St::MeteoraDammV2Swap | St::MeteoraDammV2Swap2 => out.push(Sdk::MeteoraDammV2Swap), + St::MeteoraDammV2AddLiquidity => out.push(Sdk::MeteoraDammV2AddLiquidity), + St::MeteoraDammV2RemoveLiquidity => out.push(Sdk::MeteoraDammV2RemoveLiquidity), + St::MeteoraDammV2CreatePosition => out.push(Sdk::MeteoraDammV2CreatePosition), + St::MeteoraDammV2ClosePosition => out.push(Sdk::MeteoraDammV2ClosePosition), + St::MeteoraDlmmSwap => out.push(Sdk::MeteoraDlmmSwap), + St::MeteoraDlmmAddLiquidity => out.push(Sdk::MeteoraDlmmAddLiquidity), + St::MeteoraDlmmRemoveLiquidity => out.push(Sdk::MeteoraDlmmRemoveLiquidity), + St::MeteoraDlmmInitializePool => out.push(Sdk::MeteoraDlmmInitializePool), + St::MeteoraDlmmInitializeBinArray => out.push(Sdk::MeteoraDlmmInitializeBinArray), + St::MeteoraDlmmCreatePosition => out.push(Sdk::MeteoraDlmmCreatePosition), + St::MeteoraDlmmClosePosition => out.push(Sdk::MeteoraDlmmClosePosition), + St::MeteoraDlmmClaimFee => out.push(Sdk::MeteoraDlmmClaimFee), + St::TokenAccount | St::TokenInfo => out.push(Sdk::TokenAccount), + St::NonceAccount => out.push(Sdk::NonceAccount), + St::AccountPumpFunGlobal => out.push(Sdk::AccountPumpFunGlobal), + St::AccountPumpSwapGlobalConfig => out.push(Sdk::AccountPumpSwapGlobalConfig), + St::AccountPumpSwapPool => out.push(Sdk::AccountPumpSwapPool), + _ => return false, + } + true +} + +fn push_protocol_sdk_grpc_event_types(protocol: &Protocol, out: &mut Vec) { + use Protocol as StProtocol; + use SdkGrpcEventType as Sdk; + + match protocol { + StProtocol::PumpFun => out.extend_from_slice(&[ + Sdk::PumpFunTrade, + Sdk::PumpFunBuy, + Sdk::PumpFunSell, + Sdk::PumpFunBuyExactSolIn, + Sdk::PumpFunCreate, + Sdk::PumpFunCreateV2, + Sdk::PumpFunMigrate, + Sdk::PumpFunMigrateBondingCurveCreator, + // Historical compatibility: streamer PumpFun also included PumpFees. + Sdk::PumpFeesCreateFeeSharingConfig, + Sdk::PumpFeesInitializeFeeConfig, + Sdk::PumpFeesResetFeeSharingConfig, + Sdk::PumpFeesRevokeFeeSharingAuthority, + Sdk::PumpFeesTransferFeeSharingAuthority, + Sdk::PumpFeesUpdateAdmin, + Sdk::PumpFeesUpdateFeeConfig, + Sdk::PumpFeesUpdateFeeShares, + Sdk::PumpFeesUpsertFeeTiers, + ]), + StProtocol::PumpFees => out.extend_from_slice(&[ + Sdk::PumpFeesCreateFeeSharingConfig, + Sdk::PumpFeesInitializeFeeConfig, + Sdk::PumpFeesResetFeeSharingConfig, + Sdk::PumpFeesRevokeFeeSharingAuthority, + Sdk::PumpFeesTransferFeeSharingAuthority, + Sdk::PumpFeesUpdateAdmin, + Sdk::PumpFeesUpdateFeeConfig, + Sdk::PumpFeesUpdateFeeShares, + Sdk::PumpFeesUpsertFeeTiers, + ]), + StProtocol::PumpSwap => out.extend_from_slice(&[ + Sdk::PumpSwapTrade, + Sdk::PumpSwapBuy, + Sdk::PumpSwapSell, + Sdk::PumpSwapCreatePool, + Sdk::PumpSwapLiquidityAdded, + Sdk::PumpSwapLiquidityRemoved, + ]), + StProtocol::Bonk | StProtocol::RaydiumLaunchpad => { + out.extend_from_slice(&[Sdk::BonkTrade, Sdk::BonkPoolCreate, Sdk::BonkMigrateAmm]) + } + StProtocol::RaydiumCpmm => out.extend_from_slice(&[ + Sdk::RaydiumCpmmSwap, + Sdk::RaydiumCpmmDeposit, + Sdk::RaydiumCpmmWithdraw, + Sdk::RaydiumCpmmInitialize, + ]), + StProtocol::RaydiumClmm => out.extend_from_slice(&[ + Sdk::RaydiumClmmSwap, + Sdk::RaydiumClmmCreatePool, + Sdk::RaydiumClmmOpenPosition, + Sdk::RaydiumClmmClosePosition, + Sdk::RaydiumClmmIncreaseLiquidity, + Sdk::RaydiumClmmDecreaseLiquidity, + Sdk::RaydiumClmmOpenPositionWithTokenExtNft, + Sdk::RaydiumClmmCollectFee, + ]), + StProtocol::RaydiumAmmV4 => out.extend_from_slice(&[ + Sdk::RaydiumAmmV4Swap, + Sdk::RaydiumAmmV4Deposit, + Sdk::RaydiumAmmV4Withdraw, + Sdk::RaydiumAmmV4Initialize2, + Sdk::RaydiumAmmV4WithdrawPnl, + ]), + StProtocol::MeteoraDammV2 => out.extend_from_slice(&[ + Sdk::MeteoraDammV2Swap, + Sdk::MeteoraDammV2AddLiquidity, + Sdk::MeteoraDammV2RemoveLiquidity, + Sdk::MeteoraDammV2CreatePosition, + Sdk::MeteoraDammV2ClosePosition, + ]), + StProtocol::OrcaWhirlpool => out.extend_from_slice(&[ + Sdk::OrcaWhirlpoolSwap, + Sdk::OrcaWhirlpoolLiquidityIncreased, + Sdk::OrcaWhirlpoolLiquidityDecreased, + Sdk::OrcaWhirlpoolPoolInitialized, + ]), + StProtocol::MeteoraPools => out.extend_from_slice(&[ + Sdk::MeteoraPoolsSwap, + Sdk::MeteoraPoolsAddLiquidity, + Sdk::MeteoraPoolsRemoveLiquidity, + Sdk::MeteoraPoolsBootstrapLiquidity, + Sdk::MeteoraPoolsPoolCreated, + Sdk::MeteoraPoolsSetPoolFees, + ]), + StProtocol::MeteoraDlmm => out.extend_from_slice(&[ + Sdk::MeteoraDlmmSwap, + Sdk::MeteoraDlmmAddLiquidity, + Sdk::MeteoraDlmmRemoveLiquidity, + Sdk::MeteoraDlmmInitializePool, + Sdk::MeteoraDlmmInitializeBinArray, + Sdk::MeteoraDlmmCreatePosition, + Sdk::MeteoraDlmmClosePosition, + Sdk::MeteoraDlmmClaimFee, + ]), } } @@ -218,6 +371,10 @@ fn event_type_matches(filter_type: &EventType, event_type: &EventType) -> bool { (filter_type, event_type), (EventType::PumpFunBuy, EventType::PumpFunBuyExactSolIn) | (EventType::TokenAccount, EventType::TokenInfo) + | (EventType::RaydiumClmmSwap, EventType::RaydiumClmmSwapV2) + | (EventType::RaydiumClmmSwapV2, EventType::RaydiumClmmSwap) + | (EventType::MeteoraDammV2Swap, EventType::MeteoraDammV2Swap2) + | (EventType::MeteoraDammV2Swap2, EventType::MeteoraDammV2Swap) ) } @@ -372,6 +529,17 @@ mod tests { assert!(f.passes_event_type(&EventType::TokenInfo)); } + #[test] + fn sdk_generic_swap_filters_match_streamer_specific_aliases() { + let f = + EventTypeFilter { include: vec![EventType::RaydiumClmmSwapV2], ..Default::default() }; + assert!(f.passes_event_type(&EventType::RaydiumClmmSwap)); + + let f = + EventTypeFilter { include: vec![EventType::MeteoraDammV2Swap2], ..Default::default() }; + assert!(f.passes_event_type(&EventType::MeteoraDammV2Swap)); + } + #[test] fn build_sdk_filter_pumpfun_exact_sol_in_only() { let f = EventTypeFilter { @@ -392,9 +560,150 @@ mod tests { } #[test] - fn build_sdk_filter_none_when_orca_in_mix() { + fn build_sdk_filter_maps_all_public_sdk_filter_events() { + let sdk_filter_backed = [ + EventType::BlockMeta, + EventType::PumpFunCreateToken, + EventType::PumpFunCreateV2Token, + EventType::PumpFunBuy, + EventType::PumpFunBuyExactSolIn, + EventType::PumpFunSell, + EventType::PumpFunMigrate, + EventType::PumpFunMigrateBondingCurveCreator, + EventType::PumpFeesCreateFeeSharingConfig, + EventType::PumpFeesInitializeFeeConfig, + EventType::PumpFeesResetFeeSharingConfig, + EventType::PumpFeesRevokeFeeSharingAuthority, + EventType::PumpFeesTransferFeeSharingAuthority, + EventType::PumpFeesUpdateAdmin, + EventType::PumpFeesUpdateFeeConfig, + EventType::PumpFeesUpdateFeeShares, + EventType::PumpFeesUpsertFeeTiers, + EventType::PumpSwapBuy, + EventType::PumpSwapSell, + EventType::PumpSwapCreatePool, + EventType::PumpSwapDeposit, + EventType::PumpSwapWithdraw, + EventType::BonkBuyExactIn, + EventType::BonkBuyExactOut, + EventType::BonkSellExactIn, + EventType::BonkSellExactOut, + EventType::BonkInitialize, + EventType::BonkInitializeV2, + EventType::BonkInitializeWithToken2022, + EventType::BonkMigrateToAmm, + EventType::RaydiumCpmmSwapBaseInput, + EventType::RaydiumCpmmSwapBaseOutput, + EventType::RaydiumCpmmDeposit, + EventType::RaydiumCpmmInitialize, + EventType::RaydiumCpmmWithdraw, + EventType::RaydiumClmmSwap, + EventType::RaydiumClmmSwapV2, + EventType::RaydiumClmmClosePosition, + EventType::RaydiumClmmIncreaseLiquidityV2, + EventType::RaydiumClmmDecreaseLiquidityV2, + EventType::RaydiumClmmCreatePool, + EventType::RaydiumClmmOpenPositionWithToken22Nft, + EventType::RaydiumClmmOpenPositionV2, + EventType::RaydiumClmmCollectFee, + EventType::RaydiumAmmV4SwapBaseIn, + EventType::RaydiumAmmV4SwapBaseOut, + EventType::RaydiumAmmV4Deposit, + EventType::RaydiumAmmV4Initialize2, + EventType::RaydiumAmmV4Withdraw, + EventType::RaydiumAmmV4WithdrawPnl, + EventType::OrcaWhirlpoolSwap, + EventType::OrcaWhirlpoolLiquidityIncreased, + EventType::OrcaWhirlpoolLiquidityDecreased, + EventType::OrcaWhirlpoolPoolInitialized, + EventType::MeteoraPoolsSwap, + EventType::MeteoraPoolsAddLiquidity, + EventType::MeteoraPoolsRemoveLiquidity, + EventType::MeteoraPoolsBootstrapLiquidity, + EventType::MeteoraPoolsPoolCreated, + EventType::MeteoraPoolsSetPoolFees, + EventType::MeteoraDammV2Swap, + EventType::MeteoraDammV2Swap2, + EventType::MeteoraDammV2AddLiquidity, + EventType::MeteoraDammV2RemoveLiquidity, + EventType::MeteoraDammV2CreatePosition, + EventType::MeteoraDammV2ClosePosition, + EventType::MeteoraDlmmSwap, + EventType::MeteoraDlmmAddLiquidity, + EventType::MeteoraDlmmRemoveLiquidity, + EventType::MeteoraDlmmInitializePool, + EventType::MeteoraDlmmInitializeBinArray, + EventType::MeteoraDlmmCreatePosition, + EventType::MeteoraDlmmClosePosition, + EventType::MeteoraDlmmClaimFee, + EventType::TokenAccount, + EventType::TokenInfo, + EventType::NonceAccount, + EventType::AccountPumpFunGlobal, + EventType::AccountPumpSwapGlobalConfig, + EventType::AccountPumpSwapPool, + ]; + + for event_type in sdk_filter_backed { + let f = EventTypeFilter { include: vec![event_type.clone()], ..Default::default() }; + assert!( + build_sdk_parse_event_filter(Some(&f)).is_some(), + "{event_type:?} should map to an SDK parse filter" + ); + } + } + + #[test] + fn build_sdk_filter_none_when_public_sdk_filter_enum_cannot_express_requested_type() { let f = EventTypeFilter { - include: vec![EventType::PumpFunBuy, EventType::OrcaWhirlpoolSwap], + include: vec![EventType::PumpFunBuy, EventType::MeteoraDammV2InitializePool], + ..Default::default() + }; + assert!(build_sdk_parse_event_filter(Some(&f)).is_none()); + } + + #[test] + fn build_sdk_shred_filter_falls_back_to_protocol_group_for_sdk_enum_gap() { + let f = EventTypeFilter { + include: vec![EventType::MeteoraDammV2InitializePool], + ..Default::default() + }; + let sdk_f = + build_sdk_shred_parse_event_filter(&[Protocol::MeteoraDammV2], Some(&f)).unwrap(); + + assert!(sdk_f.should_include(SdkGrpcEventType::MeteoraDammV2Swap)); + assert!(!sdk_f.should_include(SdkGrpcEventType::PumpFunBuy)); + } + + #[test] + fn build_sdk_shred_filter_skips_transaction_parse_for_account_only_gap() { + let f = EventTypeFilter { + include: vec![EventType::AccountRaydiumCpmmPoolState], + ..Default::default() + }; + let sdk_f = build_sdk_shred_parse_event_filter(&[Protocol::RaydiumCpmm], Some(&f)).unwrap(); + + assert!(!sdk_f.should_include(SdkGrpcEventType::RaydiumCpmmSwap)); + assert!(!sdk_f.should_include(SdkGrpcEventType::PumpFunBuy)); + } + + #[test] + fn build_sdk_filter_exclude_only_orca_maps_upstream() { + let f = EventTypeFilter { + include: vec![], + exclude: vec![EventType::OrcaWhirlpoolSwap], + ..Default::default() + }; + let sdk_f = build_sdk_parse_event_filter(Some(&f)).expect("mapped"); + assert!(!sdk_f.should_include(SdkGrpcEventType::OrcaWhirlpoolSwap)); + assert!(sdk_f.should_include(SdkGrpcEventType::PumpFunBuy)); + } + + #[test] + fn build_sdk_filter_exclude_only_none_when_only_sdk_filter_enum_gap() { + let f = EventTypeFilter { + include: vec![], + exclude: vec![EventType::MeteoraDammV2InitializePool], ..Default::default() }; assert!(build_sdk_parse_event_filter(Some(&f)).is_none()); @@ -404,7 +713,7 @@ mod tests { fn build_sdk_filter_exclude_only_none_when_only_unmapped_types() { let f = EventTypeFilter { include: vec![], - exclude: vec![EventType::OrcaWhirlpoolSwap], + exclude: vec![EventType::SetComputeUnitLimit], ..Default::default() }; assert!(build_sdk_parse_event_filter(Some(&f)).is_none()); diff --git a/src/streaming/event_parser/common/mod.rs b/src/streaming/event_parser/common/mod.rs index 3794626..f1fe329 100755 --- a/src/streaming/event_parser/common/mod.rs +++ b/src/streaming/event_parser/common/mod.rs @@ -1,6 +1,4 @@ pub mod filter; pub mod high_performance_clock; pub mod types; -pub mod utils; pub use types::*; -pub use utils::*; diff --git a/src/streaming/event_parser/common/types.rs b/src/streaming/event_parser/common/types.rs index 04f78a5..9cf3b01 100755 --- a/src/streaming/event_parser/common/types.rs +++ b/src/streaming/event_parser/common/types.rs @@ -4,8 +4,6 @@ use serde::{Deserialize, Serialize}; use solana_sdk::{pubkey::Pubkey, signature::Signature}; use std::{borrow::Cow, fmt, sync::Arc}; -use crate::streaming::event_parser::DexEvent; - // Object pool size configuration const EVENT_METADATA_POOL_SIZE: usize = 1000; @@ -55,6 +53,7 @@ pub enum ProtocolType { MeteoraPools, MeteoraDlmm, Common, + PumpFees, } /// Event type enumeration @@ -294,258 +293,3 @@ impl EventMetadata { self.swap_data = Some(swap_data); } } - -static SOL_MINT: std::sync::LazyLock = std::sync::LazyLock::new(spl_token::native_mint::id); -static SYSTEM_PROGRAMS: std::sync::LazyLock<[Pubkey; 3]> = std::sync::LazyLock::new(|| { - [spl_token::id(), spl_token_2022::id(), solana_sdk::pubkey!("11111111111111111111111111111111")] -}); - -/// Trait abstracting over different inner-instruction types for swap data extraction -pub trait InnerInstructionLike { - fn program_id_index(&self) -> usize; - fn accounts(&self) -> &[u8]; - fn data(&self) -> &[u8]; -} - -/// Adapter for standard Solana compiled instructions -impl InnerInstructionLike for solana_sdk::message::compiled_instruction::CompiledInstruction { - fn program_id_index(&self) -> usize { - self.program_id_index as usize - } - fn accounts(&self) -> &[u8] { - &self.accounts - } - fn data(&self) -> &[u8] { - &self.data - } -} - -/// Adapter for gRPC inner instructions (yellowstone) -impl InnerInstructionLike for yellowstone_grpc_proto::prelude::InnerInstruction { - fn program_id_index(&self) -> usize { - self.program_id_index as usize - } - fn accounts(&self) -> &[u8] { - &self.accounts - } - fn data(&self) -> &[u8] { - &self.data - } -} - -/// Extract event context (mint/token account/vault info) from a DexEvent -fn extract_swap_context( - event: &DexEvent, -) -> ( - SwapData, - Option, - Option, - Option, - Option, - Option, - Option, -) { - let mut swap_data = SwapData::default(); - let mut from_mint: Option = None; - let mut to_mint: Option = None; - let mut user_from_token: Option = None; - let mut user_to_token: Option = None; - let mut from_vault: Option = None; - let mut to_vault: Option = None; - - match event { - DexEvent::BonkTradeEvent(e) => { - from_mint = Some(e.base_token_mint); - to_mint = Some(e.quote_token_mint); - user_from_token = Some(e.user_base_token); - user_to_token = Some(e.user_quote_token); - from_vault = Some(e.base_vault); - to_vault = Some(e.quote_vault); - } - DexEvent::PumpFunTradeEvent(e) => { - swap_data.from_mint = if e.is_buy { *SOL_MINT } else { e.mint }; - swap_data.to_mint = if e.is_buy { e.mint } else { *SOL_MINT }; - } - DexEvent::PumpSwapBuyEvent(e) => { - swap_data.from_mint = e.quote_mint; - swap_data.to_mint = e.base_mint; - } - DexEvent::PumpSwapSellEvent(e) => { - swap_data.from_mint = e.base_mint; - swap_data.to_mint = e.quote_mint; - } - DexEvent::RaydiumCpmmSwapEvent(e) => { - from_mint = Some(e.input_token_mint); - to_mint = Some(e.output_token_mint); - user_from_token = Some(e.input_token_account); - user_to_token = Some(e.output_token_account); - from_vault = Some(e.input_vault); - to_vault = Some(e.output_vault); - } - DexEvent::RaydiumClmmSwapEvent(e) => { - swap_data.description = - Some("Unable to get from_mint and to_mint from RaydiumClmmSwapEvent".into()); - user_from_token = Some(e.input_token_account); - user_to_token = Some(e.output_token_account); - from_vault = Some(e.input_vault); - to_vault = Some(e.output_vault); - } - DexEvent::RaydiumClmmSwapV2Event(e) => { - from_mint = Some(e.input_vault_mint); - to_mint = Some(e.output_vault_mint); - user_from_token = Some(e.input_token_account); - user_to_token = Some(e.output_token_account); - from_vault = Some(e.input_vault); - to_vault = Some(e.output_vault); - } - DexEvent::RaydiumAmmV4SwapEvent(e) => { - swap_data.description = - Some("Unable to get from_mint and to_mint from RaydiumAmmV4SwapEvent".into()); - 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); - } - _ => {} - } - - (swap_data, from_mint, to_mint, user_from_token, user_to_token, from_vault, to_vault) -} - -/// Generic swap data extraction that works with any instruction type implementing InnerInstructionLike -fn extract_swap_data_from_instructions( - event: &DexEvent, - instructions: impl Iterator, - current_index: i32, - accounts: &[Pubkey], -) -> Option { - let (mut swap_data, fm, tm, uft, utt, fv, tv) = extract_swap_context(event); - - let user_to_token = utt.unwrap_or_default(); - let user_from_token = uft.unwrap_or_default(); - let to_vault = tv.unwrap_or_default(); - let from_vault = fv.unwrap_or_default(); - let to_mint = tm.unwrap_or_default(); - let from_mint = fm.unwrap_or_default(); - - let skip_count = (current_index + 1).max(0) as usize; - for instruction in instructions.skip(skip_count) { - let program_id_index = instruction.program_id_index(); - let program_id = match accounts.get(program_id_index) { - Some(&pid) => pid, - None => break, - }; - if !SYSTEM_PROGRAMS.contains(&program_id) { - break; - } - let data = instruction.data(); - let accs = instruction.accounts(); - - if data.len() < 8 { - continue; - } - - let get_pubkey = |i: usize| -> Option { - let idx = accs.get(i).copied().map(|b| b as usize)?; - accounts.get(idx).copied() - }; - let (source, destination, amount) = match data[0] { - 12 if accs.len() >= 4 && data.len() >= 9 => { - let amt = u64::from_le_bytes(data[1..9].try_into().unwrap()); - match (get_pubkey(0), get_pubkey(2)) { - (Some(s), Some(d)) => (s, d, amt), - _ => continue, - } - } - 3 if accs.len() >= 3 && data.len() >= 9 => { - let amt = u64::from_le_bytes(data[1..9].try_into().unwrap()); - match (get_pubkey(0), get_pubkey(1)) { - (Some(s), Some(d)) => (s, d, amt), - _ => continue, - } - } - 2 if accs.len() >= 2 && data.len() >= 12 => { - let amt = u64::from_le_bytes(data[4..12].try_into().unwrap()); - match (get_pubkey(0), get_pubkey(1)) { - (Some(s), Some(d)) => (s, d, amt), - _ => continue, - } - } - _ => continue, - }; - - match (source, destination) { - (s, d) if s == user_to_token && d == to_vault => { - swap_data.from_mint = to_mint; - swap_data.from_amount = amount; - } - (s, d) if s == from_vault && d == user_from_token => { - swap_data.to_mint = from_mint; - swap_data.to_amount = amount; - } - (s, d) if s == user_from_token && d == from_vault => { - swap_data.from_mint = from_mint; - swap_data.from_amount = amount; - } - (s, d) if s == to_vault && d == user_to_token => { - swap_data.to_mint = to_mint; - swap_data.to_amount = amount; - } - (s, d) if s == user_from_token && d == to_vault => { - swap_data.from_mint = from_mint; - swap_data.from_amount = amount; - } - (s, d) if s == from_vault && d == user_to_token => { - swap_data.to_mint = to_mint; - swap_data.to_amount = amount; - } - _ => {} - } - if swap_data.from_mint != Pubkey::default() && swap_data.to_mint != Pubkey::default() { - break; - } - if swap_data.from_amount != 0 && swap_data.to_amount != 0 { - break; - } - } - - if swap_data.from_mint != Pubkey::default() - || swap_data.to_mint != Pubkey::default() - || swap_data.from_amount != 0 - || swap_data.to_amount != 0 - { - Some(swap_data) - } else { - None - } -} - -/// Parse token transfer data from standard Solana inner instructions -pub fn parse_swap_data_from_next_instructions( - event: &DexEvent, - inner_instruction: &solana_transaction_status::InnerInstructions, - current_index: i32, - accounts: &[Pubkey], -) -> Option { - extract_swap_data_from_instructions( - event, - inner_instruction.instructions.iter().map(|ix| ix.instruction.clone()), - current_index, - accounts, - ) -} - -/// Parse token transfer data from gRPC inner instructions -pub fn parse_swap_data_from_next_grpc_instructions( - event: &DexEvent, - inner_instruction: &yellowstone_grpc_proto::prelude::InnerInstructions, - current_index: i32, - accounts: &[Pubkey], -) -> Option { - extract_swap_data_from_instructions( - event, - inner_instruction.instructions.iter().cloned(), - current_index, - accounts, - ) -} diff --git a/src/streaming/event_parser/common/utils.rs b/src/streaming/event_parser/common/utils.rs deleted file mode 100755 index 7ef340f..0000000 --- a/src/streaming/event_parser/common/utils.rs +++ /dev/null @@ -1,119 +0,0 @@ -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 -} - -/// 从字节数组中提取鉴别器和剩余数据 -pub fn extract_discriminator(length: usize, data: &[u8]) -> Option<(&[u8], &[u8])> { - if data.len() < length { - return None; - } - Some((&data[..length], &data[length..])) -} - -/// 从日志中提取程序数据 -pub fn extract_program_data(log: &str) -> Option<&str> { - const PROGRAM_DATA_PREFIX: &str = "Program data: "; - log.strip_prefix(PROGRAM_DATA_PREFIX) -} - -/// 从日志中提取程序日志 -pub fn extract_program_log<'a>(log: &'a str, prefix: &str) -> Option<&'a str> { - log.strip_prefix(prefix) -} - -/// 安全地从字节数组中读取 i64 -pub fn read_i64_le(data: &[u8], offset: usize) -> Option { - if data.len() < offset + 8 { - return None; - } - let bytes: [u8; 8] = data[offset..offset + 8].try_into().ok()?; - Some(i64::from_le_bytes(bytes)) -} - -/// 安全地从字节数组中读取u64 -pub fn read_u64_le(data: &[u8], offset: usize) -> Option { - if data.len() < offset + 8 { - return None; - } - let bytes: [u8; 8] = data[offset..offset + 8].try_into().ok()?; - 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; - } - let bytes: [u8; 16] = data[offset..offset + 16].try_into().ok()?; - Some(u128::from_le_bytes(bytes)) -} - -pub fn read_u8_le(data: &[u8], offset: usize) -> Option { - if data.len() < offset + 1 { - return None; - } - let bytes: [u8; 1] = data[offset..offset + 1].try_into().ok()?; - Some(u8::from_le_bytes(bytes)) -} - -pub fn read_option_bool(data: &[u8], offset: &mut usize) -> Option> { - let has_value = data.get(*offset).copied()?; - *offset += 1; - - if has_value == 0 { - return Some(None); - } - - let value = data.get(*offset).copied()?; - *offset += 1; - - Some(Some(value != 0)) -} - -/// 安全地从字节数组中读取u32 -pub fn read_u32_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(u32::from_le_bytes(bytes)) -} - -/// 安全地从字节数组中读取u16 -pub fn read_u16_le(data: &[u8], offset: usize) -> Option { - if data.len() < offset + 2 { - return None; - } - let bytes: [u8; 2] = data[offset..offset + 2].try_into().ok()?; - Some(u16::from_le_bytes(bytes)) -} - -/// 安全地从字节数组中读取u8 -pub fn read_u8(data: &[u8], offset: usize) -> Option { - data.get(offset).copied() -} - -/// 验证账户索引的有效性 -pub fn validate_account_indices(indices: &[u8], account_count: usize) -> bool { - indices.iter().all(|&idx| (idx as usize) < account_count) -} - -/// 格式化公钥为短字符串 -pub fn format_pubkey_short(pubkey: &solana_sdk::pubkey::Pubkey) -> String { - let s = pubkey.to_string(); - if s.len() <= 8 { - s - } else { - format!("{}...{}", &s[..4], &s[s.len() - 4..]) - } -} diff --git a/src/streaming/event_parser/core/account_event_parser.rs b/src/streaming/event_parser/core/account_event_parser.rs index a78dc43..fb14496 100644 --- a/src/streaming/event_parser/core/account_event_parser.rs +++ b/src/streaming/event_parser/core/account_event_parser.rs @@ -1,18 +1,10 @@ use crate::streaming::event_parser::common::filter::EventTypeFilter; -use crate::streaming::event_parser::common::high_performance_clock::elapsed_micros_since; -use crate::streaming::event_parser::common::{EventMetadata, EventType, ProtocolType}; +use crate::streaming::event_parser::common::{EventMetadata, EventType}; use crate::streaming::event_parser::core::traits::DexEvent; use crate::streaming::event_parser::Protocol; use crate::streaming::grpc::AccountPretty; use serde::{Deserialize, Serialize}; -use solana_account_decoder::parse_nonce::parse_nonce; use solana_sdk::pubkey::Pubkey; -use spl_token::solana_program::program_pack::Pack; -use spl_token::state::{Account, Mint}; -use spl_token_2022::{ - extension::StateWithExtensions, - state::{Account as Account2022, Mint as Mint2022}, -}; /// 通用账户事件 #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] @@ -61,186 +53,38 @@ impl AccountEventParser { account: AccountPretty, event_type_filter: Option<&EventTypeFilter>, ) -> Option { - use crate::streaming::event_parser::core::dispatcher::EventDispatcher; - - // 1. 尝试从账户 discriminator 解析(协议特定账户) - if account.data.len() >= 8 { - let discriminator = &account.data[0..8]; - - // 尝试识别协议类型 - if let Some(protocol) = EventDispatcher::match_protocol_by_program_id(&account.owner) { - // 检查是否在请求的协议列表中 - if protocols.contains(&protocol) { - // 构建临时元数据(protocol会被dispatcher设置,event_type会在parser中设置) - let metadata = EventMetadata { - slot: account.slot, - signature: account.signature, - protocol: ProtocolType::Common, // 会被 EventDispatcher::dispatch_account 设置 - event_type: EventType::default(), // 会被具体 parser 设置 - program_id: account.owner, - recv_us: account.recv_us, - handle_us: elapsed_micros_since(account.recv_us), - ..Default::default() - }; - - // 使用 dispatcher 解析 - if let Some(event) = EventDispatcher::dispatch_account( - protocol, - discriminator, - &account, - metadata, - ) { - // 应用事件类型过滤 - if let Some(filter) = event_type_filter { - if filter.passes_event_type(&event.metadata().event_type) { - return Some(event); - } - // 不匹配过滤器,继续尝试其他解析方式 - } else { - return Some(event); - } - } - } - } - } - - // 2. 尝试解析特殊账户类型(Token、Nonce等) - // 这些是通用的,不属于特定协议 - let metadata = EventMetadata { - slot: account.slot, - signature: account.signature, - protocol: ProtocolType::Common, - event_type: EventType::default(), - program_id: account.owner, - recv_us: account.recv_us, - handle_us: elapsed_micros_since(account.recv_us), - ..Default::default() - }; - - // 尝试解析 Nonce 账户 - if let Some(event) = Self::parse_nonce_account_event(&account, metadata.clone()) { - if let Some(filter) = event_type_filter { - if filter.passes_event_type(&event.metadata().event_type) { - return Some(event); - } - } else { - return Some(event); - } - } - - // 尝试解析 Token 账户 - if let Some(event) = Self::parse_token_account_event(&account, metadata) { - if let Some(filter) = event_type_filter { - if filter.passes_event_type(&event.metadata().event_type) { - return Some(event); - } - } else { - return Some(event); - } - } - - None + crate::streaming::parser_sdk_bridge::parse_sdk_account_event( + &account, + protocols, + event_type_filter, + ) } pub fn parse_token_account_event( account: &AccountPretty, - mut metadata: EventMetadata, + metadata: EventMetadata, ) -> Option { - metadata.event_type = EventType::TokenAccount; - - let pubkey = account.pubkey; - let executable = account.executable; - let lamports = account.lamports; - let owner = account.owner; - let rent_epoch = account.rent_epoch; - // Spl Token Mint - if account.data.len() >= Mint::LEN { - if let Ok(mint) = Mint::unpack_from_slice(&account.data) { - let mut metadata = metadata.clone(); - metadata.event_type = EventType::TokenInfo; - let mut event = TokenInfoEvent { - metadata, - pubkey, - executable, - lamports, - owner, - rent_epoch, - supply: mint.supply, - decimals: mint.decimals, - }; - let recv_delta = elapsed_micros_since(account.recv_us); - event.metadata.handle_us = recv_delta; - return Some(DexEvent::TokenInfoEvent(event)); - } - } - // Spl Token2022 Mint - if account.data.len() >= Account2022::LEN { - if let Ok(mint) = StateWithExtensions::::unpack(&account.data) { - let mut metadata = metadata.clone(); - metadata.event_type = EventType::TokenInfo; - let mut event = TokenInfoEvent { - metadata, - pubkey, - executable, - lamports, - owner, - rent_epoch, - supply: mint.base.supply, - decimals: mint.base.decimals, - }; - let recv_delta = elapsed_micros_since(account.recv_us); - event.metadata.handle_us = recv_delta; - return Some(DexEvent::TokenInfoEvent(event)); - } - } - let amount = if account.owner.to_bytes() == spl_token_2022::ID.to_bytes() { - StateWithExtensions::::unpack(&account.data) - .ok() - .map(|info| info.base.amount) - } else { - Account::unpack(&account.data).ok().map(|info| info.amount) - }; - - let mut event = TokenAccountEvent { - metadata, - pubkey, - executable, - lamports, - owner, - rent_epoch, - amount, - token_owner: account.owner, - }; - let recv_delta = elapsed_micros_since(account.recv_us); - event.metadata.handle_us = recv_delta; - Some(DexEvent::TokenAccountEvent(event)) + let mut account = account.clone(); + overlay_metadata(&mut account, &metadata); + let filter = EventTypeFilter::include_only([EventType::TokenAccount]); + crate::streaming::parser_sdk_bridge::parse_sdk_account_event(&account, &[], Some(&filter)) } pub fn parse_nonce_account_event( account: &AccountPretty, - mut metadata: EventMetadata, + metadata: EventMetadata, ) -> Option { - metadata.event_type = EventType::NonceAccount; + let mut account = account.clone(); + overlay_metadata(&mut account, &metadata); + let filter = EventTypeFilter::include_only([EventType::NonceAccount]); + crate::streaming::parser_sdk_bridge::parse_sdk_account_event(&account, &[], Some(&filter)) + } +} - if let Ok(info) = parse_nonce(&account.data) { - match info { - solana_account_decoder::parse_nonce::UiNonceState::Initialized(details) => { - let mut event = NonceAccountEvent { - metadata, - pubkey: account.pubkey, - executable: account.executable, - lamports: account.lamports, - owner: account.owner, - rent_epoch: account.rent_epoch, - nonce: details.blockhash, - authority: details.authority, - }; - event.metadata.handle_us = elapsed_micros_since(account.recv_us); - return Some(DexEvent::NonceAccountEvent(event)); - } - solana_account_decoder::parse_nonce::UiNonceState::Uninitialized => {} - } - } - None +fn overlay_metadata(account: &mut AccountPretty, metadata: &EventMetadata) { + account.slot = metadata.slot; + account.signature = metadata.signature; + if metadata.recv_us != 0 { + account.recv_us = metadata.recv_us; } } diff --git a/src/streaming/event_parser/core/common_event_parser.rs b/src/streaming/event_parser/core/common_event_parser.rs index 948676b..f2306b7 100644 --- a/src/streaming/event_parser/core/common_event_parser.rs +++ b/src/streaming/event_parser/core/common_event_parser.rs @@ -1,15 +1,9 @@ use crate::streaming::event_parser::common::high_performance_clock::elapsed_micros_since; -use crate::streaming::event_parser::common::types::{EventType, ProtocolType}; use crate::streaming::event_parser::common::EventMetadata; use crate::streaming::event_parser::core::traits::DexEvent; use crate::streaming::event_parser::protocols::block::block_meta_event::BlockMetaEvent; use borsh::BorshDeserialize; use serde::{Deserialize, Serialize}; -use solana_sdk::pubkey::Pubkey; - -// Compute Budget Program ID -pub const COMPUTE_BUDGET_PROGRAM_ID: Pubkey = - solana_sdk::pubkey!("ComputeBudget111111111111111111111111111111"); /// SetComputeUnitLimit 事件 #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)] @@ -42,42 +36,4 @@ impl CommonEventParser { block_meta_event.metadata.handle_us = elapsed_micros_since(recv_us); DexEvent::BlockMetaEvent(block_meta_event) } - - /// 解析 Compute Budget 指令 - pub fn parse_compute_budget_instruction( - instruction_data: &[u8], - mut metadata: EventMetadata, - ) -> Option { - if instruction_data.is_empty() { - return None; - } - - // 设置 protocol 为 Common - metadata.protocol = ProtocolType::Common; - - // Compute Budget 指令使用单字节判别器 - match instruction_data[0] { - // SetComputeUnitLimit: discriminator = 2 - 2 => { - if instruction_data.len() < 5 { - return None; - } - let units = u32::from_le_bytes(instruction_data[1..5].try_into().ok()?); - metadata.event_type = EventType::SetComputeUnitLimit; - let event = SetComputeUnitLimitEvent { metadata, units }; - Some(DexEvent::SetComputeUnitLimitEvent(event)) - } - // SetComputeUnitPrice: discriminator = 3 - 3 => { - if instruction_data.len() < 9 { - return None; - } - let micro_lamports = u64::from_le_bytes(instruction_data[1..9].try_into().ok()?); - metadata.event_type = EventType::SetComputeUnitPrice; - let event = SetComputeUnitPriceEvent { metadata, micro_lamports }; - Some(DexEvent::SetComputeUnitPriceEvent(event)) - } - _ => None, - } - } } diff --git a/src/streaming/event_parser/core/dispatcher.rs b/src/streaming/event_parser/core/dispatcher.rs index 1df8f37..295080f 100644 --- a/src/streaming/event_parser/core/dispatcher.rs +++ b/src/streaming/event_parser/core/dispatcher.rs @@ -1,331 +1,193 @@ -//! 事件路由入口(类比 sol-parser-sdk 的 `instr`,区分「原生字节解析」与「sdk 事件对齐」)。 +//! SDK-backed event dispatcher kept for compatibility with older streamer APIs. //! -//! ## 代码去哪找 -//! - **`protocols/<协议>/parser.rs`** — Yellowstone / shred 路径下的顶层与 inner 指令解析(手写)。 -//! - **`streaming/parser_sdk_bridge/`** — `sol-parser-sdk::DexEvent` → streamer `DexEvent` 字段映射。 -//! - **`protocols/sol_parser_forward/native.rs`** — Orca / Meteora Pools & DLMM:调用 sdk `instr` 后再走 bridge。 -//! -//! ## 设计原则 -//! - **单一职责**: 每个函数只负责一件事(路由、解析、合并分离) -//! - **灵活性**: 调用方可以选择是否合并,或自定义合并逻辑 -//! - **可测试性**: 每个函数都可以独立测试 +//! Streamer no longer owns protocol parsers here. The dispatcher only adapts +//! streamer metadata and routes to `sol-parser-sdk` parsers, then converts SDK +//! events back to streamer `DexEvent`. -use crate::streaming::event_parser::{ - common::EventMetadata, - core::common_event_parser::{CommonEventParser, COMPUTE_BUDGET_PROGRAM_ID}, - protocols::{ - bonk::parser as bonk, meteora_damm_v2::parser as meteora_damm_v2, - pumpfun::parser as pumpfun, pumpswap::parser as pumpswap, - raydium_amm_v4::parser as raydium_amm_v4, raydium_clmm::parser as raydium_clmm, - raydium_cpmm::parser as raydium_cpmm, sol_parser_forward, - }, - DexEvent, Protocol, +use crate::streaming::event_parser::{common::EventMetadata, DexEvent, Protocol}; +use crate::streaming::parser_sdk_bridge::{ + block_timestamp_from_stream_meta, convert_parser_event, fuse_streamer_ix_ctx, + parse_sdk_account_event, +}; +use sol_parser_sdk::core::events::EventMetadata as PbEventMetadata; +use sol_parser_sdk::instr::{ + all_inner, program_ids, pump_amm_inner, pump_inner, raydium_clmm_inner, }; use solana_sdk::pubkey::Pubkey; -/// 中心事件解析调度器 -/// -/// 负责将解析请求路由到对应协议的解析函数 pub struct EventDispatcher; impl EventDispatcher { - /// 解析 instruction 事件(只解析,不合并) - /// - /// # 参数 - /// - `protocol`: 协议类型 - /// - `instruction_discriminator`: 指令判别器 (8 bytes) - /// - `instruction_data`: 指令数据 - /// - `accounts`: 账户公钥列表 - /// - `metadata`: 事件元数据 - /// - /// # 返回 - /// 解析成功返回 `Some(DexEvent)`,否则返回 `None` #[inline] pub fn dispatch_instruction( protocol: Protocol, instruction_discriminator: &[u8], instruction_data: &[u8], accounts: &[Pubkey], - mut metadata: EventMetadata, + metadata: EventMetadata, ) -> Option { - // 根据协议类型设置 metadata.protocol - use crate::streaming::event_parser::common::ProtocolType; - metadata.protocol = match protocol { - Protocol::PumpFun => ProtocolType::PumpFun, - Protocol::PumpSwap => ProtocolType::PumpSwap, - Protocol::Bonk => ProtocolType::Bonk, - Protocol::RaydiumCpmm => ProtocolType::RaydiumCpmm, - Protocol::RaydiumClmm => ProtocolType::RaydiumClmm, - Protocol::RaydiumAmmV4 => ProtocolType::RaydiumAmmV4, - Protocol::MeteoraDammV2 => ProtocolType::MeteoraDammV2, - Protocol::OrcaWhirlpool => ProtocolType::OrcaWhirlpool, - Protocol::MeteoraPools => ProtocolType::MeteoraPools, - Protocol::MeteoraDlmm => ProtocolType::MeteoraDlmm, - }; + let mut full = Vec::with_capacity(instruction_discriminator.len() + instruction_data.len()); + full.extend_from_slice(instruction_discriminator); + full.extend_from_slice(instruction_data); - match protocol { - Protocol::OrcaWhirlpool | Protocol::MeteoraPools | Protocol::MeteoraDlmm => { - sol_parser_forward::native::dispatch_instruction( - protocol.clone(), - instruction_discriminator, - instruction_data, - accounts, - &metadata, - ) - } - Protocol::PumpFun => pumpfun::parse_pumpfun_instruction_data( - instruction_discriminator, - instruction_data, - accounts, - metadata, - ), - Protocol::PumpSwap => pumpswap::parse_pumpswap_instruction_data( - instruction_discriminator, - instruction_data, - accounts, - metadata, - ), - Protocol::Bonk => bonk::parse_bonk_instruction_data( - instruction_discriminator, - instruction_data, - accounts, - metadata, - ), - Protocol::RaydiumCpmm => raydium_cpmm::parse_raydium_cpmm_instruction_data( - instruction_discriminator, - instruction_data, - accounts, - metadata, - ), - Protocol::RaydiumClmm => raydium_clmm::parse_raydium_clmm_instruction_data( - instruction_discriminator, - instruction_data, - accounts, - metadata, - ), - Protocol::RaydiumAmmV4 => raydium_amm_v4::parse_raydium_amm_v4_instruction_data( - instruction_discriminator, - instruction_data, - accounts, - metadata, - ), - Protocol::MeteoraDammV2 => meteora_damm_v2::parse_meteora_damm_v2_instruction_data( - instruction_discriminator, - instruction_data, - accounts, - metadata, - ), - } + let program_id = Self::get_program_id(protocol); + let pb = sol_parser_sdk::instr::parse_instruction_unified( + &full, + accounts, + metadata.signature, + metadata.slot, + metadata.tx_index.unwrap_or(0), + Some(metadata.block_time_ms.saturating_mul(1000)), + metadata.recv_us, + None, + &program_id, + )?; + + let ts = block_timestamp_from_stream_meta(&metadata); + let ev = convert_parser_event(pb, Some(&ts), metadata.recv_us)?; + Some(fuse_streamer_ix_ctx(ev, &metadata)) } - /// 解析 inner instruction 事件(只解析,不合并) - /// - /// # 参数 - /// - `protocol`: 协议类型 - /// - `inner_instruction_discriminator`: 内联指令判别器 (16 bytes) - /// - `inner_instruction_data`: 内联指令数据 - /// - `metadata`: 事件元数据 - /// - /// # 返回 - /// 解析成功返回 `Some(DexEvent)`,否则返回 `None` #[inline] pub fn dispatch_inner_instruction( protocol: Protocol, inner_instruction_discriminator: &[u8], inner_instruction_data: &[u8], - mut metadata: EventMetadata, + metadata: EventMetadata, ) -> Option { - // 根据协议类型设置 metadata.protocol - use crate::streaming::event_parser::common::ProtocolType; - metadata.protocol = match protocol { - Protocol::PumpFun => ProtocolType::PumpFun, - Protocol::PumpSwap => ProtocolType::PumpSwap, - Protocol::Bonk => ProtocolType::Bonk, - Protocol::RaydiumCpmm => ProtocolType::RaydiumCpmm, - Protocol::RaydiumClmm => ProtocolType::RaydiumClmm, - Protocol::RaydiumAmmV4 => ProtocolType::RaydiumAmmV4, - Protocol::MeteoraDammV2 => ProtocolType::MeteoraDammV2, - Protocol::OrcaWhirlpool => ProtocolType::OrcaWhirlpool, - Protocol::MeteoraPools => ProtocolType::MeteoraPools, - Protocol::MeteoraDlmm => ProtocolType::MeteoraDlmm, - }; + let disc: [u8; 16] = inner_instruction_discriminator.try_into().ok()?; + let pm = pb_meta_from_streamer(&metadata); - match protocol { - Protocol::OrcaWhirlpool | Protocol::MeteoraPools | Protocol::MeteoraDlmm => { - sol_parser_forward::native::dispatch_inner_instruction( - protocol.clone(), - inner_instruction_discriminator, - inner_instruction_data, - &metadata, - ) + let pb = match protocol { + Protocol::PumpFun => pump_inner::parse_pumpfun_inner_instruction( + &disc, + inner_instruction_data, + pm, + false, + ), + Protocol::PumpSwap => { + pump_amm_inner::parse_pumpswap_inner_instruction(&disc, inner_instruction_data, pm) } - Protocol::PumpFun => pumpfun::parse_pumpfun_inner_instruction_data( - inner_instruction_discriminator, + Protocol::PumpFees => all_inner::pump_fees::parse(&disc, inner_instruction_data, pm), + Protocol::Bonk | Protocol::RaydiumLaunchpad => { + all_inner::bonk::parse(&disc, inner_instruction_data, pm) + } + Protocol::RaydiumCpmm => { + all_inner::raydium_cpmm::parse(&disc, inner_instruction_data, pm) + } + Protocol::RaydiumClmm => raydium_clmm_inner::parse_raydium_clmm_inner_instruction( + &disc, inner_instruction_data, - metadata, - ), - Protocol::PumpSwap => pumpswap::parse_pumpswap_inner_instruction_data( - inner_instruction_discriminator, - inner_instruction_data, - metadata, - ), - Protocol::Bonk => bonk::parse_bonk_inner_instruction_data( - inner_instruction_discriminator, - inner_instruction_data, - metadata, - ), - Protocol::RaydiumCpmm => raydium_cpmm::parse_raydium_cpmm_inner_instruction_data( - inner_instruction_discriminator, - inner_instruction_data, - metadata, - ), - Protocol::RaydiumClmm => raydium_clmm::parse_raydium_clmm_inner_instruction_data( - inner_instruction_discriminator, - inner_instruction_data, - metadata, - ), - Protocol::RaydiumAmmV4 => raydium_amm_v4::parse_raydium_amm_v4_inner_instruction_data( - inner_instruction_discriminator, - inner_instruction_data, - metadata, + pm, ), + Protocol::RaydiumAmmV4 => { + all_inner::raydium_amm::parse(&disc, inner_instruction_data, pm) + } Protocol::MeteoraDammV2 => { - meteora_damm_v2::parse_meteora_damm_v2_inner_instruction_data( - inner_instruction_discriminator, - inner_instruction_data, - metadata, - ) + all_inner::meteora_damm::parse(&disc, inner_instruction_data, pm) } - } + Protocol::OrcaWhirlpool => all_inner::orca::parse(&disc, inner_instruction_data, pm), + Protocol::MeteoraPools => { + all_inner::meteora_amm::parse(&disc, inner_instruction_data, pm) + } + Protocol::MeteoraDlmm => { + all_inner::meteora_dlmm::parse(&disc, inner_instruction_data, pm) + } + }?; + + let ts = block_timestamp_from_stream_meta(&metadata); + let ev = convert_parser_event(pb, Some(&ts), metadata.recv_us)?; + Some(fuse_streamer_ix_ctx(ev, &metadata)) } - /// 通过 program_id 匹配协议类型 #[inline] pub fn match_protocol_by_program_id(program_id: &Pubkey) -> Option { - if program_id == &pumpfun::PUMPFUN_PROGRAM_ID { + if program_id == &program_ids::PUMPFUN_PROGRAM_ID { Some(Protocol::PumpFun) - } else if program_id == &pumpswap::PUMPSWAP_PROGRAM_ID { + } else if program_id == &program_ids::PUMP_FEES_PROGRAM_ID { + Some(Protocol::PumpFees) + } else if program_id == &program_ids::PUMPSWAP_PROGRAM_ID { Some(Protocol::PumpSwap) - } else if program_id == &bonk::BONK_PROGRAM_ID { + } else if program_id == &program_ids::BONK_PROGRAM_ID { Some(Protocol::Bonk) - } else if program_id == &raydium_cpmm::RAYDIUM_CPMM_PROGRAM_ID { + } else if program_id == &program_ids::RAYDIUM_CPMM_PROGRAM_ID { Some(Protocol::RaydiumCpmm) - } else if program_id == &raydium_clmm::RAYDIUM_CLMM_PROGRAM_ID { + } else if program_id == &program_ids::RAYDIUM_CLMM_PROGRAM_ID { Some(Protocol::RaydiumClmm) - } else if program_id == &raydium_amm_v4::RAYDIUM_AMM_V4_PROGRAM_ID { + } else if program_id == &program_ids::RAYDIUM_AMM_V4_PROGRAM_ID { Some(Protocol::RaydiumAmmV4) - } else if program_id == &meteora_damm_v2::METEORA_DAMM_V2_PROGRAM_ID { + } else if program_id == &program_ids::METEORA_DAMM_V2_PROGRAM_ID { Some(Protocol::MeteoraDammV2) - } else if program_id == &sol_parser_forward::ORCA_WHIRLPOOL_PROGRAM_ID { + } else if program_id == &program_ids::ORCA_WHIRLPOOL_PROGRAM_ID { Some(Protocol::OrcaWhirlpool) - } else if program_id == &sol_parser_forward::METEORA_POOLS_PROGRAM_ID { + } else if program_id == &program_ids::METEORA_POOLS_PROGRAM_ID { Some(Protocol::MeteoraPools) - } else if program_id == &sol_parser_forward::METEORA_DLMM_PROGRAM_ID { + } else if program_id == &program_ids::METEORA_DLMM_PROGRAM_ID { Some(Protocol::MeteoraDlmm) } else { None } } - /// 检查是否为 Compute Budget Program #[inline] pub fn is_compute_budget_program(program_id: &Pubkey) -> bool { - program_id == &COMPUTE_BUDGET_PROGRAM_ID + program_id == &solana_sdk::pubkey!("ComputeBudget111111111111111111111111111111") } - /// 解析 Compute Budget 指令 - /// - /// # 参数 - /// - `instruction_data`: 指令数据 - /// - `metadata`: 事件元数据 - /// - /// # 返回 - /// 解析成功返回 `Some(DexEvent)`,否则返回 `None` #[inline] pub fn dispatch_compute_budget_instruction( - instruction_data: &[u8], - metadata: EventMetadata, + _instruction_data: &[u8], + _metadata: EventMetadata, ) -> Option { - CommonEventParser::parse_compute_budget_instruction(instruction_data, metadata) + None } - /// 获取指定协议的 program_id #[inline] pub fn get_program_id(protocol: Protocol) -> Pubkey { match protocol { - Protocol::PumpFun => pumpfun::PUMPFUN_PROGRAM_ID, - Protocol::PumpSwap => pumpswap::PUMPSWAP_PROGRAM_ID, - Protocol::Bonk => bonk::BONK_PROGRAM_ID, - Protocol::RaydiumCpmm => raydium_cpmm::RAYDIUM_CPMM_PROGRAM_ID, - Protocol::RaydiumClmm => raydium_clmm::RAYDIUM_CLMM_PROGRAM_ID, - Protocol::RaydiumAmmV4 => raydium_amm_v4::RAYDIUM_AMM_V4_PROGRAM_ID, - Protocol::MeteoraDammV2 => meteora_damm_v2::METEORA_DAMM_V2_PROGRAM_ID, - Protocol::OrcaWhirlpool => sol_parser_forward::ORCA_WHIRLPOOL_PROGRAM_ID, - Protocol::MeteoraPools => sol_parser_forward::METEORA_POOLS_PROGRAM_ID, - Protocol::MeteoraDlmm => sol_parser_forward::METEORA_DLMM_PROGRAM_ID, + Protocol::PumpFun => program_ids::PUMPFUN_PROGRAM_ID, + Protocol::PumpFees => program_ids::PUMP_FEES_PROGRAM_ID, + Protocol::PumpSwap => program_ids::PUMPSWAP_PROGRAM_ID, + Protocol::Bonk | Protocol::RaydiumLaunchpad => program_ids::BONK_PROGRAM_ID, + Protocol::RaydiumCpmm => program_ids::RAYDIUM_CPMM_PROGRAM_ID, + Protocol::RaydiumClmm => program_ids::RAYDIUM_CLMM_PROGRAM_ID, + Protocol::RaydiumAmmV4 => program_ids::RAYDIUM_AMM_V4_PROGRAM_ID, + Protocol::MeteoraDammV2 => program_ids::METEORA_DAMM_V2_PROGRAM_ID, + Protocol::OrcaWhirlpool => program_ids::ORCA_WHIRLPOOL_PROGRAM_ID, + Protocol::MeteoraPools => program_ids::METEORA_POOLS_PROGRAM_ID, + Protocol::MeteoraDlmm => program_ids::METEORA_DLMM_PROGRAM_ID, } } - /// 批量获取 program_ids pub fn get_program_ids(protocols: &[Protocol]) -> Vec { - protocols.iter().map(|p| Self::get_program_id(p.clone())).collect() + let mut ids = Vec::with_capacity(protocols.len()); + for protocol in protocols { + let id = Self::get_program_id(protocol.clone()); + if !ids.contains(&id) { + ids.push(id); + } + } + ids } - /// 解析账户数据 - /// - /// 根据账户的 discriminator 路由到对应协议的账户解析函数 - /// - /// # 参数 - /// - `protocol`: 协议类型 - /// - `discriminator`: 账户判别器 - /// - `account`: 账户信息 - /// - `metadata`: 事件元数据 - /// - /// # 返回 - /// 解析成功返回 `Some(DexEvent)`,否则返回 `None` pub fn dispatch_account( protocol: Protocol, - discriminator: &[u8], + _discriminator: &[u8], account: &crate::streaming::grpc::AccountPretty, - mut metadata: crate::streaming::event_parser::common::EventMetadata, + _metadata: EventMetadata, ) -> Option { - // 根据协议类型设置 metadata.protocol - use crate::streaming::event_parser::common::ProtocolType; - metadata.protocol = match protocol { - Protocol::PumpFun => ProtocolType::PumpFun, - Protocol::PumpSwap => ProtocolType::PumpSwap, - Protocol::Bonk => ProtocolType::Bonk, - Protocol::RaydiumCpmm => ProtocolType::RaydiumCpmm, - Protocol::RaydiumClmm => ProtocolType::RaydiumClmm, - Protocol::RaydiumAmmV4 => ProtocolType::RaydiumAmmV4, - Protocol::MeteoraDammV2 => ProtocolType::MeteoraDammV2, - Protocol::OrcaWhirlpool => ProtocolType::OrcaWhirlpool, - Protocol::MeteoraPools => ProtocolType::MeteoraPools, - Protocol::MeteoraDlmm => ProtocolType::MeteoraDlmm, - }; + parse_sdk_account_event(account, &[protocol], None) + } +} - match protocol { - Protocol::OrcaWhirlpool | Protocol::MeteoraPools | Protocol::MeteoraDlmm => None, - Protocol::PumpFun => { - pumpfun::parse_pumpfun_account_data(discriminator, account, metadata) - } - Protocol::PumpSwap => { - pumpswap::parse_pumpswap_account_data(discriminator, account, metadata) - } - Protocol::Bonk => bonk::parse_bonk_account_data(discriminator, account, metadata), - Protocol::RaydiumCpmm => { - raydium_cpmm::parse_raydium_cpmm_account_data(discriminator, account, metadata) - } - Protocol::RaydiumClmm => { - raydium_clmm::parse_raydium_clmm_account_data(discriminator, account, metadata) - } - Protocol::RaydiumAmmV4 => { - raydium_amm_v4::parse_raydium_amm_v4_account_data(discriminator, account, metadata) - } - Protocol::MeteoraDammV2 => { - // Meteora DAMM 目前不需要解析账户数据,返回 None - None - } - } +#[inline] +fn pb_meta_from_streamer(sm: &EventMetadata) -> PbEventMetadata { + PbEventMetadata { + signature: sm.signature, + slot: sm.slot, + tx_index: sm.tx_index.unwrap_or(0), + block_time_us: sm.block_time_ms.saturating_mul(1000), + grpc_recv_us: sm.recv_us, + recent_blockhash: sm.recent_blockhash.clone(), } } diff --git a/src/streaming/event_parser/core/event_parser/compiled_path/compiled_instruction.rs b/src/streaming/event_parser/core/event_parser/compiled_path/compiled_instruction.rs deleted file mode 100644 index 9f728aa..0000000 --- a/src/streaming/event_parser/core/event_parser/compiled_path/compiled_instruction.rs +++ /dev/null @@ -1,195 +0,0 @@ -//! Single Solana [`CompiledInstruction`] parsing with local inner merge and swap enrichment. -use crate::streaming::event_parser::{ - common::{ - filter::{passes_event_type_filter, EventTypeFilter}, - high_performance_clock::elapsed_micros_since, - parse_swap_data_from_next_instructions, EventMetadata, - }, - core::{dispatcher::EventDispatcher, merger_event::merge}, - protocols::{ - raydium_amm_v4::parser::RAYDIUM_AMM_V4_PROGRAM_ID, - sol_parser_forward::METEORA_DLMM_PROGRAM_ID, - }, - DexEvent, Protocol, -}; -use prost_types::Timestamp; -use solana_sdk::{ - message::compiled_instruction::CompiledInstruction, pubkey::Pubkey, signature::Signature, -}; -use solana_transaction_status::InnerInstructions; -use std::sync::Arc; - -pub(super) fn parse_events_from_instruction( - protocols: &[Protocol], - event_type_filter: Option<&EventTypeFilter>, - instruction: &CompiledInstruction, - accounts: &[Pubkey], - signature: Signature, - slot: u64, - block_time: Option, - recv_us: i64, - outer_index: i64, - inner_index: Option, - bot_wallet: Option, - tx_index: Option, - recent_blockhash: Option<&str>, - inner_instructions: Option<&InnerInstructions>, - callback: Arc, -) -> anyhow::Result<()> { - // Bounds check before reading the program id index. - let program_id_index = instruction.program_id_index as usize; - if program_id_index >= accounts.len() { - return Ok(()); - } - let program_id = accounts[program_id_index]; - if !super::super::helpers::should_handle(protocols, event_type_filter, &program_id) { - return Ok(()); - } - - let is_cu_program = EventDispatcher::is_compute_budget_program(&program_id); - - let disc_len = match program_id { - RAYDIUM_AMM_V4_PROGRAM_ID | METEORA_DLMM_PROGRAM_ID => 1, - _ => 8, - }; - - // Non-ComputeBudget instructions need at least a discriminator. - if !is_cu_program && instruction.data.len() < disc_len { - return Ok(()); - } - - // Build streamer metadata. - let timestamp = block_time.unwrap_or(Timestamp { seconds: 0, nanos: 0 }); - let block_time_ms = timestamp.seconds * 1000 + (timestamp.nanos as i64) / 1_000_000; - let metadata = EventMetadata::new( - signature, - slot, - timestamp.seconds, - block_time_ms, - Default::default(), // protocol will be set by dispatcher - Default::default(), // event_type will be set by dispatcher - program_id, - outer_index, - inner_index, - recv_us, - tx_index, - recent_blockhash.map(|s| s.to_string()), - ); - - if is_cu_program { - if let Some(event) = EventDispatcher::dispatch_compute_budget_instruction( - &instruction.data, - metadata.clone(), - ) { - if passes_event_type_filter(event_type_filter, &event) { - callback(event); - } - } - return Ok(()); - } - - // Match the parser protocol. - let protocol = match EventDispatcher::match_protocol_by_program_id(&program_id) { - Some(p) => p, - None => return Ok(()), - }; - - // Split discriminator and instruction payload. - let instruction_discriminator = &instruction.data[..disc_len]; - let instruction_data = &instruction.data[disc_len..]; - - // Build the account pubkey list for this instruction. - let account_pubkeys: Vec = instruction - .accounts - .iter() - .filter_map(|&idx| accounts.get(idx as usize).copied()) - .collect(); - - // Parse the instruction event. - let mut event = match EventDispatcher::dispatch_instruction( - protocol.clone(), - instruction_discriminator, - instruction_data, - &account_pubkeys, - metadata.clone(), - ) { - Some(e) => e, - None => return Ok(()), - }; - - // Find the next CPI log for merge. - let mut inner_instruction_event: Option = None; - if let Some(inner_instructions_ref) = inner_instructions { - let raw = inner_index.unwrap_or(-1); - let current_inner_idx = raw.clamp(i32::MIN as i64, i32::MAX as i64) as i32; - - // Parse the inner event and swap data in parallel on the compiled local path. - let (inner_event_result, swap_data_result) = std::thread::scope(|s| { - let inner_event_handle = s.spawn(|| { - for (idx, inner_instruction) in - inner_instructions_ref.instructions.iter().enumerate() - { - // Only inspect CPI logs after the current inner instruction. - if (idx as i32) <= current_inner_idx { - continue; - } - - let inner_data = &inner_instruction.instruction.data; - // Inner CPI logs use a 16-byte discriminator. - if inner_data.len() < 16 { - continue; - } - let inner_discriminator = &inner_data[..16]; - let inner_instruction_data = &inner_data[16..]; - - if let Some(inner_event) = EventDispatcher::dispatch_inner_instruction( - protocol.clone(), - inner_discriminator, - inner_instruction_data, - metadata.clone(), - ) { - return Some(inner_event); - } - } - None - }); - - let swap_data_handle = s.spawn(|| { - if event.metadata().swap_data.is_none() { - parse_swap_data_from_next_instructions( - &event, - inner_instructions_ref, - current_inner_idx, - accounts, - ) - } else { - None - } - }); - - // Wait for both local tasks. - (inner_event_handle.join().unwrap(), swap_data_handle.join().unwrap()) - }); - - inner_instruction_event = inner_event_result; - if let Some(swap_data) = swap_data_result { - event.metadata_mut().set_swap_data(swap_data); - } - } - - // PumpFun MIGRATE emits instruction-only data when no CPI log exists. - - // Merge CPI details into the outer event. - if let Some(inner_instruction_event) = inner_instruction_event { - merge(&mut event, inner_instruction_event); - } - - // Stamp handling latency using the high-performance clock. - event.metadata_mut().handle_us = elapsed_micros_since(recv_us); - event = super::super::helpers::process_event(event, bot_wallet); - if passes_event_type_filter(event_type_filter, &event) { - callback(event); - } - - Ok(()) -} diff --git a/src/streaming/event_parser/core/event_parser/compiled_path/compiled_transaction.rs b/src/streaming/event_parser/core/event_parser/compiled_path/compiled_transaction.rs deleted file mode 100644 index ec39f7a..0000000 --- a/src/streaming/event_parser/core/event_parser/compiled_path/compiled_transaction.rs +++ /dev/null @@ -1,87 +0,0 @@ -//! Sequential top-level and inner ix traversal for [`VersionedTransaction`]. -use crate::streaming::event_parser::{common::filter::EventTypeFilter, DexEvent, Protocol}; -use prost_types::Timestamp; -use solana_sdk::{pubkey::Pubkey, signature::Signature, transaction::VersionedTransaction}; -use solana_transaction_status::InnerInstructions; -use std::sync::Arc; - -pub(crate) async fn parse_instruction_events_from_versioned_transaction( - protocols: &[Protocol], - event_type_filter: Option<&EventTypeFilter>, - transaction: &VersionedTransaction, - signature: Signature, - slot: Option, - block_time: Option, - recv_us: i64, - accounts: &[Pubkey], - inner_instructions: &[InnerInstructions], - bot_wallet: Option, - tx_index: Option, - callback: Arc, -) -> anyhow::Result<()> { - let compiled_instructions = transaction.message.instructions(); - let recent_blockhash = Some(transaction.message.recent_blockhash().to_string()); - let mut accounts: Vec = accounts.to_vec(); - let has_program = accounts - .iter() - .any(|account| super::super::helpers::should_handle(protocols, event_type_filter, account)); - if has_program { - // Parse each instruction in order. - for (index, instruction) in compiled_instructions.iter().enumerate() { - if let Some(program_id) = accounts.get(instruction.program_id_index as usize) { - let program_id = *program_id; - let inner_instructions = inner_instructions - .iter() - .find(|inner_instruction| inner_instruction.index == index as u8); - if super::super::helpers::should_handle(protocols, event_type_filter, &program_id) { - let max_idx = instruction.accounts.iter().max().unwrap_or(&0); - if *max_idx as usize >= accounts.len() { - accounts.resize(*max_idx as usize + 1, Pubkey::default()); - } - super::compiled_instruction::parse_events_from_instruction( - protocols, - event_type_filter, - instruction, - &accounts, - signature, - slot.unwrap_or(0), - block_time, - recv_us, - index as i64, - None, - bot_wallet, - tx_index, - recent_blockhash.as_deref(), - inner_instructions, - callback.clone(), - )?; - } - // Immediately process inner instructions for correct ordering - if let Some(inner_instructions) = inner_instructions { - for (inner_index, inner_instruction) in - inner_instructions.instructions.iter().enumerate() - { - super::compiled_instruction::parse_events_from_instruction( - protocols, - event_type_filter, - &inner_instruction.instruction, - &accounts, - signature, - slot.unwrap_or(0), - block_time, - recv_us, - index as i64, - Some(inner_index as i64), - bot_wallet, - tx_index, - recent_blockhash.as_deref(), - Some(&inner_instructions), - callback.clone(), - )?; - } - } - } - } - } - Ok(()) -} diff --git a/src/streaming/event_parser/core/event_parser/compiled_path/mod.rs b/src/streaming/event_parser/core/event_parser/compiled_path/mod.rs deleted file mode 100644 index afd58e1..0000000 --- a/src/streaming/event_parser/core/event_parser/compiled_path/mod.rs +++ /dev/null @@ -1,11 +0,0 @@ -//! Standard [`VersionedTransaction`] / [`CompiledInstruction`] path for RPC and replay. -//! -//! | Module | Responsibility | -//! |--------|------| -//! | [`compiled_transaction`] | top-level ix loop | -//! | [`compiled_instruction`] | single Solana `CompiledInstruction` | - -mod compiled_instruction; -mod compiled_transaction; - -pub(super) use compiled_transaction::parse_instruction_events_from_versioned_transaction; diff --git a/src/streaming/event_parser/core/event_parser/grpc_path/grpc_instruction.rs b/src/streaming/event_parser/core/event_parser/grpc_path/grpc_instruction.rs deleted file mode 100644 index 95dc191..0000000 --- a/src/streaming/event_parser/core/event_parser/grpc_path/grpc_instruction.rs +++ /dev/null @@ -1,171 +0,0 @@ -//! Single Yellowstone [`CompiledInstruction`] parsing: dispatch, inner merge, swap enrichment. -use crate::streaming::event_parser::{ - common::{ - filter::{passes_event_type_filter, EventTypeFilter}, - high_performance_clock::elapsed_micros_since, - parse_swap_data_from_next_grpc_instructions, EventMetadata, - }, - core::{dispatcher::EventDispatcher, merger_event::merge}, - protocols::{ - raydium_amm_v4::parser::RAYDIUM_AMM_V4_PROGRAM_ID, - sol_parser_forward::METEORA_DLMM_PROGRAM_ID, - }, - DexEvent, Protocol, -}; -use prost_types::Timestamp; -use solana_sdk::{pubkey::Pubkey, signature::Signature}; -use std::sync::Arc; - -pub(super) fn parse_events_from_grpc_instruction( - protocols: &[Protocol], - event_type_filter: Option<&EventTypeFilter>, - instruction: &yellowstone_grpc_proto::prelude::CompiledInstruction, - accounts: &[Pubkey], - signature: Signature, - slot: u64, - block_time: Option, - recv_us: i64, - outer_index: i64, - inner_index: Option, - bot_wallet: Option, - tx_index: Option, - recent_blockhash: Option<&str>, - inner_instructions: Option<&yellowstone_grpc_proto::prelude::InnerInstructions>, - callback: Arc, -) -> anyhow::Result<()> { - // Bounds check before reading the program id index. - let program_id_index = instruction.program_id_index as usize; - if program_id_index >= accounts.len() { - return Ok(()); - } - let program_id = accounts[program_id_index]; - if !super::super::helpers::should_handle(protocols, event_type_filter, &program_id) { - return Ok(()); - } - - let is_cu_program = EventDispatcher::is_compute_budget_program(&program_id); - - let disc_len = match program_id { - RAYDIUM_AMM_V4_PROGRAM_ID | METEORA_DLMM_PROGRAM_ID => 1, - _ => 8, - }; - - // Non-ComputeBudget instructions need at least a discriminator. - if !is_cu_program && instruction.data.len() < disc_len { - return Ok(()); - } - // Build streamer metadata. - let timestamp = block_time.unwrap_or(Timestamp { seconds: 0, nanos: 0 }); - let block_time_ms = timestamp.seconds * 1000 + (timestamp.nanos as i64) / 1_000_000; - let metadata = EventMetadata::new( - signature, - slot, - timestamp.seconds, - block_time_ms, - Default::default(), // protocol will be set by dispatcher - Default::default(), // event_type will be set by dispatcher - program_id, - outer_index, - inner_index, - recv_us, - tx_index, - recent_blockhash.map(|s| s.to_string()), - ); - - if is_cu_program { - if let Some(event) = EventDispatcher::dispatch_compute_budget_instruction( - &instruction.data, - metadata.clone(), - ) { - if passes_event_type_filter(event_type_filter, &event) { - callback(event); - } - } - return Ok(()); - } - - // Match the parser protocol. - let protocol = match EventDispatcher::match_protocol_by_program_id(&program_id) { - Some(p) => p, - None => return Ok(()), - }; - - // Split discriminator and instruction payload. - let instruction_discriminator = &instruction.data[..disc_len]; - let instruction_data = &instruction.data[disc_len..]; - - // Build the account pubkey list for this instruction. - let account_pubkeys: Vec = instruction - .accounts - .iter() - .filter_map(|&idx| accounts.get(idx as usize).copied()) - .collect(); - - // Parse the instruction event. - let mut event = match EventDispatcher::dispatch_instruction( - protocol.clone(), - instruction_discriminator, - instruction_data, - &account_pubkeys, - metadata.clone(), - ) { - Some(e) => e, - None => return Ok(()), - }; - - // Find the next CPI log for merge. The gRPC hot path stays sequential to avoid - // thread::scope spawn/join overhead. - let mut inner_instruction_event: Option = None; - if let Some(inner_instructions_ref) = inner_instructions { - let raw = inner_index.unwrap_or(-1); - let current_inner_idx = raw.clamp(i32::MIN as i64, i32::MAX as i64) as i32; - - for (idx, inner_instruction) in inner_instructions_ref.instructions.iter().enumerate() { - if (idx as i32) <= current_inner_idx { - continue; - } - let inner_data = &inner_instruction.data; - if inner_data.len() < 16 { - continue; - } - let inner_discriminator = &inner_data[..16]; - let inner_instruction_data = &inner_data[16..]; - if let Some(inner_event) = EventDispatcher::dispatch_inner_instruction( - protocol.clone(), - inner_discriminator, - inner_instruction_data, - metadata.clone(), - ) { - inner_instruction_event = Some(inner_event); - break; - } - } - - if event.metadata().swap_data.is_none() { - if let Some(swap_data) = parse_swap_data_from_next_grpc_instructions( - &event, - inner_instructions_ref, - current_inner_idx, - accounts, - ) { - event.metadata_mut().set_swap_data(swap_data); - } - } - } - - // PumpFun MIGRATE emits instruction-only data when no CPI log exists. - - // Merge CPI details into the outer event. - if let Some(inner_instruction_event) = inner_instruction_event { - merge(&mut event, inner_instruction_event); - } - - // Stamp handling latency using the high-performance clock. - event.metadata_mut().handle_us = elapsed_micros_since(recv_us); - event = super::super::helpers::process_event(event, bot_wallet); - if passes_event_type_filter(event_type_filter, &event) { - callback(event); - } - - Ok(()) -} diff --git a/src/streaming/event_parser/core/event_parser/grpc_path/grpc_ix_mode.rs b/src/streaming/event_parser/core/event_parser/grpc_path/grpc_ix_mode.rs deleted file mode 100644 index 41c8400..0000000 --- a/src/streaming/event_parser/core/event_parser/grpc_path/grpc_ix_mode.rs +++ /dev/null @@ -1,9 +0,0 @@ -//! Top-level ix parsing strategy for the gRPC path. - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub(super) enum GrpcIxParseMode { - /// Parse all subscribed instructions, used when transaction meta is missing. - Full, - /// Parse only ComputeBudget locally; DEX events come from sol-parser-sdk. - ComputeBudgetOnly, -} diff --git a/src/streaming/event_parser/core/event_parser/grpc_path/grpc_transaction.rs b/src/streaming/event_parser/core/event_parser/grpc_path/grpc_transaction.rs deleted file mode 100644 index abf3a75..0000000 --- a/src/streaming/event_parser/core/event_parser/grpc_path/grpc_transaction.rs +++ /dev/null @@ -1,265 +0,0 @@ -//! Yellowstone transaction parsing: SDK-first DEX parsing plus optional local ix fallback. -//! -//! When `transaction.meta` is missing, the SDK low-latency parser cannot see logs / complete inner -//! instruction context and may return no events. In that case streamer uses the local full ix path. -//! -//! When meta exists, DEX events come from `sol-parser-sdk`; the local second pass is limited to -//! ComputeBudget events when the user asked for them. -use crate::streaming::event_parser::{ - common::{ - filter::{ - build_sdk_parse_event_filter, filter_includes_compute_budget_types, EventTypeFilter, - }, - high_performance_clock::elapsed_micros_since, - }, - core::dispatcher::EventDispatcher, - DexEvent, Protocol, -}; -use prost_types::Timestamp; -use sol_parser_sdk::grpc::parse_subscribe_update_transaction_low_latency; -use solana_sdk::{pubkey::Pubkey, signature::Signature}; -use std::sync::Arc; -use yellowstone_grpc_proto::geyser::{SubscribeUpdateTransaction, SubscribeUpdateTransactionInfo}; - -use super::grpc_ix_mode::GrpcIxParseMode; - -pub(crate) async fn parse_grpc_transaction( - protocols: &[Protocol], - event_type_filter: Option<&EventTypeFilter>, - mut grpc_tx: SubscribeUpdateTransactionInfo, - signature: Signature, - slot: Option, - block_time: Option, - recv_us: i64, - bot_wallet: Option, - tx_index: Option, - callback: Arc, -) -> anyhow::Result<()> { - let slot_u = slot.unwrap_or(0); - let block_us_micro = block_time.map(|t| t.seconds * 1_000_000 + t.nanos as i64 / 1_000); - - if grpc_tx.transaction.as_ref().and_then(|tx| tx.message.as_ref()).is_none() { - return Ok(()); - } - - let use_sol_parser_sdk = grpc_tx.meta.is_some(); - let skip_ix_pass = - use_sol_parser_sdk && !filter_includes_compute_budget_types(event_type_filter); - - if use_sol_parser_sdk { - let mut update = SubscribeUpdateTransaction { - slot: slot_u, - transaction: Some(grpc_tx), - ..Default::default() - }; - let sdk_parse_filter = build_sdk_parse_event_filter(event_type_filter); - let pb_events = parse_subscribe_update_transaction_low_latency( - &update, - recv_us, - block_us_micro, - sdk_parse_filter.as_ref(), - ); - let adapted = crate::streaming::parser_sdk_bridge::adapt_parser_events_list( - pb_events, - block_time.as_ref(), - recv_us, - protocols, - event_type_filter, - ); - for mut ev in adapted { - ev.metadata_mut().handle_us = elapsed_micros_since(recv_us); - ev = super::super::helpers::process_event(ev, bot_wallet); - callback(ev); - } - - if skip_ix_pass { - return Ok(()); - } - - let Some(tx) = update.transaction.take() else { - return Ok(()); - }; - grpc_tx = tx; - } - - let Some(transition) = grpc_tx.transaction.as_ref() else { - return Ok(()); - }; - let Some(message) = transition.message.as_ref() else { - return Ok(()); - }; - - let ix_mode = - if use_sol_parser_sdk { GrpcIxParseMode::ComputeBudgetOnly } else { GrpcIxParseMode::Full }; - - let accounts = build_account_keys(message, grpc_tx.meta.as_ref()); - let inner_instructions = - grpc_tx.meta.as_ref().map(|meta| meta.inner_instructions.as_slice()).unwrap_or_default(); - let recent_blockhash = if message.recent_blockhash.len() == 32 { - Some(solana_sdk::bs58::encode(&message.recent_blockhash).into_string()) - } else { - None - }; - - parse_instruction_events_from_grpc_transaction( - protocols, - event_type_filter, - ix_mode, - &message.instructions, - signature, - slot, - block_time, - recv_us, - &accounts, - inner_instructions, - bot_wallet, - tx_index, - recent_blockhash, - callback, - ) - .await?; - - Ok(()) -} - -fn build_account_keys( - message: &yellowstone_grpc_proto::prelude::Message, - meta: Option<&yellowstone_grpc_proto::prelude::TransactionStatusMeta>, -) -> Vec { - let loaded_len = meta - .map(|m| m.loaded_writable_addresses.len() + m.loaded_readonly_addresses.len()) - .unwrap_or(0); - let mut accounts = Vec::with_capacity(message.account_keys.len() + loaded_len); - - for account in &message.account_keys { - push_account_key(&mut accounts, account); - } - - if let Some(meta) = meta { - for account in - meta.loaded_writable_addresses.iter().chain(meta.loaded_readonly_addresses.iter()) - { - push_account_key(&mut accounts, account); - } - } - - accounts -} - -#[inline] -fn push_account_key(accounts: &mut Vec, account: &[u8]) { - let pubkey = if account.len() == 32 { - Pubkey::try_from(account).unwrap_or_default() - } else { - Pubkey::default() - }; - accounts.push(pubkey); -} - -pub(super) async fn parse_instruction_events_from_grpc_transaction( - protocols: &[Protocol], - event_type_filter: Option<&EventTypeFilter>, - ix_mode: GrpcIxParseMode, - compiled_instructions: &[yellowstone_grpc_proto::prelude::CompiledInstruction], - signature: Signature, - slot: Option, - block_time: Option, - recv_us: i64, - accounts: &[Pubkey], - inner_instructions: &[yellowstone_grpc_proto::solana::storage::confirmed_block::InnerInstructions], - bot_wallet: Option, - tx_index: Option, - recent_blockhash: Option, - callback: Arc, -) -> anyhow::Result<()> { - let mut accounts = accounts.to_vec(); - let has_program = match ix_mode { - GrpcIxParseMode::Full => accounts.iter().any(|account| { - super::super::helpers::should_handle(protocols, event_type_filter, account) - }), - GrpcIxParseMode::ComputeBudgetOnly => compiled_instructions.iter().any(|ix| { - accounts - .get(ix.program_id_index as usize) - .map(EventDispatcher::is_compute_budget_program) - .unwrap_or(false) - }), - }; - if has_program { - // Parse each instruction in order. - for (index, instruction) in compiled_instructions.iter().enumerate() { - if let Some(program_id) = accounts.get(instruction.program_id_index as usize) { - let program_id = *program_id; - let inner_instructions_ref = inner_instructions - .iter() - .find(|inner_instruction| inner_instruction.index == index as u32); - let max_idx = instruction.accounts.iter().max().unwrap_or(&0); - if *max_idx as usize >= accounts.len() { - accounts.resize(*max_idx as usize + 1, Pubkey::default()); - } - let handle_outer = match ix_mode { - GrpcIxParseMode::Full => super::super::helpers::should_handle( - protocols, - event_type_filter, - &program_id, - ), - GrpcIxParseMode::ComputeBudgetOnly => { - EventDispatcher::is_compute_budget_program(&program_id) - } - }; - if handle_outer { - super::grpc_instruction::parse_events_from_grpc_instruction( - protocols, - event_type_filter, - instruction, - &accounts, - signature, - slot.unwrap_or(0), - block_time, - recv_us, - index as i64, - None, - bot_wallet, - tx_index, - recent_blockhash.as_deref(), - inner_instructions_ref, - callback.clone(), - )?; - } - if ix_mode == GrpcIxParseMode::Full { - if let Some(inner_instructions) = inner_instructions_ref { - for (inner_index, inner_instruction) in - inner_instructions.instructions.iter().enumerate() - { - let inner_accounts = &inner_instruction.accounts; - let data = &inner_instruction.data; - let instruction = - yellowstone_grpc_proto::prelude::CompiledInstruction { - program_id_index: inner_instruction.program_id_index, - accounts: inner_accounts.to_vec(), - data: data.to_vec(), - }; - super::grpc_instruction::parse_events_from_grpc_instruction( - protocols, - event_type_filter, - &instruction, - &accounts, - signature, - slot.unwrap_or(0), - block_time, - recv_us, - inner_instructions.index as i64, - Some(inner_index as i64), - bot_wallet, - tx_index, - recent_blockhash.as_deref(), - Some(inner_instructions), - callback.clone(), - )?; - } - } - } - } - } - } - Ok(()) -} diff --git a/src/streaming/event_parser/core/event_parser/grpc_path/mod.rs b/src/streaming/event_parser/core/event_parser/grpc_path/mod.rs deleted file mode 100644 index ffa8d0d..0000000 --- a/src/streaming/event_parser/core/event_parser/grpc_path/mod.rs +++ /dev/null @@ -1,13 +0,0 @@ -//! Yellowstone gRPC path for `SubscribeUpdateTransactionInfo` and SDK aggregate parsing. -//! -//! | Module | Responsibility | -//! |--------|------| -//! | [`grpc_ix_mode`] | `GrpcIxParseMode` | -//! | [`grpc_transaction`] | whole subscription message and top-level ix loop | -//! | [`grpc_instruction`] | single Yellowstone `CompiledInstruction` | - -mod grpc_instruction; -mod grpc_ix_mode; -mod grpc_transaction; - -pub(super) use grpc_transaction::parse_grpc_transaction; diff --git a/src/streaming/event_parser/core/event_parser/helpers.rs b/src/streaming/event_parser/core/event_parser/helpers.rs index 8c85964..81ca97e 100644 --- a/src/streaming/event_parser/core/event_parser/helpers.rs +++ b/src/streaming/event_parser/core/event_parser/helpers.rs @@ -1,30 +1,13 @@ -//! Protocol filtering and event enrichment for PumpFun / PumpSwap / Bonk / bot flags. +//! Event enrichment for PumpFun / PumpSwap / Bonk / bot flags. use crate::streaming::event_parser::{ - common::filter::{filter_includes_compute_budget_types, EventTypeFilter}, - core::dispatcher::EventDispatcher, core::global_state::{ add_bonk_dev_address, add_dev_address, is_bonk_dev_address_in_signature, is_dev_address_in_signature, }, - DexEvent, Protocol, + DexEvent, }; use solana_sdk::pubkey::Pubkey; -pub(super) fn should_handle( - protocols: &[Protocol], - event_type_filter: Option<&EventTypeFilter>, - program_id: &Pubkey, -) -> bool { - if EventDispatcher::is_compute_budget_program(program_id) { - return filter_includes_compute_budget_types(event_type_filter); - } - if let Some(protocol) = EventDispatcher::match_protocol_by_program_id(program_id) { - protocols.contains(&protocol) - } else { - false - } -} - // ================================================================================================ // Event Post-Processing // ================================================================================================ diff --git a/src/streaming/event_parser/core/event_parser/mod.rs b/src/streaming/event_parser/core/event_parser/mod.rs index 23fcb1f..60500ff 100644 --- a/src/streaming/event_parser/core/event_parser/mod.rs +++ b/src/streaming/event_parser/core/event_parser/mod.rs @@ -1,13 +1,9 @@ -//! Transaction parser entry point with separate gRPC and standard ix paths. +//! Transaction parser entry point. //! -//! | Module | Path | -//! |--------|------| -//! | [`grpc_path`] | Yellowstone gRPC | -//! | [`compiled_path`] | standard transaction / RPC replay | -//! | [`helpers`] | `should_handle`、`process_event` | +//! gRPC and ShredStream parsing are delegated to `sol-parser-sdk`; this layer +//! only adapts SDK events into streamer event structs and applies streamer +//! metadata enrichment. -mod compiled_path; -mod grpc_path; pub(crate) mod helpers; pub struct EventParser; @@ -16,7 +12,7 @@ impl EventParser { pub async fn parse_grpc_transaction( protocols: &[crate::streaming::event_parser::Protocol], event_type_filter: Option<&crate::streaming::event_parser::common::filter::EventTypeFilter>, - grpc_tx: yellowstone_grpc_proto::geyser::SubscribeUpdateTransactionInfo, + mut grpc_tx: yellowstone_grpc_proto::geyser::SubscribeUpdateTransactionInfo, signature: solana_sdk::signature::Signature, slot: Option, block_time: Option, @@ -25,19 +21,46 @@ impl EventParser { tx_index: Option, callback: std::sync::Arc, ) -> anyhow::Result<()> { - grpc_path::parse_grpc_transaction( - protocols, - event_type_filter, - grpc_tx, - signature, - slot, - block_time, + if grpc_tx.signature.is_empty() { + grpc_tx.signature = signature.as_ref().to_vec(); + } + if grpc_tx.index == 0 { + grpc_tx.index = tx_index.unwrap_or(0); + } + + let block_us = block_time.map(|t| t.seconds * 1_000_000 + t.nanos as i64 / 1_000); + let update = yellowstone_grpc_proto::geyser::SubscribeUpdateTransaction { + slot: slot.unwrap_or(0), + transaction: Some(grpc_tx), + ..Default::default() + }; + let sdk_parse_filter = + crate::streaming::event_parser::common::filter::build_sdk_parse_event_filter( + event_type_filter, + ); + let sdk_events = sol_parser_sdk::grpc::parse_subscribe_update_transaction_low_latency( + &update, recv_us, - bot_wallet, - tx_index, - callback, - ) - .await + block_us, + sdk_parse_filter.as_ref(), + ); + for sdk_event in sdk_events { + if let Some(mut event) = crate::streaming::parser_sdk_bridge::adapt_parser_event( + sdk_event, + block_time.as_ref(), + recv_us, + protocols, + event_type_filter, + ) { + event.metadata_mut().handle_us = + crate::streaming::event_parser::common::high_performance_clock::elapsed_micros_since( + recv_us, + ); + event = helpers::process_event(event, bot_wallet); + callback(event); + } + } + Ok(()) } #[allow(clippy::too_many_arguments)] @@ -47,28 +70,45 @@ impl EventParser { transaction: &solana_sdk::transaction::VersionedTransaction, signature: solana_sdk::signature::Signature, slot: Option, - block_time: Option, + _block_time: Option, recv_us: i64, - accounts: &[solana_sdk::pubkey::Pubkey], - inner_instructions: &[solana_transaction_status::InnerInstructions], + _accounts: &[solana_sdk::pubkey::Pubkey], + _inner_instructions: &[solana_transaction_status::InnerInstructions], bot_wallet: Option, tx_index: Option, callback: std::sync::Arc, ) -> anyhow::Result<()> { - compiled_path::parse_instruction_events_from_versioned_transaction( - protocols, - event_type_filter, + let sdk_parse_filter = + crate::streaming::event_parser::common::filter::build_sdk_shred_parse_event_filter( + protocols, + event_type_filter, + ); + let mut sdk_events = Vec::with_capacity(4); + sol_parser_sdk::shredstream::parse_transaction_dex_events_with_filter( transaction, signature, - slot, - block_time, + slot.unwrap_or(0), + tx_index.unwrap_or(0), recv_us, - accounts, - inner_instructions, - bot_wallet, - tx_index, - callback, - ) - .await + sdk_parse_filter.as_ref(), + &mut sdk_events, + ); + for sdk_event in sdk_events { + if let Some(mut event) = crate::streaming::parser_sdk_bridge::adapt_parser_event( + sdk_event, + None, + recv_us, + protocols, + event_type_filter, + ) { + event.metadata_mut().handle_us = + crate::streaming::event_parser::common::high_performance_clock::elapsed_micros_since( + recv_us, + ); + event = helpers::process_event(event, bot_wallet); + callback(event); + } + } + Ok(()) } } diff --git a/src/streaming/event_parser/mod.rs b/src/streaming/event_parser/mod.rs index b5e7ceb..32f25a0 100755 --- a/src/streaming/event_parser/mod.rs +++ b/src/streaming/event_parser/mod.rs @@ -1,7 +1,8 @@ //! Solana DEX 交易解析:**对外类型** [`DexEvent`]、[`Protocol`]; -//! **`common`**(元数据/过滤器)、**`core`**(调度、合并、gRPC 解析入口)、**`protocols`**(按协议的 parser/events)。 +//! **`common`**(元数据/过滤器)、**`core`**(SDK 调度与入口)、**`protocols`**(按协议的事件类型与兼容 facade)。 //! -//! 与 **sol-parser-sdk** 对照:`protocols/*/parser` ≈ sdk `instr/*`,`parser_sdk_bridge` ≈ sdk 事件枚举与各实现的胶水层。 +//! gRPC/ShredStream 解析统一委托给 **sol-parser-sdk**,本 crate 只做订阅、过滤映射、 +//! SDK 事件到 streamer 事件的适配,以及兼容旧公开路径的轻量转发。 //! //! [`DexEvent`]: crate::streaming::event_parser::DexEvent //! [`Protocol`]: crate::streaming::event_parser::Protocol diff --git a/src/streaming/event_parser/protocols/bonk/events.rs b/src/streaming/event_parser/protocols/bonk/events.rs index 8172a59..9ab98e1 100755 --- a/src/streaming/event_parser/protocols/bonk/events.rs +++ b/src/streaming/event_parser/protocols/bonk/events.rs @@ -73,13 +73,6 @@ pub struct BonkTradeEvent { pub const BONK_TRADE_EVENT_LOG_SIZE: usize = 32 + 8 * 13 + 1 + 1 + 1; -pub fn bonk_trade_event_log_decode(data: &[u8]) -> Option { - if data.len() < BONK_TRADE_EVENT_LOG_SIZE { - return None; - } - borsh::from_slice::(&data[..BONK_TRADE_EVENT_LOG_SIZE]).ok() -} - /// Create pool event #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)] pub struct BonkPoolCreateEvent { @@ -110,13 +103,6 @@ pub struct BonkPoolCreateEvent { pub const BONK_POOL_CREATE_EVENT_LOG_SIZE: usize = 256; -pub fn bonk_pool_create_event_log_decode(data: &[u8]) -> Option { - if data.len() < BONK_POOL_CREATE_EVENT_LOG_SIZE { - return None; - } - borsh::from_slice::(&data[..BONK_POOL_CREATE_EVENT_LOG_SIZE]).ok() -} - /// Create pool event #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)] pub struct BonkMigrateToAmmEvent { diff --git a/src/streaming/event_parser/protocols/bonk/parser.rs b/src/streaming/event_parser/protocols/bonk/parser.rs old mode 100755 new mode 100644 index 9686f61..f98a6cd --- a/src/streaming/event_parser/protocols/bonk/parser.rs +++ b/src/streaming/event_parser/protocols/bonk/parser.rs @@ -1,605 +1,31 @@ +use crate::streaming::event_parser::{ + common::EventMetadata, core::EventDispatcher, DexEvent, Protocol, +}; use solana_sdk::pubkey::Pubkey; -use crate::streaming::event_parser::{ - common::{utils::*, EventMetadata, EventType}, - protocols::bonk::{ - bonk_pool_create_event_log_decode, bonk_trade_event_log_decode, discriminators, AmmFeeOn, - BonkMigrateToAmmEvent, BonkMigrateToCpswapEvent, BonkPoolCreateEvent, BonkTradeEvent, - ConstantCurve, CurveParams, FixedCurve, LinearCurve, MintParams, TradeDirection, - VestingParams, - }, - DexEvent, -}; +pub use sol_parser_sdk::instr::program_ids::BONK_PROGRAM_ID; -/// Bonk Program ID -pub const BONK_PROGRAM_ID: Pubkey = - solana_sdk::pubkey!("LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj"); - -/// 解析 Bonk instruction data -/// -/// 根据判别器路由到具体的 instruction 解析函数 pub fn parse_bonk_instruction_data( discriminator: &[u8], data: &[u8], accounts: &[Pubkey], metadata: EventMetadata, ) -> Option { - match discriminator { - discriminators::BUY_EXACT_IN => parse_buy_exact_in_instruction(data, accounts, metadata), - discriminators::BUY_EXACT_OUT => parse_buy_exact_out_instruction(data, accounts, metadata), - discriminators::SELL_EXACT_IN => parse_sell_exact_in_instruction(data, accounts, metadata), - discriminators::SELL_EXACT_OUT => { - parse_sell_exact_out_instruction(data, accounts, metadata) - } - discriminators::INITIALIZE => parse_initialize_instruction(data, accounts, metadata), - discriminators::INITIALIZE_V2 => parse_initialize_v2_instruction(data, accounts, metadata), - discriminators::INITIALIZE_WITH_TOKEN_2022 => { - parse_initialize_with_token_2022_instruction(data, accounts, metadata) - } - discriminators::MIGRATE_TO_AMM => { - parse_migrate_to_amm_instruction(data, accounts, metadata) - } - discriminators::MIGRATE_TO_CP_SWAP => { - parse_migrate_to_cpswap_instruction(data, accounts, metadata) - } - _ => None, - } + EventDispatcher::dispatch_instruction(Protocol::Bonk, discriminator, data, accounts, metadata) } -/// 解析 Bonk inner instruction data -/// -/// 根据判别器路由到具体的 inner instruction 解析函数 pub fn parse_bonk_inner_instruction_data( discriminator: &[u8], data: &[u8], metadata: EventMetadata, ) -> Option { - match discriminator { - discriminators::TRADE_EVENT => parse_trade_inner_instruction(data, metadata), - discriminators::POOL_CREATE_EVENT => parse_pool_create_inner_instruction(data, metadata), - _ => None, - } + EventDispatcher::dispatch_inner_instruction(Protocol::Bonk, discriminator, data, metadata) } -/// 解析 Bonk 账户数据 -/// -/// 根据判别器路由到具体的账户解析函数 pub fn parse_bonk_account_data( discriminator: &[u8], account: &crate::streaming::grpc::AccountPretty, - metadata: crate::streaming::event_parser::common::EventMetadata, -) -> Option { - match discriminator { - discriminators::POOL_STATE_ACCOUNT => { - crate::streaming::event_parser::protocols::bonk::types::pool_state_parser( - account, metadata, - ) - } - discriminators::GLOBAL_CONFIG_ACCOUNT => { - crate::streaming::event_parser::protocols::bonk::types::global_config_parser( - account, metadata, - ) - } - discriminators::PLATFORM_CONFIG_ACCOUNT => { - crate::streaming::event_parser::protocols::bonk::types::platform_config_parser( - account, metadata, - ) - } - _ => None, - } -} - -/// Parse pool creation event -fn parse_pool_create_inner_instruction(data: &[u8], metadata: EventMetadata) -> Option { - // Note: event_type will be set by the instruction parser, not here - // Because different initialize instructions have different event types - if let Some(event) = bonk_pool_create_event_log_decode(data) { - Some(DexEvent::BonkPoolCreateEvent(BonkPoolCreateEvent { metadata, ..event })) - } else { - None - } -} - -/// Parse trade event -fn parse_trade_inner_instruction(data: &[u8], metadata: EventMetadata) -> Option { - if let Some(event) = bonk_trade_event_log_decode(data) { - if metadata.event_type == EventType::BonkBuyExactIn - || metadata.event_type == EventType::BonkBuyExactOut - { - if event.trade_direction != TradeDirection::Buy { - return None; - } - } else if (metadata.event_type == EventType::BonkSellExactIn - || metadata.event_type == EventType::BonkSellExactOut) - && event.trade_direction != TradeDirection::Sell - { - return None; - } - Some(DexEvent::BonkTradeEvent(BonkTradeEvent { metadata, ..event })) - } else { - None - } -} - -/// Parse buy instruction event -fn parse_buy_exact_in_instruction( - data: &[u8], - accounts: &[Pubkey], - mut metadata: EventMetadata, + metadata: EventMetadata, ) -> Option { - metadata.event_type = EventType::BonkBuyExactIn; - - if data.len() < 16 || accounts.len() < 18 { - return None; - } - - let amount_in = read_u64_le(data, 0)?; - let minimum_amount_out = read_u64_le(data, 8)?; - let share_fee_rate = read_u64_le(data, 16)?; - - Some(DexEvent::BonkTradeEvent(BonkTradeEvent { - metadata, - amount_in, - minimum_amount_out, - share_fee_rate, - payer: accounts[0], - global_config: accounts[2], - platform_config: accounts[3], - pool_state: accounts[4], - user_base_token: accounts[5], - user_quote_token: accounts[6], - base_vault: accounts[7], - quote_vault: accounts[8], - base_token_mint: accounts[9], - quote_token_mint: accounts[10], - base_token_program: accounts[11], - quote_token_program: accounts[12], - system_program: accounts[15], - platform_associated_account: accounts[16], - creator_associated_account: accounts[17], - trade_direction: TradeDirection::Buy, - ..Default::default() - })) -} - -fn parse_buy_exact_out_instruction( - data: &[u8], - accounts: &[Pubkey], - mut metadata: EventMetadata, -) -> Option { - metadata.event_type = EventType::BonkBuyExactOut; - - if data.len() < 16 || accounts.len() < 18 { - return None; - } - - let amount_out = read_u64_le(data, 0)?; - let maximum_amount_in = read_u64_le(data, 8)?; - let share_fee_rate = read_u64_le(data, 16)?; - - Some(DexEvent::BonkTradeEvent(BonkTradeEvent { - metadata, - amount_out, - maximum_amount_in, - share_fee_rate, - payer: accounts[0], - global_config: accounts[2], - platform_config: accounts[3], - pool_state: accounts[4], - user_base_token: accounts[5], - user_quote_token: accounts[6], - base_vault: accounts[7], - quote_vault: accounts[8], - base_token_mint: accounts[9], - quote_token_mint: accounts[10], - base_token_program: accounts[11], - quote_token_program: accounts[12], - system_program: accounts[15], - platform_associated_account: accounts[16], - creator_associated_account: accounts[17], - trade_direction: TradeDirection::Buy, - ..Default::default() - })) -} - -fn parse_sell_exact_in_instruction( - data: &[u8], - accounts: &[Pubkey], - mut metadata: EventMetadata, -) -> Option { - metadata.event_type = EventType::BonkSellExactIn; - - if data.len() < 16 || accounts.len() < 18 { - return None; - } - - let amount_in = read_u64_le(data, 0)?; - let minimum_amount_out = read_u64_le(data, 8)?; - let share_fee_rate = read_u64_le(data, 16)?; - - Some(DexEvent::BonkTradeEvent(BonkTradeEvent { - metadata, - amount_in, - minimum_amount_out, - share_fee_rate, - payer: accounts[0], - global_config: accounts[2], - platform_config: accounts[3], - pool_state: accounts[4], - user_base_token: accounts[5], - user_quote_token: accounts[6], - base_vault: accounts[7], - quote_vault: accounts[8], - base_token_mint: accounts[9], - quote_token_mint: accounts[10], - base_token_program: accounts[11], - quote_token_program: accounts[12], - system_program: accounts[15], - platform_associated_account: accounts[16], - creator_associated_account: accounts[17], - trade_direction: TradeDirection::Sell, - ..Default::default() - })) -} - -fn parse_sell_exact_out_instruction( - data: &[u8], - accounts: &[Pubkey], - mut metadata: EventMetadata, -) -> Option { - metadata.event_type = EventType::BonkSellExactOut; - - if data.len() < 16 || accounts.len() < 18 { - return None; - } - - let amount_out = read_u64_le(data, 0)?; - let maximum_amount_in = read_u64_le(data, 8)?; - let share_fee_rate = read_u64_le(data, 16)?; - - Some(DexEvent::BonkTradeEvent(BonkTradeEvent { - metadata, - amount_out, - maximum_amount_in, - share_fee_rate, - payer: accounts[0], - global_config: accounts[2], - platform_config: accounts[3], - pool_state: accounts[4], - user_base_token: accounts[5], - user_quote_token: accounts[6], - base_vault: accounts[7], - quote_vault: accounts[8], - base_token_mint: accounts[9], - quote_token_mint: accounts[10], - base_token_program: accounts[11], - quote_token_program: accounts[12], - system_program: accounts[15], - platform_associated_account: accounts[16], - creator_associated_account: accounts[17], - trade_direction: TradeDirection::Sell, - ..Default::default() - })) -} - -/// Parse initialize event -fn parse_initialize_instruction( - data: &[u8], - accounts: &[Pubkey], - mut metadata: EventMetadata, -) -> Option { - metadata.event_type = EventType::BonkInitialize; - - if data.len() < 24 { - return None; - } - - let mut offset = 0; - let base_mint_param = parse_mint_params(data, &mut offset)?; - let curve_param = parse_curve_params(data, &mut offset)?; - let vesting_param = parse_vesting_params(data, &mut offset)?; - - Some(DexEvent::BonkPoolCreateEvent(BonkPoolCreateEvent { - metadata, - payer: accounts[0], - creator: accounts[1], - global_config: accounts[2], - platform_config: accounts[3], - pool_state: accounts[5], - base_mint: accounts[6], - quote_mint: accounts[7], - base_vault: accounts[8], - quote_vault: accounts[9], - base_mint_param, - curve_param, - vesting_param, - ..Default::default() - })) -} - -/// Parse initialize event -fn parse_initialize_v2_instruction( - data: &[u8], - accounts: &[Pubkey], - mut metadata: EventMetadata, -) -> Option { - metadata.event_type = EventType::BonkInitializeV2; - - if data.len() < 24 { - return None; - } - - let mut offset = 0; - let base_mint_param = parse_mint_params(data, &mut offset)?; - let curve_param = parse_curve_params(data, &mut offset)?; - let vesting_param = parse_vesting_params(data, &mut offset)?; - let amm_fee_on = data[offset]; - - Some(DexEvent::BonkPoolCreateEvent(BonkPoolCreateEvent { - metadata, - payer: accounts[0], - creator: accounts[1], - global_config: accounts[2], - platform_config: accounts[3], - pool_state: accounts[5], - base_mint: accounts[6], - quote_mint: accounts[7], - base_vault: accounts[8], - quote_vault: accounts[9], - base_mint_param, - curve_param, - vesting_param, - amm_fee_on: if amm_fee_on == 0 { - Some(AmmFeeOn::QuoteToken) - } else { - Some(AmmFeeOn::BothToken) - }, - ..Default::default() - })) -} - -/// Parse initialize event -fn parse_initialize_with_token_2022_instruction( - data: &[u8], - accounts: &[Pubkey], - mut metadata: EventMetadata, -) -> Option { - metadata.event_type = EventType::BonkInitializeWithToken2022; - - if data.len() < 24 { - return None; - } - - let mut offset = 0; - let base_mint_param = parse_mint_params(data, &mut offset)?; - let curve_param = parse_curve_params(data, &mut offset)?; - let vesting_param = parse_vesting_params(data, &mut offset)?; - let amm_fee_on = data[offset]; - - Some(DexEvent::BonkPoolCreateEvent(BonkPoolCreateEvent { - metadata, - payer: accounts[0], - creator: accounts[1], - global_config: accounts[2], - platform_config: accounts[3], - pool_state: accounts[5], - base_mint: accounts[6], - quote_mint: accounts[7], - base_vault: accounts[8], - quote_vault: accounts[9], - base_mint_param, - curve_param, - vesting_param, - amm_fee_on: if amm_fee_on == 0 { - Some(AmmFeeOn::QuoteToken) - } else { - Some(AmmFeeOn::BothToken) - }, - ..Default::default() - })) -} - -/// Parse MintParams structure -fn parse_mint_params(data: &[u8], offset: &mut usize) -> Option { - // Read decimals (1 byte) - let decimals = read_u8(data, *offset)?; - *offset += 1; - - // Read name string length and content - let name_len = read_u32_le(data, *offset)? as usize; - *offset += 4; - if data.len() < *offset + name_len { - return None; - } - let name = String::from_utf8(data[*offset..*offset + name_len].to_vec()).ok()?; - *offset += name_len; - - // Read symbol string length and content - let symbol_len = read_u32_le(data, *offset)? as usize; - *offset += 4; - if data.len() < *offset + symbol_len { - return None; - } - let symbol = String::from_utf8(data[*offset..*offset + symbol_len].to_vec()).ok()?; - *offset += symbol_len; - - // Read uri string length and content - let uri_len = read_u32_le(data, *offset)? as usize; - *offset += 4; - if data.len() < *offset + uri_len { - return None; - } - let uri = String::from_utf8(data[*offset..*offset + uri_len].to_vec()).ok()?; - *offset += uri_len; - - Some(MintParams { decimals, name, symbol, uri }) -} - -/// Parse CurveParams structure -fn parse_curve_params(data: &[u8], offset: &mut usize) -> Option { - // Read curve type identifier (1 byte) - let curve_type = read_u8(data, *offset)?; - *offset += 1; - - match curve_type { - 0 => { - // Constant curve - let supply = read_u64_le(data, *offset)?; - *offset += 8; - let total_base_sell = read_u64_le(data, *offset)?; - *offset += 8; - let total_quote_fund_raising = read_u64_le(data, *offset)?; - *offset += 8; - let migrate_type = read_u8(data, *offset)?; - *offset += 1; - - Some(CurveParams::Constant { - data: ConstantCurve { - supply, - total_base_sell, - total_quote_fund_raising, - migrate_type, - }, - }) - } - 1 => { - // Fixed curve - let supply = read_u64_le(data, *offset)?; - *offset += 8; - let total_quote_fund_raising = read_u64_le(data, *offset)?; - *offset += 8; - let migrate_type = read_u8(data, *offset)?; - *offset += 1; - - Some(CurveParams::Fixed { - data: FixedCurve { supply, total_quote_fund_raising, migrate_type }, - }) - } - 2 => { - // Linear curve - let supply = read_u64_le(data, *offset)?; - *offset += 8; - let total_quote_fund_raising = read_u64_le(data, *offset)?; - *offset += 8; - let migrate_type = read_u8(data, *offset)?; - *offset += 1; - - Some(CurveParams::Linear { - data: LinearCurve { supply, total_quote_fund_raising, migrate_type }, - }) - } - _ => None, - } -} - -/// Parse VestingParams structure -fn parse_vesting_params(data: &[u8], offset: &mut usize) -> Option { - let total_locked_amount = read_u64_le(data, *offset)?; - *offset += 8; - let cliff_period = read_u64_le(data, *offset)?; - *offset += 8; - let unlock_period = read_u64_le(data, *offset)?; - *offset += 8; - - Some(VestingParams { total_locked_amount, cliff_period, unlock_period }) -} - -/// Parse migrate to AMM event -fn parse_migrate_to_amm_instruction( - data: &[u8], - accounts: &[Pubkey], - mut metadata: EventMetadata, -) -> Option { - metadata.event_type = EventType::BonkMigrateToAmm; - - if data.len() < 16 { - return None; - } - - let base_lot_size = u64::from_le_bytes(data[0..8].try_into().unwrap()); - let quote_lot_size = u64::from_le_bytes(data[8..16].try_into().unwrap()); - let market_vault_signer_nonce = data[16]; - - Some(DexEvent::BonkMigrateToAmmEvent(BonkMigrateToAmmEvent { - metadata, - base_lot_size, - quote_lot_size, - market_vault_signer_nonce, - payer: accounts[0], - base_mint: accounts[1], - quote_mint: accounts[2], - openbook_program: accounts[3], - market: accounts[4], - request_queue: accounts[5], - event_queue: accounts[6], - bids: accounts[7], - asks: accounts[8], - market_vault_signer: accounts[9], - market_base_vault: accounts[10], - market_quote_vault: accounts[11], - amm_program: accounts[12], - amm_pool: accounts[13], - amm_authority: accounts[14], - amm_open_orders: accounts[15], - amm_lp_mint: accounts[16], - amm_base_vault: accounts[17], - amm_quote_vault: accounts[18], - amm_target_orders: accounts[19], - amm_config: accounts[20], - amm_create_fee_destination: accounts[21], - authority: accounts[22], - pool_state: accounts[23], - global_config: accounts[24], - base_vault: accounts[25], - quote_vault: accounts[26], - pool_lp_token: accounts[27], - spl_token_program: accounts[28], - associated_token_program: accounts[29], - system_program: accounts[30], - rent_program: accounts[31], - ..Default::default() - })) -} - -/// Parse migrate to CP Swap event -fn parse_migrate_to_cpswap_instruction( - _data: &[u8], - accounts: &[Pubkey], - mut metadata: EventMetadata, -) -> Option { - metadata.event_type = EventType::BonkMigrateToCpswap; - - Some(DexEvent::BonkMigrateToCpswapEvent(BonkMigrateToCpswapEvent { - metadata, - payer: accounts[0], - base_mint: accounts[1], - quote_mint: accounts[2], - platform_config: accounts[3], - cpswap_program: accounts[4], - cpswap_pool: accounts[5], - cpswap_authority: accounts[6], - cpswap_lp_mint: accounts[7], - cpswap_base_vault: accounts[8], - cpswap_quote_vault: accounts[9], - cpswap_config: accounts[10], - cpswap_create_pool_fee: accounts[11], - cpswap_observation: accounts[12], - lock_program: accounts[13], - lock_authority: accounts[14], - lock_lp_vault: accounts[15], - authority: accounts[16], - pool_state: accounts[17], - global_config: accounts[18], - base_vault: accounts[19], - quote_vault: accounts[20], - pool_lp_token: accounts[21], - base_token_program: accounts[22], - quote_token_program: accounts[23], - associated_token_program: accounts[24], - system_program: accounts[25], - rent_program: accounts[26], - metadata_program: accounts[27], - remaining_accounts: accounts[28..].to_vec(), - ..Default::default() - })) + EventDispatcher::dispatch_account(Protocol::Bonk, discriminator, account, metadata) } diff --git a/src/streaming/event_parser/protocols/bonk/types.rs b/src/streaming/event_parser/protocols/bonk/types.rs index 586314a..5b5b4b4 100755 --- a/src/streaming/event_parser/protocols/bonk/types.rs +++ b/src/streaming/event_parser/protocols/bonk/types.rs @@ -2,17 +2,6 @@ use borsh::BorshDeserialize; use serde::{Deserialize, Serialize}; use solana_sdk::pubkey::Pubkey; -use crate::streaming::{ - event_parser::{ - common::{EventMetadata, EventType}, - protocols::bonk::{ - BonkGlobalConfigAccountEvent, BonkPlatformConfigAccountEvent, BonkPoolStateAccountEvent, - }, - DexEvent, - }, - grpc::AccountPretty, -}; - #[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)] pub enum TradeDirection { #[default] @@ -182,34 +171,6 @@ impl Default for PoolState { } pub const POOL_STATE_SIZE: usize = 8 + 1 * 5 + 8 * 10 + 32 * 7 + 8 * 8 + 8 * 5 + 1 + 1 + 8 + 54; - -pub fn pool_state_decode(data: &[u8]) -> Option { - if data.len() < POOL_STATE_SIZE { - return None; - } - borsh::from_slice::(&data[..POOL_STATE_SIZE]).ok() -} - -pub fn pool_state_parser(account: &AccountPretty, mut metadata: EventMetadata) -> Option { - metadata.event_type = EventType::AccountBonkPoolState; - - if account.data.len() < POOL_STATE_SIZE + 8 { - return None; - } - if let Some(pool_state) = pool_state_decode(&account.data[8..POOL_STATE_SIZE + 8]) { - Some(DexEvent::BonkPoolStateAccountEvent(BonkPoolStateAccountEvent { - metadata, - pubkey: account.pubkey, - executable: account.executable, - lamports: account.lamports, - owner: account.owner, - rent_epoch: account.rent_epoch, - pool_state, - })) - } else { - None - } -} #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)] pub struct GlobalConfig { pub epoch: u64, @@ -233,37 +194,6 @@ pub struct GlobalConfig { pub const GLOBAL_CONFIG_SIZE: usize = 8 + 1 + 2 + 8 * 8 + 32 * 5 + 8 * 16; -pub fn global_config_decode(data: &[u8]) -> Option { - if data.len() < GLOBAL_CONFIG_SIZE { - return None; - } - borsh::from_slice::(&data[..GLOBAL_CONFIG_SIZE]).ok() -} - -pub fn global_config_parser( - account: &AccountPretty, - mut metadata: EventMetadata, -) -> Option { - metadata.event_type = EventType::AccountBonkGlobalConfig; - - if account.data.len() < GLOBAL_CONFIG_SIZE + 8 { - return None; - } - if let Some(global_config) = global_config_decode(&account.data[8..GLOBAL_CONFIG_SIZE + 8]) { - Some(DexEvent::BonkGlobalConfigAccountEvent(BonkGlobalConfigAccountEvent { - metadata, - pubkey: account.pubkey, - executable: account.executable, - lamports: account.lamports, - owner: account.owner, - rent_epoch: account.rent_epoch, - global_config, - })) - } else { - None - } -} - #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)] pub struct BondingCurveParam { pub migrate_type: u8, @@ -351,36 +281,3 @@ impl Default for PlatformConfig { pub const PLATFORM_CONFIG_SIZE: usize = 8 + 32 * 2 + 8 * 4 + 64 + 256 + 256 + 32 + 8 + 32 + 32 + 8 + 32 + 108; - -pub fn platform_config_decode(data: &[u8]) -> Option { - if data.len() < PLATFORM_CONFIG_SIZE { - return None; - } - borsh::from_slice::(&data[..PLATFORM_CONFIG_SIZE]).ok() -} - -pub fn platform_config_parser( - account: &AccountPretty, - mut metadata: EventMetadata, -) -> Option { - metadata.event_type = EventType::AccountBonkPlatformConfig; - - if account.data.len() < PLATFORM_CONFIG_SIZE + 8 { - return None; - } - if let Some(platform_config) = - platform_config_decode(&account.data[8..PLATFORM_CONFIG_SIZE + 8]) - { - Some(DexEvent::BonkPlatformConfigAccountEvent(BonkPlatformConfigAccountEvent { - metadata, - pubkey: account.pubkey, - executable: account.executable, - lamports: account.lamports, - owner: account.owner, - rent_epoch: account.rent_epoch, - platform_config, - })) - } else { - None - } -} diff --git a/src/streaming/event_parser/protocols/meteora_damm_v2/events.rs b/src/streaming/event_parser/protocols/meteora_damm_v2/events.rs index ac20fe8..ee6b0b8 100644 --- a/src/streaming/event_parser/protocols/meteora_damm_v2/events.rs +++ b/src/streaming/event_parser/protocols/meteora_damm_v2/events.rs @@ -463,17 +463,3 @@ pub mod discriminators { /// Decode swap event from CPI log pub const METEORA_DAMM_V2_SWAP_EVENT_LOG_SIZE: usize = 180; -pub fn meteora_damm_v2_swap_event_decode(data: &[u8]) -> Option { - if data.len() < METEORA_DAMM_V2_SWAP_EVENT_LOG_SIZE { - return None; - } - borsh::from_slice::(&data[..METEORA_DAMM_V2_SWAP_EVENT_LOG_SIZE]).ok() -} - -/// Decode initialize pool event from CPI log -/// Note: discriminator (16 bytes) is already removed by the caller -pub fn meteora_damm_v2_initialize_pool_event_decode( - data: &[u8], -) -> Option { - borsh::from_slice::(&data).ok() -} diff --git a/src/streaming/event_parser/protocols/meteora_damm_v2/parser.rs b/src/streaming/event_parser/protocols/meteora_damm_v2/parser.rs index ee71399..f2c8b4e 100644 --- a/src/streaming/event_parser/protocols/meteora_damm_v2/parser.rs +++ b/src/streaming/event_parser/protocols/meteora_damm_v2/parser.rs @@ -1,444 +1,42 @@ use crate::streaming::event_parser::{ - common::{EventMetadata, EventType}, - protocols::meteora_damm_v2::{ - discriminators, meteora_damm_v2_initialize_pool_event_decode, - meteora_damm_v2_swap_event_decode, MeteoraDammV2InitializeCustomizablePoolEvent, - MeteoraDammV2InitializePoolEvent, MeteoraDammV2InitializePoolWithDynamicConfigEvent, - MeteoraDammV2Swap2Event, MeteoraDammV2SwapEvent, - }, - DexEvent, + common::EventMetadata, core::EventDispatcher, DexEvent, Protocol, }; use solana_sdk::pubkey::Pubkey; -/// Meteora DAMM v2 程序ID -pub const METEORA_DAMM_V2_PROGRAM_ID: Pubkey = - solana_sdk::pubkey!("cpamdpZCGKUy5JxQXB4dcpGPiikHawvSWAd6mEn1sGG"); +pub use sol_parser_sdk::instr::program_ids::METEORA_DAMM_V2_PROGRAM_ID; -/// 解析 Meteora DAMM v2 instruction data -/// -/// 根据判别器路由到具体的 instruction 解析函数 pub fn parse_meteora_damm_v2_instruction_data( discriminator: &[u8], data: &[u8], accounts: &[Pubkey], metadata: EventMetadata, ) -> Option { - match discriminator { - discriminators::SWAP_IX => parse_swap_instruction(data, accounts, metadata), - discriminators::SWAP2_IX => parse_swap2_instruction(data, accounts, metadata), - discriminators::INITIALIZE_POOL_IX => { - parse_initialize_pool_instruction(data, accounts, metadata) - } - discriminators::INITIALIZE_CUSTOMIZABLE_POOL_IX => { - parse_initialize_customizable_pool_instruction(data, accounts, metadata) - } - discriminators::INITIALIZE_POOL_WITH_DYNAMIC_CONFIG_IX => { - parse_initialize_pool_with_dynamic_config_instruction(data, accounts, metadata) - } - _ => None, - } + EventDispatcher::dispatch_instruction( + Protocol::MeteoraDammV2, + discriminator, + data, + accounts, + metadata, + ) } -/// 解析 Meteora DAMM v2 inner instruction data (CPI events) -/// -/// 根据判别器路由到具体的 inner instruction 解析函数 pub fn parse_meteora_damm_v2_inner_instruction_data( discriminator: &[u8], data: &[u8], metadata: EventMetadata, ) -> Option { - match discriminator { - discriminators::SWAP_EVENT => parse_swap_inner_instruction(data, metadata), - discriminators::INITIALIZE_POOL_EVENT => { - parse_initialize_pool_inner_instruction(data, metadata) - } - _ => None, - } -} - -/// 解析 swap 指令 -fn parse_swap_instruction( - data: &[u8], - accounts: &[Pubkey], - mut metadata: EventMetadata, -) -> Option { - metadata.event_type = EventType::MeteoraDammV2Swap; - - if data.len() < 16 || accounts.len() < 14 { - return None; - } - - // 跳过 discriminator (8 bytes) - let amount_in = u64::from_le_bytes(data[0..8].try_into().unwrap()); - let minimum_amount_out = u64::from_le_bytes(data[8..16].try_into().unwrap()); - - Some(DexEvent::MeteoraDammV2SwapEvent(MeteoraDammV2SwapEvent { + EventDispatcher::dispatch_inner_instruction( + Protocol::MeteoraDammV2, + discriminator, + data, metadata, - pool_authority: accounts[0], - pool: accounts[1], - input_token_account: accounts[2], - output_token_account: accounts[3], - token_a_vault: accounts[4], - token_b_vault: accounts[5], - token_a_mint: accounts[6], - token_b_mint: accounts[7], - payer: accounts[8], - token_a_program: accounts[9], - token_b_program: accounts[10], - referral_token_account: Some(accounts[11]), - event_authority: accounts[12], - program: accounts[13], - amount_0: amount_in, - amount_1: minimum_amount_out, - ..Default::default() - })) + ) } -/// 解析 swap2 指令 -fn parse_swap2_instruction( - data: &[u8], - accounts: &[Pubkey], - mut metadata: EventMetadata, +pub fn parse_meteora_damm_v2_account_data( + discriminator: &[u8], + account: &crate::streaming::grpc::AccountPretty, + metadata: EventMetadata, ) -> Option { - metadata.event_type = EventType::MeteoraDammV2Swap2; - - if data.len() < 16 || accounts.len() < 13 { - return None; - } - - // 跳过 discriminator (8 bytes) - let amount_0 = u64::from_le_bytes(data[0..8].try_into().unwrap()); - let amount_1 = u64::from_le_bytes(data[8..16].try_into().unwrap()); - let swap_mode = data[16]; - - // swap2 可能有 15 个账户(带 referral)或 14 个账户 - let has_referral = accounts.len() >= 15; - - Some(DexEvent::MeteoraDammV2Swap2Event(MeteoraDammV2Swap2Event { - metadata, - pool_authority: accounts[0], - pool: accounts[1], - input_token_account: accounts[2], - output_token_account: accounts[3], - token_a_vault: accounts[4], - token_b_vault: accounts[5], - token_a_mint: accounts[6], - token_b_mint: accounts[7], - payer: accounts[8], - token_a_program: accounts[9], - token_b_program: accounts[10], - referral_token_account: if has_referral && accounts.len() > 11 { - Some(accounts[11]) - } else { - None - }, - event_authority: accounts[if has_referral { 12 } else { 11 }], - program: accounts[if has_referral { 13 } else { 12 }], - sysvar: accounts[if has_referral { 14 } else { 13 }], - amount_0, - amount_1, - swap_mode, - has_referral, - ..Default::default() - })) -} - -/// 解析 initialize_pool 指令 -fn parse_initialize_pool_instruction( - data: &[u8], - accounts: &[Pubkey], - mut metadata: EventMetadata, -) -> Option { - metadata.event_type = EventType::MeteoraDammV2InitializePool; - - if accounts.len() < 20 { - return None; - } - - // 解析 instruction data (不包含 discriminator,已被调用者移除) - // 结构: liquidity (u128 = 16 bytes) + sqrt_price (u128 = 16 bytes) + activation_point (Option = 1 + 8 bytes) - if data.len() < 33 { - return None; - } - - let mut offset = 0; - - // 读取 liquidity (u128) - let liquidity = u128::from_le_bytes(data[offset..offset + 16].try_into().ok()?); - offset += 16; - - // 读取 sqrt_price (u128) - let sqrt_price = u128::from_le_bytes(data[offset..offset + 16].try_into().ok()?); - offset += 16; - - // 读取 activation_point (Option) - let option_tag = data[offset]; - offset += 1; - let _activation_point = if option_tag == 1 && data.len() >= offset + 8 { - Some(u64::from_le_bytes(data[offset..offset + 8].try_into().ok()?)) - } else { - None - }; - - Some(DexEvent::MeteoraDammV2InitializePoolEvent(MeteoraDammV2InitializePoolEvent { - metadata, - creator: accounts[0], - position_nft_mint: accounts[1], - position_nft_account: accounts[2], - payer: accounts[3], - config: accounts[4], - pool_authority: accounts[5], - pool: accounts[6], - position: accounts[7], - token_a_mint: accounts[8], - token_b_mint: accounts[9], - token_a_vault: accounts[10], - token_b_vault: accounts[11], - payer_token_a: accounts[12], - payer_token_b: accounts[13], - token_a_program: accounts[14], - token_b_program: accounts[15], - event_authority: accounts[18], - program: accounts[19], - remaining_accounts: accounts[20..].to_vec(), - liquidity, - sqrt_price, - ..Default::default() - })) -} - -/// 解析 initialize_customizable_pool 指令 -fn parse_initialize_customizable_pool_instruction( - data: &[u8], - accounts: &[Pubkey], - mut metadata: EventMetadata, -) -> Option { - metadata.event_type = EventType::MeteoraDammV2InitializeCustomizablePool; - - if accounts.len() < 19 { - return None; - } - - // 解析 instruction data (不包含 discriminator) - // 结构: PoolFeeParameters + sqrt_min_price + sqrt_max_price + has_alpha_vault + liquidity + sqrt_price + activation_type + collect_fee_mode + activation_point - if data.len() < 99 { - return None; - } - - let mut offset = 0; - - // 解析 PoolFeeParameters - use crate::streaming::event_parser::protocols::meteora_damm_v2::PoolFeeParameters; - use borsh::BorshDeserialize; - - // PoolFeeParameters size: 8 + 2 + 8 + 8 + 1 + 3 + 1 + (optional DynamicFee) - // 先读取前 31 bytes (不包含 dynamic_fee option tag) - let pool_fees = PoolFeeParameters::deserialize(&mut &data[offset..]).ok()?; - - // 计算 pool_fees 消耗的字节数 - // BaseFee: 8 + 2 + 8 + 8 + 1 = 27 bytes - // padding: 3 bytes - // option tag: 1 byte - // 如果 dynamic_fee 存在: 2 + 16 + 2 + 2 + 2 + 4 + 4 = 32 bytes - let pool_fees_size = 31 + if pool_fees.dynamic_fee.is_some() { 32 } else { 0 }; - offset += pool_fees_size; - - // 读取 sqrt_min_price (u128) - let sqrt_min_price = u128::from_le_bytes(data[offset..offset + 16].try_into().ok()?); - offset += 16; - - // 读取 sqrt_max_price (u128) - let sqrt_max_price = u128::from_le_bytes(data[offset..offset + 16].try_into().ok()?); - offset += 16; - - // 读取 has_alpha_vault (bool) - let _has_alpha_vault = data[offset]; - offset += 1; - - // 读取 liquidity (u128) - let liquidity = u128::from_le_bytes(data[offset..offset + 16].try_into().ok()?); - offset += 16; - - // 读取 sqrt_price (u128) - let sqrt_price = u128::from_le_bytes(data[offset..offset + 16].try_into().ok()?); - offset += 16; - - // 读取 activation_type (u8) - let activation_type = data[offset]; - offset += 1; - - // 读取 collect_fee_mode (u8) - let collect_fee_mode = data[offset]; - offset += 1; - - // 读取 activation_point (Option) - let option_tag = data[offset]; - let _activation_point = if option_tag == 1 && data.len() >= offset + 9 { - Some(u64::from_le_bytes(data[offset + 1..offset + 9].try_into().ok()?)) - } else { - None - }; - - Some(DexEvent::MeteoraDammV2InitializeCustomizablePoolEvent( - MeteoraDammV2InitializeCustomizablePoolEvent { - metadata, - creator: accounts[0], - position_nft_mint: accounts[1], - position_nft_account: accounts[2], - payer: accounts[3], - pool_authority: accounts[4], - pool: accounts[5], - position: accounts[6], - token_a_mint: accounts[7], - token_b_mint: accounts[8], - token_a_vault: accounts[9], - token_b_vault: accounts[10], - payer_token_a: accounts[11], - payer_token_b: accounts[12], - token_a_program: accounts[13], - token_b_program: accounts[14], - token_2022_program: accounts[15], - system_program: accounts[16], - event_authority: accounts[17], - program: accounts[18], - remaining_accounts: accounts[19..].to_vec(), - pool_fees, - sqrt_min_price, - sqrt_max_price, - activation_type, - collect_fee_mode, - liquidity, - sqrt_price, - ..Default::default() - }, - )) -} - -/// 解析 initialize_pool_with_dynamic_config 指令 -fn parse_initialize_pool_with_dynamic_config_instruction( - data: &[u8], - accounts: &[Pubkey], - mut metadata: EventMetadata, -) -> Option { - metadata.event_type = EventType::MeteoraDammV2InitializePoolWithDynamicConfig; - - if accounts.len() < 21 { - return None; - } - - if data.len() < 99 { - return None; - } - - let mut offset = 0; - - // 解析 PoolFeeParameters - use crate::streaming::event_parser::protocols::meteora_damm_v2::PoolFeeParameters; - use borsh::BorshDeserialize; - - let pool_fees = PoolFeeParameters::deserialize(&mut &data[offset..]).ok()?; - - // 计算 pool_fees 消耗的字节数 - // BaseFee: 8 + 2 + 8 + 8 + 1 = 27 bytes - // padding: 3 bytes - // option tag: 1 byte - // 如果 dynamic_fee 存在: 2 + 16 + 2 + 2 + 2 + 4 + 4 = 32 bytes - let pool_fees_size = 31 + if pool_fees.dynamic_fee.is_some() { 32 } else { 0 }; - offset += pool_fees_size; - - // 读取 sqrt_min_price (u128) - let sqrt_min_price = u128::from_le_bytes(data[offset..offset + 16].try_into().ok()?); - offset += 16; - - // 读取 sqrt_max_price (u128) - let sqrt_max_price = u128::from_le_bytes(data[offset..offset + 16].try_into().ok()?); - offset += 16; - - // 读取 has_alpha_vault (bool) - let _has_alpha_vault = data[offset]; - offset += 1; - - // 读取 liquidity (u128) - let liquidity = u128::from_le_bytes(data[offset..offset + 16].try_into().ok()?); - offset += 16; - - // 读取 sqrt_price (u128) - let sqrt_price = u128::from_le_bytes(data[offset..offset + 16].try_into().ok()?); - offset += 16; - - // 读取 activation_type (u8) - let activation_type = data[offset]; - offset += 1; - - // 读取 collect_fee_mode (u8) - let collect_fee_mode = data[offset]; - offset += 1; - - // 读取 activation_point (Option) - let option_tag = data[offset]; - let _activation_point = if option_tag == 1 && data.len() >= offset + 9 { - Some(u64::from_le_bytes(data[offset + 1..offset + 9].try_into().ok()?)) - } else { - None - }; - - Some(DexEvent::MeteoraDammV2InitializePoolWithDynamicConfigEvent( - MeteoraDammV2InitializePoolWithDynamicConfigEvent { - metadata, - creator: accounts[0], - position_nft_mint: accounts[1], - position_nft_account: accounts[2], - payer: accounts[3], - pool_creator_authority: accounts[4], - pool_authority: accounts[6], - pool: accounts[7], - position: accounts[8], - token_a_mint: accounts[9], - token_b_mint: accounts[10], - token_a_vault: accounts[11], - token_b_vault: accounts[12], - payer_token_a: accounts[13], - payer_token_b: accounts[14], - token_a_program: accounts[15], - token_b_program: accounts[16], - token_2022_program: accounts[17], - system_program: accounts[18], - event_authority: accounts[19], - program: accounts[20], - config: accounts[5], - pool_fees, - sqrt_min_price, - sqrt_max_price, - activation_type, - collect_fee_mode, - liquidity, - sqrt_price, - ..Default::default() - }, - )) -} - -/// 解析 swap inner instruction (CPI event) -fn parse_swap_inner_instruction(data: &[u8], metadata: EventMetadata) -> Option { - if let Some(event) = meteora_damm_v2_swap_event_decode(data) { - Some(DexEvent::MeteoraDammV2SwapEvent(MeteoraDammV2SwapEvent { metadata, ..event })) - } else { - None - } -} - -/// 解析 initialize pool inner instruction (CPI event) -fn parse_initialize_pool_inner_instruction( - data: &[u8], - mut metadata: EventMetadata, -) -> Option { - metadata.event_type = EventType::MeteoraDammV2InitializePool; - if let Some(event) = meteora_damm_v2_initialize_pool_event_decode(data) { - Some(DexEvent::MeteoraDammV2InitializePoolEvent(MeteoraDammV2InitializePoolEvent { - metadata, - ..event - })) - } else { - None - } + EventDispatcher::dispatch_account(Protocol::MeteoraDammV2, discriminator, account, metadata) } diff --git a/src/streaming/event_parser/protocols/pumpfun/events.rs b/src/streaming/event_parser/protocols/pumpfun/events.rs index 6bc7f78..3e0142e 100755 --- a/src/streaming/event_parser/protocols/pumpfun/events.rs +++ b/src/streaming/event_parser/protocols/pumpfun/events.rs @@ -97,136 +97,6 @@ pub struct PumpFunCreateV2TokenEvent { pub program: Pubkey, } -pub fn pumpfun_create_v2_token_event_log_decode(data: &[u8]) -> Option { - let mut offset = 0; - - // Parse name string: [length (4 bytes u32)][string bytes] - if data.len() < offset + 4 { - return None; - } - let name_len = u32::from_le_bytes(data[offset..offset + 4].try_into().ok()?) as usize; - offset += 4; - if data.len() < offset + name_len { - return None; - } - let name = String::from_utf8(data[offset..offset + name_len].to_vec()).ok()?; - offset += name_len; - - // Parse symbol string - if data.len() < offset + 4 { - return None; - } - let symbol_len = u32::from_le_bytes(data[offset..offset + 4].try_into().ok()?) as usize; - offset += 4; - if data.len() < offset + symbol_len { - return None; - } - let symbol = String::from_utf8(data[offset..offset + symbol_len].to_vec()).ok()?; - offset += symbol_len; - - // Parse uri string - if data.len() < offset + 4 { - return None; - } - let uri_len = u32::from_le_bytes(data[offset..offset + 4].try_into().ok()?) as usize; - offset += 4; - if data.len() < offset + uri_len { - return None; - } - let uri = String::from_utf8(data[offset..offset + uri_len].to_vec()).ok()?; - offset += uri_len; - - // Parse Pubkey fields (32 bytes each) - if data.len() < offset + 32 { - return None; - } - let mint = Pubkey::new_from_array(data[offset..offset + 32].try_into().ok()?); - offset += 32; - - if data.len() < offset + 32 { - return None; - } - let bonding_curve = Pubkey::new_from_array(data[offset..offset + 32].try_into().ok()?); - offset += 32; - - if data.len() < offset + 32 { - return None; - } - let user = Pubkey::new_from_array(data[offset..offset + 32].try_into().ok()?); - offset += 32; - - if data.len() < offset + 32 { - return None; - } - let creator = Pubkey::new_from_array(data[offset..offset + 32].try_into().ok()?); - offset += 32; - - // Parse numeric fields - if data.len() < offset + 8 { - return None; - } - let timestamp = i64::from_le_bytes(data[offset..offset + 8].try_into().ok()?); - offset += 8; - - if data.len() < offset + 8 { - return None; - } - let virtual_token_reserves = u64::from_le_bytes(data[offset..offset + 8].try_into().ok()?); - offset += 8; - - if data.len() < offset + 8 { - return None; - } - let virtual_sol_reserves = u64::from_le_bytes(data[offset..offset + 8].try_into().ok()?); - offset += 8; - - if data.len() < offset + 8 { - return None; - } - let real_token_reserves = u64::from_le_bytes(data[offset..offset + 8].try_into().ok()?); - offset += 8; - - if data.len() < offset + 8 { - return None; - } - let token_total_supply = u64::from_le_bytes(data[offset..offset + 8].try_into().ok()?); - offset += 8; - - // If data length allows, parse V2 extra fields: token_program (32 bytes) + is_mayhem_mode (1 byte) + is_cashback_enabled (1 byte) - let (token_program, is_mayhem_mode, is_cashback_enabled) = if data.len() >= offset + 34 { - let token_program = Pubkey::new_from_array(data[offset..offset + 32].try_into().ok()?); - let is_mayhem_mode = data[offset + 32] != 0; - let is_cashback_enabled = data[offset + 33] != 0; - (token_program, is_mayhem_mode, is_cashback_enabled) - } else if data.len() >= offset + 33 { - // Backward compat: only token_program + is_mayhem_mode, no is_cashback_enabled - let token_program = Pubkey::new_from_array(data[offset..offset + 32].try_into().ok()?); - let is_mayhem_mode = data[offset + 32] != 0; - (token_program, is_mayhem_mode, false) - } else { - (Pubkey::default(), false, false) - }; - - Some(PumpFunCreateV2TokenEvent { - name, - symbol, - uri, - mint, - bonding_curve, - user, - creator, - timestamp, - virtual_token_reserves, - virtual_sol_reserves, - real_token_reserves, - token_total_supply, - token_program, - is_mayhem_mode, - is_cashback_enabled, - ..Default::default() - }) -} - #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)] pub struct PumpFunTradeEvent { #[borsh(skip)] @@ -318,49 +188,6 @@ pub struct PumpFunTradeEvent { /// Layout: mint(32)+sol_amount(8)+token_amount(8)+is_buy(1)+user(32)+timestamp(8)+virtual_sol(8)+virtual_token(8)+real_sol(8)+real_token(8)+fee_recipient(32)+fee_basis_points(8)+fee(8)+creator(32)+creator_fee_bps(8)+creator_fee(8)+track_volume(1)+total_unclaimed(8)+total_claimed(8)+current_sol_volume(8)+last_update_timestamp(8) = 250 pub const PUMPFUN_TRADE_EVENT_LOG_SIZE: usize = 250; -/// Decode TradeEvent log; if data.len() > 250 then parse ix_name, mayhem_mode, cashback (IDL-aligned). -pub fn pumpfun_trade_event_log_decode(data: &[u8]) -> Option { - if data.len() < PUMPFUN_TRADE_EVENT_LOG_SIZE { - return None; - } - let mut event = - borsh::from_slice::(&data[..PUMPFUN_TRADE_EVENT_LOG_SIZE]).ok()?; - let mut offset = PUMPFUN_TRADE_EVENT_LOG_SIZE; - if offset < data.len() { - let (ix_name, inc) = read_borsh_string(data, offset).unwrap_or((String::new(), 0)); - offset += inc; - event.ix_name = ix_name; - } - if offset + 1 <= data.len() { - event.mayhem_mode = data[offset] != 0; - offset += 1; - } - if offset + 8 <= data.len() { - event.cashback_fee_basis_points = - u64::from_le_bytes(data[offset..offset + 8].try_into().ok()?); - offset += 8; - } - if offset + 8 <= data.len() { - event.cashback = u64::from_le_bytes(data[offset..offset + 8].try_into().ok()?); - } - event.is_cashback_coin = event.cashback_fee_basis_points > 0; - Some(event) -} - -#[inline] -fn read_borsh_string(data: &[u8], start: usize) -> Option<(String, usize)> { - if start + 4 > data.len() { - return None; - } - let len = u32::from_le_bytes(data[start..start + 4].try_into().ok()?) as usize; - let start = start + 4; - if start + len > data.len() { - return None; - } - let s = String::from_utf8_lossy(&data[start..start + len]).to_string(); - Some((s, 4 + len)) -} - #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)] pub struct PumpFunMigrateEvent { #[borsh(skip)] @@ -551,13 +378,6 @@ pub struct PumpFunMigrateBondingCurveCreatorEvent { pub const PUMPFUN_MIGRATE_EVENT_LOG_SIZE: usize = 160; -pub fn pumpfun_migrate_event_log_decode(data: &[u8]) -> Option { - if data.len() < PUMPFUN_MIGRATE_EVENT_LOG_SIZE { - return None; - } - borsh::from_slice::(&data[..PUMPFUN_MIGRATE_EVENT_LOG_SIZE]).ok() -} - /// Bonding curve #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)] pub struct PumpFunBondingCurveAccountEvent { diff --git a/src/streaming/event_parser/protocols/pumpfun/parser.rs b/src/streaming/event_parser/protocols/pumpfun/parser.rs old mode 100755 new mode 100644 index 959d3f0..e7d5c6c --- a/src/streaming/event_parser/protocols/pumpfun/parser.rs +++ b/src/streaming/event_parser/protocols/pumpfun/parser.rs @@ -1,448 +1,37 @@ use crate::streaming::event_parser::{ - common::{EventMetadata, EventType}, - protocols::pumpfun::{ - discriminators, pumpfun_create_v2_token_event_log_decode, pumpfun_migrate_event_log_decode, - pumpfun_trade_event_log_decode, PumpFunCreateTokenEvent, PumpFunCreateV2TokenEvent, - PumpFunMigrateEvent, PumpFunTradeEvent, - }, - DexEvent, + common::EventMetadata, core::EventDispatcher, DexEvent, Protocol, }; use solana_sdk::pubkey::Pubkey; -/// PumpFun程序ID -pub const PUMPFUN_PROGRAM_ID: Pubkey = - solana_sdk::pubkey!("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"); +pub use sol_parser_sdk::instr::program_ids::PUMPFUN_PROGRAM_ID; -/// 解析 PumpFun instruction data -/// -/// 根据判别器路由到具体的 instruction 解析函数 pub fn parse_pumpfun_instruction_data( discriminator: &[u8], data: &[u8], accounts: &[Pubkey], metadata: EventMetadata, ) -> Option { - match discriminator { - discriminators::CREATE_TOKEN_IX => parse_create_token_instruction(data, accounts, metadata), - discriminators::CREATE_V2_TOKEN_IX => { - parse_create_v2_token_instruction(data, accounts, metadata) - } - discriminators::BUY_IX => parse_buy_instruction(data, accounts, metadata), - discriminators::BUY_EXACT_SOL_IN_IX => { - parse_buy_exact_sol_in_instruction(data, accounts, metadata) - } - discriminators::SELL_IX => parse_sell_instruction(data, accounts, metadata), - discriminators::MIGRATE_IX => parse_migrate_instruction(data, accounts, metadata), - _ => None, - } + EventDispatcher::dispatch_instruction( + Protocol::PumpFun, + discriminator, + data, + accounts, + metadata, + ) } -/// 解析 PumpFun inner instruction data -/// -/// 根据判别器路由到具体的 inner instruction 解析函数 pub fn parse_pumpfun_inner_instruction_data( discriminator: &[u8], data: &[u8], metadata: EventMetadata, ) -> Option { - match discriminator { - discriminators::CREATE_TOKEN_EVENT => parse_create_token_inner_instruction(data, metadata), - discriminators::TRADE_EVENT => parse_trade_inner_instruction(data, metadata), - discriminators::COMPLETE_PUMP_AMM_MIGRATION_EVENT => { - parse_migrate_inner_instruction(data, metadata) - } - _ => None, - } + EventDispatcher::dispatch_inner_instruction(Protocol::PumpFun, discriminator, data, metadata) } -/// 解析 PumpFun 账户数据 -/// -/// 根据判别器路由到具体的账户解析函数 pub fn parse_pumpfun_account_data( discriminator: &[u8], account: &crate::streaming::grpc::AccountPretty, - metadata: crate::streaming::event_parser::common::EventMetadata, -) -> Option { - match discriminator { - discriminators::BONDING_CURVE_ACCOUNT => { - crate::streaming::event_parser::protocols::pumpfun::types::bonding_curve_parser( - account, metadata, - ) - } - discriminators::GLOBAL_ACCOUNT => { - crate::streaming::event_parser::protocols::pumpfun::types::global_parser( - account, metadata, - ) - } - _ => None, - } -} - -/// 解析迁移事件 -fn parse_migrate_inner_instruction(data: &[u8], mut metadata: EventMetadata) -> Option { - metadata.event_type = EventType::PumpFunMigrate; - if let Some(event) = pumpfun_migrate_event_log_decode(data) { - Some(DexEvent::PumpFunMigrateEvent(PumpFunMigrateEvent { metadata, ..event })) - } else { - None - } -} - -/// 解析创建代币日志事件 -fn parse_create_token_inner_instruction( - data: &[u8], - mut metadata: EventMetadata, + metadata: EventMetadata, ) -> Option { - metadata.event_type = EventType::PumpFunCreateToken; - if let Some(event) = pumpfun_create_v2_token_event_log_decode(data) { - Some(DexEvent::PumpFunCreateV2TokenEvent(PumpFunCreateV2TokenEvent { metadata, ..event })) - } else { - None - } -} - -/// 解析交易事件 (inner instruction 不设置 event_type,因为不知道是 Buy 还是 Sell) -fn parse_trade_inner_instruction(data: &[u8], metadata: EventMetadata) -> Option { - // 注意:inner instruction 的 trade event 不设置 event_type - // 因为它会被合并到 instruction event 中,而 instruction event 已经设置了正确的 event_type - if let Some(event) = pumpfun_trade_event_log_decode(data) { - Some(DexEvent::PumpFunTradeEvent(PumpFunTradeEvent { metadata, ..event })) - } else { - None - } -} - -/// 解析创建代币指令事件 -/// 账户: 0: mint, 1: mint_authority, 2: bonding_curve, 3: associated_bonding_curve, 4: global, -/// 5: mpl_token_metadata, 6: metadata_account, 7: user, 8: system_program, 9: token_program, -/// 10: associated_token_program, 11: rent, 12: event_authority, 13: program. -/// 共 14 个固定账户,不足时返回 None 避免越界。 -fn parse_create_token_instruction( - data: &[u8], - accounts: &[Pubkey], - mut metadata: EventMetadata, -) -> Option { - metadata.event_type = EventType::PumpFunCreateToken; - - const CREATE_TOKEN_MIN_ACCOUNTS: usize = 14; - if data.len() < 16 || accounts.len() < CREATE_TOKEN_MIN_ACCOUNTS { - return None; - } - let mut offset = 0; - if offset + 4 > data.len() { - return None; - } - let name_len = u32::from_le_bytes(data[offset..offset + 4].try_into().unwrap()) as usize; - offset += 4; - if offset + name_len > data.len() { - return None; - } - let name = String::from_utf8_lossy(&data[offset..offset + name_len]); - offset += name_len; - if offset + 4 > data.len() { - return None; - } - let symbol_len = u32::from_le_bytes(data[offset..offset + 4].try_into().unwrap()) as usize; - offset += 4; - if offset + symbol_len > data.len() { - return None; - } - let symbol = String::from_utf8_lossy(&data[offset..offset + symbol_len]); - offset += symbol_len; - if offset + 4 > data.len() { - return None; - } - let uri_len = u32::from_le_bytes(data[offset..offset + 4].try_into().unwrap()) as usize; - offset += 4; - if offset + uri_len > data.len() { - return None; - } - let uri = String::from_utf8_lossy(&data[offset..offset + uri_len]); - offset += uri_len; - let creator = if offset + 32 <= data.len() { - Pubkey::new_from_array(data[offset..offset + 32].try_into().ok()?) - } else { - Pubkey::default() - }; - - Some(DexEvent::PumpFunCreateTokenEvent(PumpFunCreateTokenEvent { - metadata, - name: name.to_string(), - symbol: symbol.to_string(), - uri: uri.to_string(), - creator, - mint: accounts[0], - mint_authority: accounts[1], - bonding_curve: accounts[2], - associated_bonding_curve: accounts[3], - global: accounts[4], - mpl_token_metadata: accounts[5], - metadata_account: accounts[6], - user: accounts[7], - system_program: accounts[8], - token_program: accounts[9], - associated_token_program: accounts[10], - rent: accounts[11], - event_authority: accounts[12], - program: accounts[13], - ..Default::default() - })) -} - -/// 解析创建 V2 代币指令事件 (SPL-22 Token, Mayhem Mode) -/// 与 IDL create_v2 及区块浏览器一致,共 16 个固定账户: -/// 0: mint, 1: mint_authority, 2: bonding_curve, 3: associated_bonding_curve, 4: global, -/// 5: user, 6: system_program, 7: token_program, 8: associated_token_program, 9: mayhem_program_id, -/// 10: global_params, 11: sol_vault, 12: mayhem_state, 13: mayhem_token_vault, 14: event_authority, 15: program. -/// 不足 16 个账户时返回 None 避免越界。 -/// 注意:shredstream 路径仅传入 static_account_keys,若交易使用 Address Lookup Tables, -/// 无法解析 loaded_addresses,部分账户会以 default 填充,导致 token_program/global 等错误。 -fn parse_create_v2_token_instruction( - data: &[u8], - accounts: &[Pubkey], - mut metadata: EventMetadata, -) -> Option { - const CREATE_V2_MIN_ACCOUNTS: usize = 16; - // Guard: avoid index out of bounds (e.g. ALT-loaded tx with fewer static accounts). See issue #63. - if accounts.len() < CREATE_V2_MIN_ACCOUNTS || data.len() < 16 { - return None; - } - metadata.event_type = EventType::PumpFunCreateV2Token; - - let mut offset = 0; - if offset + 4 > data.len() { - return None; - } - let name_len = u32::from_le_bytes(data[offset..offset + 4].try_into().unwrap()) as usize; - offset += 4; - if offset + name_len > data.len() { - return None; - } - let name = String::from_utf8_lossy(&data[offset..offset + name_len]); - offset += name_len; - if offset + 4 > data.len() { - return None; - } - let symbol_len = u32::from_le_bytes(data[offset..offset + 4].try_into().unwrap()) as usize; - offset += 4; - if offset + symbol_len > data.len() { - return None; - } - let symbol = String::from_utf8_lossy(&data[offset..offset + symbol_len]); - offset += symbol_len; - if offset + 4 > data.len() { - return None; - } - let uri_len = u32::from_le_bytes(data[offset..offset + 4].try_into().unwrap()) as usize; - offset += 4; - if offset + uri_len > data.len() { - return None; - } - let uri = String::from_utf8_lossy(&data[offset..offset + uri_len]); - offset += uri_len; - let creator = if offset + 32 <= data.len() { - Pubkey::new_from_array(data[offset..offset + 32].try_into().ok()?) - } else { - Pubkey::default() - }; - - // Safe slice: already guaranteed accounts.len() >= 16 above; avoid any index panic (issue #63). - let acc = &accounts[0..CREATE_V2_MIN_ACCOUNTS]; - Some(DexEvent::PumpFunCreateV2TokenEvent(PumpFunCreateV2TokenEvent { - metadata, - name: name.to_string(), - symbol: symbol.to_string(), - uri: uri.to_string(), - creator, - mint: acc[0], - mint_authority: acc[1], - bonding_curve: acc[2], - associated_bonding_curve: acc[3], - global: acc[4], - user: acc[5], - system_program: acc[6], - token_program: acc[7], - associated_token_program: acc[8], - mayhem_program_id: acc[9], - global_params: acc[10], - sol_vault: acc[11], - mayhem_state: acc[12], - mayhem_token_vault: acc[13], - event_authority: acc[14], - program: acc[15], - ..Default::default() - })) -} - -/// Parse buy instruction event. -/// Buy has 16 fixed accounts + optional 17th (index 16, "Account" on block explorers): -/// 0: global, 1: fee_recipient, 2: mint, 3: bonding_curve, 4: associated_bonding_curve, -/// 5: associated_user, 6: user, 7: system_program, 8: token_program, 9: creator_vault, -/// 10: event_authority, 11: program, 12: global_volume_accumulator, 13: user_volume_accumulator, -/// 14: fee_config, 15: fee_program, 16 (optional): account. -fn parse_buy_instruction( - data: &[u8], - accounts: &[Pubkey], - mut metadata: EventMetadata, -) -> Option { - metadata.event_type = EventType::PumpFunBuy; - - if data.len() < 16 || accounts.len() < 16 { - return None; - } - let amount = u64::from_le_bytes(data[0..8].try_into().unwrap()); - let max_sol_cost = u64::from_le_bytes(data[8..16].try_into().unwrap()); - Some(DexEvent::PumpFunTradeEvent(PumpFunTradeEvent { - metadata, - global: accounts[0], - fee_recipient: accounts[1], - mint: accounts[2], - bonding_curve: accounts[3], - associated_bonding_curve: accounts[4], - associated_user: accounts[5], - user: accounts[6], - system_program: accounts[7], - token_program: accounts[8], - creator_vault: accounts[9], - event_authority: accounts[10], - program: accounts[11], - global_volume_accumulator: accounts[12], - user_volume_accumulator: accounts[13], - fee_config: accounts[14], - fee_program: accounts[15], - account: accounts.get(16).copied(), - max_sol_cost, - amount, - is_buy: true, - ..Default::default() - })) -} - -/// Parse buy_exact_sol_in instruction event. -/// Same account layout as buy: 16 fixed + optional 17th (index 16). -/// Args: spendable_sol_in (SOL), min_tokens_out (token). -fn parse_buy_exact_sol_in_instruction( - data: &[u8], - accounts: &[Pubkey], - mut metadata: EventMetadata, -) -> Option { - metadata.event_type = EventType::PumpFunBuyExactSolIn; - - if data.len() < 16 || accounts.len() < 16 { - return None; - } - - let spendable_sol_in = u64::from_le_bytes(data[0..8].try_into().unwrap()); - let min_tokens_out = u64::from_le_bytes(data[8..16].try_into().unwrap()); - - Some(DexEvent::PumpFunTradeEvent(PumpFunTradeEvent { - metadata, - global: accounts[0], - fee_recipient: accounts[1], - mint: accounts[2], - bonding_curve: accounts[3], - associated_bonding_curve: accounts[4], - associated_user: accounts[5], - user: accounts[6], - system_program: accounts[7], - token_program: accounts[8], - creator_vault: accounts[9], - event_authority: accounts[10], - program: accounts[11], - global_volume_accumulator: accounts[12], - user_volume_accumulator: accounts[13], - fee_config: accounts[14], - fee_program: accounts[15], - account: accounts.get(16).copied(), - max_sol_cost: spendable_sol_in, - amount: min_tokens_out, - is_buy: true, - ..Default::default() - })) -} - -/// Parse sell instruction event. -/// Sell has 14 fixed accounts; some versions pass 17 accounts, index 16 = "Account" on block explorers. -fn parse_sell_instruction( - data: &[u8], - accounts: &[Pubkey], - mut metadata: EventMetadata, -) -> Option { - metadata.event_type = EventType::PumpFunSell; - - if data.len() < 16 || accounts.len() < 14 { - return None; - } - let amount = u64::from_le_bytes(data[0..8].try_into().unwrap()); - let min_sol_output = u64::from_le_bytes(data[8..16].try_into().unwrap()); - Some(DexEvent::PumpFunTradeEvent(PumpFunTradeEvent { - metadata, - global: accounts[0], - fee_recipient: accounts[1], - mint: accounts[2], - bonding_curve: accounts[3], - associated_bonding_curve: accounts[4], - associated_user: accounts[5], - user: accounts[6], - system_program: accounts[7], - creator_vault: accounts[8], - token_program: accounts[9], - event_authority: accounts[10], - program: accounts[11], - global_volume_accumulator: Pubkey::default(), - user_volume_accumulator: Pubkey::default(), - fee_config: accounts[12], - fee_program: accounts[13], - account: accounts.get(16).copied(), - min_sol_output, - amount, - is_buy: false, - ..Default::default() - })) -} - -/// 解析迁移指令事件 -/// 共 24 个固定账户: 0: global, 1: withdraw_authority, 2: mint, 3: bonding_curve, 4: associated_bonding_curve, -/// 5: user, 6: system_program, 7: token_program, 8: pump_amm, 9: pool, 10: pool_authority, -/// 11: pool_authority_mint_account, 12: pool_authority_wsol_account, 13: amm_global_config, 14: wsol_mint, -/// 15: lp_mint, 16: user_pool_token_account, 17: pool_base_token_account, 18: pool_quote_token_account, -/// 19: token_2022_program, 20: associated_token_program, 21: pump_amm_event_authority, 22: event_authority, 23: program. -fn parse_migrate_instruction( - _data: &[u8], - accounts: &[Pubkey], - mut metadata: EventMetadata, -) -> Option { - metadata.event_type = EventType::PumpFunMigrate; - - if accounts.len() < 24 { - return None; - } - Some(DexEvent::PumpFunMigrateEvent(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() - })) + EventDispatcher::dispatch_account(Protocol::PumpFun, discriminator, account, metadata) } diff --git a/src/streaming/event_parser/protocols/pumpfun/types.rs b/src/streaming/event_parser/protocols/pumpfun/types.rs index f4118b4..1f6000e 100644 --- a/src/streaming/event_parser/protocols/pumpfun/types.rs +++ b/src/streaming/event_parser/protocols/pumpfun/types.rs @@ -2,15 +2,6 @@ use borsh::BorshDeserialize; use serde::{Deserialize, Serialize}; use solana_sdk::pubkey::Pubkey; -use crate::streaming::{ - event_parser::{ - common::{EventMetadata, EventType}, - protocols::pumpfun::{PumpFunBondingCurveAccountEvent, PumpFunGlobalAccountEvent}, - DexEvent, - }, - grpc::AccountPretty, -}; - #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)] pub struct BondingCurve { pub virtual_token_reserves: u64, @@ -26,37 +17,6 @@ pub struct BondingCurve { pub const BONDING_CURVE_SIZE: usize = 8 * 5 + 1 + 32 + 1 + 1; -pub fn bonding_curve_decode(data: &[u8]) -> Option { - if data.len() < BONDING_CURVE_SIZE { - return None; - } - borsh::from_slice::(&data[..BONDING_CURVE_SIZE]).ok() -} - -pub fn bonding_curve_parser( - account: &AccountPretty, - mut metadata: EventMetadata, -) -> Option { - metadata.event_type = EventType::AccountPumpFunBondingCurve; - - if account.data.len() < BONDING_CURVE_SIZE + 8 { - return None; - } - if let Some(bonding_curve) = bonding_curve_decode(&account.data[8..BONDING_CURVE_SIZE + 8]) { - Some(DexEvent::PumpFunBondingCurveAccountEvent(PumpFunBondingCurveAccountEvent { - metadata, - pubkey: account.pubkey, - executable: account.executable, - lamports: account.lamports, - owner: account.owner, - rent_epoch: account.rent_epoch, - bonding_curve, - })) - } else { - None - } -} - #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)] pub struct Global { pub initialized: bool, @@ -84,31 +44,3 @@ pub struct Global { pub const GLOBAL_SIZE: usize = 1 + 32 * 2 + 8 * 5 + 32 + 1 + 8 * 2 + 32 * 7 + 32 * 2 + 1 + 32 * 2 + 1 + 32 * 7 + 1; - -pub fn global_decode(data: &[u8]) -> Option { - if data.len() < GLOBAL_SIZE { - return None; - } - borsh::from_slice::(&data[..GLOBAL_SIZE]).ok() -} - -pub fn global_parser(account: &AccountPretty, mut metadata: EventMetadata) -> Option { - metadata.event_type = EventType::AccountPumpFunGlobal; - - if account.data.len() < GLOBAL_SIZE + 8 { - return None; - } - if let Some(global) = global_decode(&account.data[8..GLOBAL_SIZE + 8]) { - Some(DexEvent::PumpFunGlobalAccountEvent(PumpFunGlobalAccountEvent { - metadata, - pubkey: account.pubkey, - executable: account.executable, - lamports: account.lamports, - owner: account.owner, - rent_epoch: account.rent_epoch, - global, - })) - } else { - None - } -} diff --git a/src/streaming/event_parser/protocols/pumpswap/events.rs b/src/streaming/event_parser/protocols/pumpswap/events.rs index 04119fc..e1fd53b 100755 --- a/src/streaming/event_parser/protocols/pumpswap/events.rs +++ b/src/streaming/event_parser/protocols/pumpswap/events.rs @@ -2,7 +2,6 @@ use borsh::BorshDeserialize; use serde::{Deserialize, Serialize}; use solana_sdk::pubkey::Pubkey; -use crate::streaming::event_parser::common::utils::{read_i64_le, read_u32_le, read_u64_le}; use crate::streaming::event_parser::common::EventMetadata; use crate::streaming::event_parser::protocols::pumpswap::types::{GlobalConfig, Pool}; @@ -70,102 +69,6 @@ pub const PUMP_SWAP_BUY_EVENT_LOG_MIN: usize = 385; /// Backwards-compatible name for the minimum BuyEvent payload size (legacy `borsh` slice length). pub const PUMP_SWAP_BUY_EVENT_LOG_SIZE: usize = PUMP_SWAP_BUY_EVENT_LOG_MIN; -pub fn pump_swap_buy_event_log_decode(data: &[u8]) -> Option { - if data.len() < PUMP_SWAP_BUY_EVENT_LOG_MIN { - return None; - } - let timestamp = read_i64_le(data, 0)?; - let base_amount_out = read_u64_le(data, 8)?; - let max_quote_amount_in = read_u64_le(data, 16)?; - let user_base_token_reserves = read_u64_le(data, 24)?; - let user_quote_token_reserves = read_u64_le(data, 32)?; - let pool_base_token_reserves = read_u64_le(data, 40)?; - let pool_quote_token_reserves = read_u64_le(data, 48)?; - let quote_amount_in = read_u64_le(data, 56)?; - let lp_fee_basis_points = read_u64_le(data, 64)?; - let lp_fee = read_u64_le(data, 72)?; - let protocol_fee_basis_points = read_u64_le(data, 80)?; - let protocol_fee = read_u64_le(data, 88)?; - let quote_amount_in_with_lp_fee = read_u64_le(data, 96)?; - let user_quote_amount_in = read_u64_le(data, 104)?; - let pool = Pubkey::new_from_array(data.get(112..144)?.try_into().ok()?); - let user = Pubkey::new_from_array(data.get(144..176)?.try_into().ok()?); - let user_base_token_account = Pubkey::new_from_array(data.get(176..208)?.try_into().ok()?); - let user_quote_token_account = Pubkey::new_from_array(data.get(208..240)?.try_into().ok()?); - let protocol_fee_recipient = Pubkey::new_from_array(data.get(240..272)?.try_into().ok()?); - let protocol_fee_recipient_token_account = - Pubkey::new_from_array(data.get(272..304)?.try_into().ok()?); - let coin_creator = Pubkey::new_from_array(data.get(304..336)?.try_into().ok()?); - let coin_creator_fee_basis_points = read_u64_le(data, 336)?; - let coin_creator_fee = read_u64_le(data, 344)?; - let track_volume = *data.get(352)? != 0; - let total_unclaimed_tokens = read_u64_le(data, 353)?; - let total_claimed_tokens = read_u64_le(data, 361)?; - let current_sol_volume = read_u64_le(data, 369)?; - let last_update_timestamp = read_i64_le(data, 377)?; - - let mut offset = 385usize; - let min_base_amount_out = if data.len() >= offset + 8 { - let v = read_u64_le(data, offset)?; - offset += 8; - v - } else { - 0 - }; - - let ix_name = if data.len() >= offset + 4 { - let slen = read_u32_le(data, offset)? as usize; - let str_start = offset + 4; - if data.len() < str_start + slen { - return None; - } - offset = str_start + slen; - String::from_utf8_lossy(&data[str_start..offset]).into_owned() - } else { - String::new() - }; - - let cashback_fee_basis_points = read_u64_le(data, offset).unwrap_or(0); - let cashback = read_u64_le(data, offset + 8).unwrap_or(0); - - Some(PumpSwapBuyEvent { - metadata: EventMetadata::default(), - timestamp, - base_amount_out, - max_quote_amount_in, - user_base_token_reserves, - user_quote_token_reserves, - pool_base_token_reserves, - pool_quote_token_reserves, - quote_amount_in, - lp_fee_basis_points, - lp_fee, - protocol_fee_basis_points, - protocol_fee, - quote_amount_in_with_lp_fee, - user_quote_amount_in, - pool, - user, - user_base_token_account, - user_quote_token_account, - protocol_fee_recipient, - protocol_fee_recipient_token_account, - coin_creator, - coin_creator_fee_basis_points, - coin_creator_fee, - track_volume, - total_unclaimed_tokens, - total_claimed_tokens, - current_sol_volume, - last_update_timestamp, - min_base_amount_out, - ix_name, - cashback_fee_basis_points, - cashback, - ..Default::default() - }) -} - /// 卖出事件 #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)] pub struct PumpSwapSellEvent { @@ -221,72 +124,6 @@ pub const PUMP_SWAP_SELL_EVENT_WITH_CASHBACK: usize = 368; /// Backwards-compatible name for the pre-cashback SellEvent payload size. pub const PUMP_SWAP_SELL_EVENT_LOG_SIZE: usize = PUMP_SWAP_SELL_EVENT_LOG_MIN; -pub fn pump_swap_sell_event_log_decode(data: &[u8]) -> Option { - if data.len() < PUMP_SWAP_SELL_EVENT_LOG_MIN { - return None; - } - let timestamp = read_i64_le(data, 0)?; - let base_amount_in = read_u64_le(data, 8)?; - let min_quote_amount_out = read_u64_le(data, 16)?; - let user_base_token_reserves = read_u64_le(data, 24)?; - let user_quote_token_reserves = read_u64_le(data, 32)?; - let pool_base_token_reserves = read_u64_le(data, 40)?; - let pool_quote_token_reserves = read_u64_le(data, 48)?; - let quote_amount_out = read_u64_le(data, 56)?; - let lp_fee_basis_points = read_u64_le(data, 64)?; - let lp_fee = read_u64_le(data, 72)?; - let protocol_fee_basis_points = read_u64_le(data, 80)?; - let protocol_fee = read_u64_le(data, 88)?; - let quote_amount_out_without_lp_fee = read_u64_le(data, 96)?; - let user_quote_amount_out = read_u64_le(data, 104)?; - let pool = Pubkey::new_from_array(data.get(112..144)?.try_into().ok()?); - let user = Pubkey::new_from_array(data.get(144..176)?.try_into().ok()?); - let user_base_token_account = Pubkey::new_from_array(data.get(176..208)?.try_into().ok()?); - let user_quote_token_account = Pubkey::new_from_array(data.get(208..240)?.try_into().ok()?); - let protocol_fee_recipient = Pubkey::new_from_array(data.get(240..272)?.try_into().ok()?); - let protocol_fee_recipient_token_account = - Pubkey::new_from_array(data.get(272..304)?.try_into().ok()?); - let coin_creator = Pubkey::new_from_array(data.get(304..336)?.try_into().ok()?); - let coin_creator_fee_basis_points = read_u64_le(data, 336)?; - let coin_creator_fee = read_u64_le(data, 344)?; - let (cashback_fee_basis_points, cashback) = if data.len() >= PUMP_SWAP_SELL_EVENT_WITH_CASHBACK - { - (read_u64_le(data, 352)?, read_u64_le(data, 360)?) - } else { - (0, 0) - }; - - Some(PumpSwapSellEvent { - metadata: EventMetadata::default(), - timestamp, - base_amount_in, - min_quote_amount_out, - user_base_token_reserves, - user_quote_token_reserves, - pool_base_token_reserves, - pool_quote_token_reserves, - quote_amount_out, - lp_fee_basis_points, - lp_fee, - protocol_fee_basis_points, - protocol_fee, - quote_amount_out_without_lp_fee, - user_quote_amount_out, - pool, - user, - user_base_token_account, - user_quote_token_account, - protocol_fee_recipient, - protocol_fee_recipient_token_account, - coin_creator, - coin_creator_fee_basis_points, - coin_creator_fee, - cashback_fee_basis_points, - cashback, - ..Default::default() - }) -} - /// 创建池子事件 #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)] pub struct PumpSwapCreatePoolEvent { @@ -322,13 +159,6 @@ pub struct PumpSwapCreatePoolEvent { pub const PUMP_SWAP_CREATE_POOL_EVENT_LOG_SIZE: usize = 325; -pub fn pump_swap_create_pool_event_log_decode(data: &[u8]) -> Option { - if data.len() < PUMP_SWAP_CREATE_POOL_EVENT_LOG_SIZE { - return None; - } - borsh::from_slice::(&data[..PUMP_SWAP_CREATE_POOL_EVENT_LOG_SIZE]).ok() -} - /// 存款事件 #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)] pub struct PumpSwapDepositEvent { @@ -362,13 +192,6 @@ pub struct PumpSwapDepositEvent { pub const PUMP_SWAP_DEPOSIT_EVENT_LOG_SIZE: usize = 248; -pub fn pump_swap_deposit_event_log_decode(data: &[u8]) -> Option { - if data.len() < PUMP_SWAP_DEPOSIT_EVENT_LOG_SIZE { - return None; - } - borsh::from_slice::(&data[..PUMP_SWAP_DEPOSIT_EVENT_LOG_SIZE]).ok() -} - /// 提款事件 #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)] pub struct PumpSwapWithdrawEvent { @@ -402,13 +225,6 @@ pub struct PumpSwapWithdrawEvent { pub const PUMP_SWAP_WITHDRAW_EVENT_LOG_SIZE: usize = 248; -pub fn pump_swap_withdraw_event_log_decode(data: &[u8]) -> Option { - if data.len() < PUMP_SWAP_WITHDRAW_EVENT_LOG_SIZE { - return None; - } - borsh::from_slice::(&data[..PUMP_SWAP_WITHDRAW_EVENT_LOG_SIZE]).ok() -} - /// 全局配置 #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)] pub struct PumpSwapGlobalConfigAccountEvent { diff --git a/src/streaming/event_parser/protocols/pumpswap/parser.rs b/src/streaming/event_parser/protocols/pumpswap/parser.rs old mode 100755 new mode 100644 index 2a76205..72b0579 --- a/src/streaming/event_parser/protocols/pumpswap/parser.rs +++ b/src/streaming/event_parser/protocols/pumpswap/parser.rs @@ -1,367 +1,37 @@ use crate::streaming::event_parser::{ - common::{read_u64_le, EventMetadata, EventType}, - protocols::pumpswap::{ - discriminators, pump_swap_buy_event_log_decode, pump_swap_create_pool_event_log_decode, - pump_swap_deposit_event_log_decode, pump_swap_sell_event_log_decode, - pump_swap_withdraw_event_log_decode, PumpSwapBuyEvent, PumpSwapCreatePoolEvent, - PumpSwapDepositEvent, PumpSwapSellEvent, PumpSwapWithdrawEvent, - }, - DexEvent, + common::EventMetadata, core::EventDispatcher, DexEvent, Protocol, }; use solana_sdk::pubkey::Pubkey; -/// PumpSwap程序ID -pub const PUMPSWAP_PROGRAM_ID: Pubkey = - solana_sdk::pubkey!("pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA"); +pub use sol_parser_sdk::instr::program_ids::PUMPSWAP_PROGRAM_ID; -/// 解析 PumpSwap instruction data -/// -/// 根据判别器路由到具体的 instruction 解析函数 pub fn parse_pumpswap_instruction_data( discriminator: &[u8], data: &[u8], accounts: &[Pubkey], metadata: EventMetadata, ) -> Option { - match discriminator { - discriminators::BUY_IX => parse_buy_instruction(data, accounts, metadata), - discriminators::BUY_EXACT_QUOTE_IN_IX => { - parse_buy_exact_quote_in_instruction(data, accounts, metadata) - } - discriminators::SELL_IX => parse_sell_instruction(data, accounts, metadata), - discriminators::CREATE_POOL_IX => parse_create_pool_instruction(data, accounts, metadata), - discriminators::DEPOSIT_IX => parse_deposit_instruction(data, accounts, metadata), - discriminators::WITHDRAW_IX => parse_withdraw_instruction(data, accounts, metadata), - _ => None, - } + EventDispatcher::dispatch_instruction( + Protocol::PumpSwap, + discriminator, + data, + accounts, + metadata, + ) } -/// 解析 PumpSwap inner instruction data -/// -/// 根据判别器路由到具体的 inner instruction 解析函数 pub fn parse_pumpswap_inner_instruction_data( discriminator: &[u8], data: &[u8], metadata: EventMetadata, ) -> Option { - match discriminator { - discriminators::BUY_EVENT => parse_buy_inner_instruction(data, metadata), - discriminators::SELL_EVENT => parse_sell_inner_instruction(data, metadata), - discriminators::CREATE_POOL_EVENT => parse_create_pool_inner_instruction(data, metadata), - discriminators::DEPOSIT_EVENT => parse_deposit_inner_instruction(data, metadata), - discriminators::WITHDRAW_EVENT => parse_withdraw_inner_instruction(data, metadata), - _ => None, - } + EventDispatcher::dispatch_inner_instruction(Protocol::PumpSwap, discriminator, data, metadata) } -/// 解析 PumpSwap 账户数据 -/// -/// 根据判别器路由到具体的账户解析函数 pub fn parse_pumpswap_account_data( discriminator: &[u8], account: &crate::streaming::grpc::AccountPretty, - metadata: crate::streaming::event_parser::common::EventMetadata, -) -> Option { - match discriminator { - discriminators::GLOBAL_CONFIG_ACCOUNT => { - crate::streaming::event_parser::protocols::pumpswap::types::global_config_parser( - account, metadata, - ) - } - discriminators::POOL_ACCOUNT => { - crate::streaming::event_parser::protocols::pumpswap::types::pool_parser( - account, metadata, - ) - } - _ => None, - } -} - -/// 解析买入日志事件 -fn parse_buy_inner_instruction(data: &[u8], metadata: EventMetadata) -> Option { - // Note: event_type will be set by instruction parser - if let Some(event) = pump_swap_buy_event_log_decode(data) { - Some(DexEvent::PumpSwapBuyEvent(PumpSwapBuyEvent { metadata, ..event })) - } else { - None - } -} - -/// 解析卖出日志事件 -fn parse_sell_inner_instruction(data: &[u8], metadata: EventMetadata) -> Option { - // Note: event_type will be set by instruction parser - if let Some(event) = pump_swap_sell_event_log_decode(data) { - Some(DexEvent::PumpSwapSellEvent(PumpSwapSellEvent { metadata, ..event })) - } else { - None - } -} - -/// 解析创建池子日志事件 -fn parse_create_pool_inner_instruction(data: &[u8], metadata: EventMetadata) -> Option { - // Note: event_type will be set by instruction parser - if let Some(event) = pump_swap_create_pool_event_log_decode(data) { - Some(DexEvent::PumpSwapCreatePoolEvent(PumpSwapCreatePoolEvent { metadata, ..event })) - } else { - None - } -} - -/// 解析存款日志事件 -fn parse_deposit_inner_instruction(data: &[u8], metadata: EventMetadata) -> Option { - // Note: event_type will be set by instruction parser - if let Some(event) = pump_swap_deposit_event_log_decode(data) { - Some(DexEvent::PumpSwapDepositEvent(PumpSwapDepositEvent { metadata, ..event })) - } else { - None - } -} - -/// 解析提款日志事件 -fn parse_withdraw_inner_instruction(data: &[u8], metadata: EventMetadata) -> Option { - // Note: event_type will be set by instruction parser - if let Some(event) = pump_swap_withdraw_event_log_decode(data) { - Some(DexEvent::PumpSwapWithdrawEvent(PumpSwapWithdrawEvent { metadata, ..event })) - } else { - None - } -} - -/// 解析买入指令事件 -/// Buy 指令共 23 个固定账户(与 idl/pump_amm.json 一致): -/// 0: pool, 1: user, 2: global_config, 3: base_mint, 4: quote_mint, 5: user_base_token_account, -/// 6: user_quote_token_account, 7: pool_base_token_account, 8: pool_quote_token_account, -/// 9: protocol_fee_recipient, 10: protocol_fee_recipient_token_account, 11: base_token_program, -/// 12: quote_token_program, 13: system_program, 14: associated_token_program, 15: event_authority, -/// 16: program, 17: coin_creator_vault_ata, 18: coin_creator_vault_authority, -/// 19: global_volume_accumulator, 20: user_volume_accumulator, 21: fee_config, 22: fee_program. -fn parse_buy_instruction( - data: &[u8], - accounts: &[Pubkey], - mut metadata: EventMetadata, + metadata: EventMetadata, ) -> Option { - metadata.event_type = EventType::PumpSwapBuy; - - if data.len() < 16 || accounts.len() < 13 { - return None; - } - - let base_amount_out = read_u64_le(data, 0)?; - let max_quote_amount_in = read_u64_le(data, 8)?; - - Some(DexEvent::PumpSwapBuyEvent(PumpSwapBuyEvent { - metadata, - base_amount_out, - max_quote_amount_in, - ix_name: "buy".to_string(), - pool: accounts[0], - user: accounts[1], - base_mint: accounts[3], - quote_mint: accounts[4], - user_base_token_account: accounts[5], - user_quote_token_account: accounts[6], - pool_base_token_account: accounts[7], - pool_quote_token_account: accounts[8], - protocol_fee_recipient: accounts[9], - protocol_fee_recipient_token_account: accounts[10], - base_token_program: accounts[11], - quote_token_program: accounts[12], - coin_creator_vault_ata: accounts.get(17).copied().unwrap_or_default(), - coin_creator_vault_authority: accounts.get(18).copied().unwrap_or_default(), - ..Default::default() - })) -} - -/// 解析 buy_exact_quote_in 指令事件 -/// 账户布局与 buy 相同,共 23 个固定账户(0–22,17/18 为 coin_creator_vault_ata / coin_creator_vault_authority)。 -/// 参数顺序与 buy 不同: spendable_quote_in (SOL), min_base_amount_out (token). -fn parse_buy_exact_quote_in_instruction( - data: &[u8], - accounts: &[Pubkey], - mut metadata: EventMetadata, -) -> Option { - metadata.event_type = EventType::PumpSwapBuy; - - if data.len() < 16 || accounts.len() < 13 { - return None; - } - - // 注意:buy_exact_quote_in 的参数顺序是先 quote (SOL) 再 base (token) - let spendable_quote_in = read_u64_le(data, 0)?; - let min_base_amount_out = read_u64_le(data, 8)?; - - Some(DexEvent::PumpSwapBuyEvent(PumpSwapBuyEvent { - metadata, - base_amount_out: min_base_amount_out, - max_quote_amount_in: spendable_quote_in, - min_base_amount_out, - ix_name: "buy_exact_quote_in".to_string(), - pool: accounts[0], - user: accounts[1], - base_mint: accounts[3], - quote_mint: accounts[4], - user_base_token_account: accounts[5], - user_quote_token_account: accounts[6], - pool_base_token_account: accounts[7], - pool_quote_token_account: accounts[8], - protocol_fee_recipient: accounts[9], - protocol_fee_recipient_token_account: accounts[10], - base_token_program: accounts[11], - quote_token_program: accounts[12], - coin_creator_vault_ata: accounts.get(17).copied().unwrap_or_default(), - coin_creator_vault_authority: accounts.get(18).copied().unwrap_or_default(), - ..Default::default() - })) -} - -/// 解析卖出指令事件 -/// Sell 指令共 21 个固定账户(与 idl/pump_amm.json 一致): -/// 0: pool, 1: user, 2: global_config, 3: base_mint, 4: quote_mint, 5: user_base_token_account, -/// 6: user_quote_token_account, 7: pool_base_token_account, 8: pool_quote_token_account, -/// 9: protocol_fee_recipient, 10: protocol_fee_recipient_token_account, 11: base_token_program, -/// 12: quote_token_program, 13: system_program, 14: associated_token_program, 15: event_authority, -/// 16: program, 17: coin_creator_vault_ata, 18: coin_creator_vault_authority, 19: fee_config, 20: fee_program. -fn parse_sell_instruction( - data: &[u8], - accounts: &[Pubkey], - mut metadata: EventMetadata, -) -> Option { - metadata.event_type = EventType::PumpSwapSell; - - if data.len() < 16 || accounts.len() < 13 { - return None; - } - - let base_amount_in = read_u64_le(data, 0)?; - let min_quote_amount_out = read_u64_le(data, 8)?; - - Some(DexEvent::PumpSwapSellEvent(PumpSwapSellEvent { - metadata, - base_amount_in, - min_quote_amount_out, - pool: accounts[0], - user: accounts[1], - base_mint: accounts[3], - quote_mint: accounts[4], - user_base_token_account: accounts[5], - user_quote_token_account: accounts[6], - pool_base_token_account: accounts[7], - pool_quote_token_account: accounts[8], - protocol_fee_recipient: accounts[9], - protocol_fee_recipient_token_account: accounts[10], - base_token_program: accounts[11], - quote_token_program: accounts[12], - coin_creator_vault_ata: accounts.get(17).copied().unwrap_or_default(), - coin_creator_vault_authority: accounts.get(18).copied().unwrap_or_default(), - ..Default::default() - })) -} - -/// 解析创建池子指令事件 -fn parse_create_pool_instruction( - data: &[u8], - accounts: &[Pubkey], - mut metadata: EventMetadata, -) -> Option { - metadata.event_type = EventType::PumpSwapCreatePool; - - if data.len() < 18 || accounts.len() < 11 { - return None; - } - - let index = u16::from_le_bytes(data[0..2].try_into().ok()?); - let base_amount_in = u64::from_le_bytes(data[2..10].try_into().ok()?); - let quote_amount_in = u64::from_le_bytes(data[10..18].try_into().ok()?); - let coin_creator = if data.len() >= 50 { - Pubkey::new_from_array(data[18..50].try_into().ok()?) - } else { - Pubkey::default() - }; - - Some(DexEvent::PumpSwapCreatePoolEvent(PumpSwapCreatePoolEvent { - metadata, - index, - base_amount_in, - quote_amount_in, - pool: accounts[0], - creator: accounts[2], - base_mint: accounts[3], - quote_mint: accounts[4], - lp_mint: accounts[5], - user_base_token_account: accounts[6], - user_quote_token_account: accounts[7], - user_pool_token_account: accounts[8], - pool_base_token_account: accounts[9], - pool_quote_token_account: accounts[10], - coin_creator, - ..Default::default() - })) -} - -/// 解析存款指令事件 -fn parse_deposit_instruction( - data: &[u8], - accounts: &[Pubkey], - mut metadata: EventMetadata, -) -> Option { - metadata.event_type = EventType::PumpSwapDeposit; - - if data.len() < 24 || accounts.len() < 11 { - return None; - } - - let lp_token_amount_out = u64::from_le_bytes(data[0..8].try_into().ok()?); - let max_base_amount_in = u64::from_le_bytes(data[8..16].try_into().ok()?); - let max_quote_amount_in = u64::from_le_bytes(data[16..24].try_into().ok()?); - - Some(DexEvent::PumpSwapDepositEvent(PumpSwapDepositEvent { - metadata, - lp_token_amount_out, - max_base_amount_in, - max_quote_amount_in, - pool: accounts[0], - user: accounts[2], - base_mint: accounts[3], - quote_mint: accounts[4], - user_base_token_account: accounts[6], - user_quote_token_account: accounts[7], - user_pool_token_account: accounts[8], - pool_base_token_account: accounts[9], - pool_quote_token_account: accounts[10], - ..Default::default() - })) -} - -/// 解析提款指令事件 -fn parse_withdraw_instruction( - data: &[u8], - accounts: &[Pubkey], - mut metadata: EventMetadata, -) -> Option { - metadata.event_type = EventType::PumpSwapWithdraw; - - if data.len() < 24 || accounts.len() < 11 { - return None; - } - - let lp_token_amount_in = u64::from_le_bytes(data[0..8].try_into().ok()?); - let min_base_amount_out = u64::from_le_bytes(data[8..16].try_into().ok()?); - let min_quote_amount_out = u64::from_le_bytes(data[16..24].try_into().ok()?); - - Some(DexEvent::PumpSwapWithdrawEvent(PumpSwapWithdrawEvent { - metadata, - lp_token_amount_in, - min_base_amount_out, - min_quote_amount_out, - pool: accounts[0], - user: accounts[2], - base_mint: accounts[3], - quote_mint: accounts[4], - user_base_token_account: accounts[6], - user_quote_token_account: accounts[7], - user_pool_token_account: accounts[8], - pool_base_token_account: accounts[9], - pool_quote_token_account: accounts[10], - ..Default::default() - })) + EventDispatcher::dispatch_account(Protocol::PumpSwap, discriminator, account, metadata) } diff --git a/src/streaming/event_parser/protocols/pumpswap/types.rs b/src/streaming/event_parser/protocols/pumpswap/types.rs index b2e2d94..e56a2a3 100644 --- a/src/streaming/event_parser/protocols/pumpswap/types.rs +++ b/src/streaming/event_parser/protocols/pumpswap/types.rs @@ -2,15 +2,6 @@ use borsh::BorshDeserialize; use serde::{Deserialize, Serialize}; use solana_sdk::pubkey::Pubkey; -use crate::streaming::{ - event_parser::{ - common::{EventMetadata, EventType}, - protocols::pumpswap::{PumpSwapGlobalConfigAccountEvent, PumpSwapPoolAccountEvent}, - DexEvent, - }, - grpc::AccountPretty, -}; - #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)] pub struct GlobalConfig { pub admin: Pubkey, @@ -28,37 +19,6 @@ pub struct GlobalConfig { pub const GLOBAL_CONFIG_SIZE: usize = 32 + 8 + 8 + 1 + 32 * 8 + 8 + 32 + 32 + 32 + 1 + 32 * 7; -pub fn global_config_decode(data: &[u8]) -> Option { - if data.len() < GLOBAL_CONFIG_SIZE { - return None; - } - borsh::from_slice::(&data[..GLOBAL_CONFIG_SIZE]).ok() -} - -pub fn global_config_parser( - account: &AccountPretty, - mut metadata: EventMetadata, -) -> Option { - metadata.event_type = EventType::AccountPumpSwapGlobalConfig; - - if account.data.len() < GLOBAL_CONFIG_SIZE + 8 { - return None; - } - if let Some(config) = global_config_decode(&account.data[8..GLOBAL_CONFIG_SIZE + 8]) { - Some(DexEvent::PumpSwapGlobalConfigAccountEvent(PumpSwapGlobalConfigAccountEvent { - metadata, - pubkey: account.pubkey, - executable: account.executable, - lamports: account.lamports, - owner: account.owner, - rent_epoch: account.rent_epoch, - global_config: config, - })) - } else { - None - } -} - #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)] pub struct Pool { pub pool_bump: u8, @@ -83,108 +43,3 @@ pub const POOL_BODY_LEGACY: usize = 1 + 2 + 32 * 6 + 8 + 32 + 1; pub const POOL_BODY: usize = POOL_BODY_LEGACY + 1 + 7; pub const POOL_SIZE: usize = POOL_BODY; - -pub fn pool_decode(data: &[u8]) -> Option { - if data.len() >= POOL_BODY { - return borsh::from_slice::(&data[..POOL_BODY]).ok(); - } - if data.len() < POOL_BODY_LEGACY { - return None; - } - let legacy = borsh::from_slice::(&data[..POOL_BODY_LEGACY]).ok()?; - Some(legacy.into()) -} - -/// Pre-cashback on-chain layout (Borsh-compatible prefix of `Pool`). -#[derive(Clone, Debug, BorshDeserialize)] -struct PoolLegacy { - pub pool_bump: u8, - pub index: u16, - pub creator: Pubkey, - pub base_mint: Pubkey, - pub quote_mint: Pubkey, - pub lp_mint: Pubkey, - pub pool_base_token_account: Pubkey, - pub pool_quote_token_account: Pubkey, - pub lp_supply: u64, - pub coin_creator: Pubkey, - pub is_mayhem_mode: bool, -} - -impl From for Pool { - fn from(p: PoolLegacy) -> Self { - Pool { - pool_bump: p.pool_bump, - index: p.index, - creator: p.creator, - base_mint: p.base_mint, - quote_mint: p.quote_mint, - lp_mint: p.lp_mint, - pool_base_token_account: p.pool_base_token_account, - pool_quote_token_account: p.pool_quote_token_account, - lp_supply: p.lp_supply, - coin_creator: p.coin_creator, - is_mayhem_mode: p.is_mayhem_mode, - is_cashback_coin: false, - reserved: [0u8; 7], - } - } -} - -pub fn pool_parser(account: &AccountPretty, mut metadata: EventMetadata) -> Option { - metadata.event_type = EventType::AccountPumpSwapPool; - - let body = account.data.get(8..)?; - if body.len() < POOL_BODY_LEGACY { - return None; - } - if let Some(pool) = pool_decode(body) { - Some(DexEvent::PumpSwapPoolAccountEvent(PumpSwapPoolAccountEvent { - metadata, - pubkey: account.pubkey, - executable: account.executable, - lamports: account.lamports, - owner: account.owner, - rent_epoch: account.rent_epoch, - pool, - })) - } else { - None - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn pool_decode_legacy_and_extended() { - let keys: Vec = (0..6).map(|_| Pubkey::new_unique()).collect(); - let coin = Pubkey::new_unique(); - let mut legacy = Vec::new(); - legacy.push(9u8); - legacy.extend_from_slice(&7u16.to_le_bytes()); - for k in &keys { - legacy.extend_from_slice(k.as_ref()); - } - legacy.extend_from_slice(&99u64.to_le_bytes()); - legacy.extend_from_slice(coin.as_ref()); - legacy.push(1u8); - - let p = pool_decode(&legacy).expect("legacy"); - assert_eq!(p.pool_bump, 9); - assert_eq!(p.index, 7); - assert_eq!(p.lp_supply, 99); - assert!(p.is_mayhem_mode); - assert!(!p.is_cashback_coin); - assert_eq!(p.reserved, [0u8; 7]); - - let mut ext = legacy.clone(); - ext.push(1u8); - ext.extend_from_slice(&[2u8, 3, 4, 5, 6, 7, 8]); - - let p2 = pool_decode(&ext).expect("extended"); - assert!(p2.is_cashback_coin); - assert_eq!(p2.reserved, [2, 3, 4, 5, 6, 7, 8]); - } -} diff --git a/src/streaming/event_parser/protocols/raydium_amm_v4/parser.rs b/src/streaming/event_parser/protocols/raydium_amm_v4/parser.rs old mode 100755 new mode 100644 index 090366f..fd3837c --- a/src/streaming/event_parser/protocols/raydium_amm_v4/parser.rs +++ b/src/streaming/event_parser/protocols/raydium_amm_v4/parser.rs @@ -1,323 +1,42 @@ use crate::streaming::event_parser::{ - common::{read_u64_le, EventMetadata, EventType}, - protocols::raydium_amm_v4::{ - discriminators, RaydiumAmmV4DepositEvent, RaydiumAmmV4Initialize2Event, - RaydiumAmmV4SwapEvent, RaydiumAmmV4WithdrawEvent, RaydiumAmmV4WithdrawPnlEvent, - }, - DexEvent, + common::EventMetadata, core::EventDispatcher, DexEvent, Protocol, }; use solana_sdk::pubkey::Pubkey; -/// Raydium AMM V4程序ID -pub const RAYDIUM_AMM_V4_PROGRAM_ID: Pubkey = - solana_sdk::pubkey!("675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8"); +pub use sol_parser_sdk::instr::program_ids::RAYDIUM_AMM_V4_PROGRAM_ID; -/// 解析 Raydium AMM V4 instruction data -/// -/// 根据判别器路由到具体的 instruction 解析函数 pub fn parse_raydium_amm_v4_instruction_data( discriminator: &[u8], data: &[u8], accounts: &[Pubkey], metadata: EventMetadata, ) -> Option { - match discriminator { - discriminators::SWAP_BASE_IN => parse_swap_base_input_instruction(data, accounts, metadata), - discriminators::SWAP_BASE_OUT => { - parse_swap_base_output_instruction(data, accounts, metadata) - } - discriminators::DEPOSIT => parse_deposit_instruction(data, accounts, metadata), - discriminators::INITIALIZE2 => parse_initialize2_instruction(data, accounts, metadata), - discriminators::WITHDRAW => parse_withdraw_instruction(data, accounts, metadata), - discriminators::WITHDRAW_PNL => parse_withdraw_pnl_instruction(data, accounts, metadata), - _ => None, - } + EventDispatcher::dispatch_instruction( + Protocol::RaydiumAmmV4, + discriminator, + data, + accounts, + metadata, + ) } -/// 解析 Raydium AMM V4 inner instruction data -/// -/// Raydium AMM V4 没有 inner instruction 事件 pub fn parse_raydium_amm_v4_inner_instruction_data( - _discriminator: &[u8], - _data: &[u8], - _metadata: EventMetadata, + discriminator: &[u8], + data: &[u8], + metadata: EventMetadata, ) -> Option { - None + EventDispatcher::dispatch_inner_instruction( + Protocol::RaydiumAmmV4, + discriminator, + data, + metadata, + ) } -/// 解析 Raydium AMM V4 账户数据 -/// -/// 根据判别器路由到具体的账户解析函数 pub fn parse_raydium_amm_v4_account_data( discriminator: &[u8], account: &crate::streaming::grpc::AccountPretty, - metadata: crate::streaming::event_parser::common::EventMetadata, -) -> Option { - match discriminator { - discriminators::AMM_INFO => { - crate::streaming::event_parser::protocols::raydium_amm_v4::types::amm_info_parser( - account, metadata, - ) - } - _ => None, - } -} - -/// 解析提现指令事件 -fn parse_withdraw_pnl_instruction( - _data: &[u8], - accounts: &[Pubkey], - mut metadata: EventMetadata, + metadata: EventMetadata, ) -> Option { - metadata.event_type = EventType::RaydiumAmmV4WithdrawPnl; - - if accounts.len() < 17 { - return None; - } - - Some(DexEvent::RaydiumAmmV4WithdrawPnlEvent(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], - mut metadata: EventMetadata, -) -> Option { - metadata.event_type = EventType::RaydiumAmmV4Withdraw; - - if data.len() < 8 || accounts.len() < 22 { - return None; - } - let amount = read_u64_le(data, 0)?; - - Some(DexEvent::RaydiumAmmV4WithdrawEvent(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], - mut metadata: EventMetadata, -) -> Option { - metadata.event_type = EventType::RaydiumAmmV4Initialize2; - - if data.len() < 25 || accounts.len() < 21 { - return None; - } - let nonce = data[0]; - let open_time = read_u64_le(data, 1)?; - let init_pc_amount = read_u64_le(data, 9)?; - let init_coin_amount = read_u64_le(data, 17)?; - - Some(DexEvent::RaydiumAmmV4Initialize2Event(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], - mut metadata: EventMetadata, -) -> Option { - metadata.event_type = EventType::RaydiumAmmV4Deposit; - - if data.len() < 24 || accounts.len() < 14 { - return None; - } - let max_coin_amount = read_u64_le(data, 0)?; - let max_pc_amount = read_u64_le(data, 8)?; - let base_side = read_u64_le(data, 16)?; - - Some(DexEvent::RaydiumAmmV4DepositEvent(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], - mut metadata: EventMetadata, -) -> Option { - metadata.event_type = EventType::RaydiumAmmV4SwapBaseOut; - - if data.len() < 16 || accounts.len() < 17 { - return None; - } - let max_amount_in = read_u64_le(data, 0)?; - let amount_out = read_u64_le(data, 8)?; - - let mut accounts = accounts.to_vec(); - if accounts.len() == 17 { - // 添加一个默认的 Pubkey 作为 amm_target_orders 的占位符 - // 因为在某些情况下,amm_target_orders 可能是可选的 - accounts.insert(4, Pubkey::default()); - } - - Some(DexEvent::RaydiumAmmV4SwapEvent(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], - mut metadata: EventMetadata, -) -> Option { - metadata.event_type = EventType::RaydiumAmmV4SwapBaseIn; - - if data.len() < 16 || accounts.len() < 17 { - return None; - } - let amount_in = read_u64_le(data, 0)?; - let minimum_amount_out = read_u64_le(data, 8)?; - - let mut accounts = accounts.to_vec(); - if accounts.len() == 17 { - // 添加一个默认的 Pubkey 作为 amm_target_orders 的占位符 - // 因为在某些情况下,amm_target_orders 可能是可选的 - accounts.insert(4, Pubkey::default()); - } - - Some(DexEvent::RaydiumAmmV4SwapEvent(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() - })) + EventDispatcher::dispatch_account(Protocol::RaydiumAmmV4, discriminator, account, metadata) } diff --git a/src/streaming/event_parser/protocols/raydium_amm_v4/types.rs b/src/streaming/event_parser/protocols/raydium_amm_v4/types.rs index 073dac6..49bdcd5 100644 --- a/src/streaming/event_parser/protocols/raydium_amm_v4/types.rs +++ b/src/streaming/event_parser/protocols/raydium_amm_v4/types.rs @@ -2,15 +2,6 @@ use borsh::BorshDeserialize; use serde::{Deserialize, Serialize}; use solana_sdk::pubkey::Pubkey; -use crate::streaming::{ - event_parser::{ - common::{EventMetadata, EventType}, - protocols::raydium_amm_v4::RaydiumAmmV4AmmInfoAccountEvent, - DexEvent, - }, - grpc::AccountPretty, -}; - #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)] pub struct Fees { pub min_separate_numerator: u64, @@ -80,34 +71,6 @@ pub struct AmmInfo { pub const AMM_INFO_SIZE: usize = 752; -pub fn amm_info_decode(data: &[u8]) -> Option { - if data.len() < AMM_INFO_SIZE { - return None; - } - borsh::from_slice::(&data[..AMM_INFO_SIZE]).ok() -} - -pub fn amm_info_parser(account: &AccountPretty, mut metadata: EventMetadata) -> Option { - metadata.event_type = EventType::AccountRaydiumAmmV4AmmInfo; - - if account.data.len() < AMM_INFO_SIZE { - return None; - } - if let Some(amm_info) = amm_info_decode(&account.data[..AMM_INFO_SIZE]) { - Some(DexEvent::RaydiumAmmV4AmmInfoAccountEvent(RaydiumAmmV4AmmInfoAccountEvent { - metadata, - pubkey: account.pubkey, - executable: account.executable, - lamports: account.lamports, - owner: account.owner, - rent_epoch: account.rent_epoch, - amm_info: amm_info, - })) - } else { - None - } -} - #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)] pub struct MarketState { pub padding: [u8; 5], @@ -135,10 +98,3 @@ pub struct MarketState { } pub const MARKET_STATE_SIZE: usize = 388; - -pub fn market_state_decode(data: &[u8]) -> Option { - if data.len() < MARKET_STATE_SIZE { - return None; - } - borsh::from_slice::(&data[..MARKET_STATE_SIZE]).ok() -} diff --git a/src/streaming/event_parser/protocols/raydium_clmm/parser.rs b/src/streaming/event_parser/protocols/raydium_clmm/parser.rs old mode 100755 new mode 100644 index 41e9bc0..96af864 --- a/src/streaming/event_parser/protocols/raydium_clmm/parser.rs +++ b/src/streaming/event_parser/protocols/raydium_clmm/parser.rs @@ -1,384 +1,42 @@ use crate::streaming::event_parser::{ - common::{ - read_i32_le, read_option_bool, read_u128_le, read_u64_le, read_u8_le, EventMetadata, - EventType, - }, - protocols::raydium_clmm::{ - discriminators, RaydiumClmmClosePositionEvent, RaydiumClmmCreatePoolEvent, - RaydiumClmmDecreaseLiquidityV2Event, RaydiumClmmIncreaseLiquidityV2Event, - RaydiumClmmOpenPositionV2Event, RaydiumClmmOpenPositionWithToken22NftEvent, - RaydiumClmmSwapEvent, RaydiumClmmSwapV2Event, - }, - DexEvent, + common::EventMetadata, core::EventDispatcher, DexEvent, Protocol, }; use solana_sdk::pubkey::Pubkey; -/// Raydium CLMM程序ID -pub const RAYDIUM_CLMM_PROGRAM_ID: Pubkey = - solana_sdk::pubkey!("CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK"); +pub use sol_parser_sdk::instr::program_ids::RAYDIUM_CLMM_PROGRAM_ID; -/// 解析 Raydium CLMM instruction data -/// -/// 根据判别器路由到具体的 instruction 解析函数 pub fn parse_raydium_clmm_instruction_data( discriminator: &[u8], data: &[u8], accounts: &[Pubkey], metadata: EventMetadata, ) -> Option { - match discriminator { - discriminators::SWAP => parse_swap_instruction(data, accounts, metadata), - discriminators::SWAP_V2 => parse_swap_v2_instruction(data, accounts, metadata), - discriminators::CLOSE_POSITION => { - parse_close_position_instruction(data, accounts, metadata) - } - discriminators::DECREASE_LIQUIDITY_V2 => { - parse_decrease_liquidity_v2_instruction(data, accounts, metadata) - } - discriminators::CREATE_POOL => parse_create_pool_instruction(data, accounts, metadata), - discriminators::INCREASE_LIQUIDITY_V2 => { - parse_increase_liquidity_v2_instruction(data, accounts, metadata) - } - discriminators::OPEN_POSITION_WITH_TOKEN_22_NFT => { - parse_open_position_with_token_22_nft_instruction(data, accounts, metadata) - } - discriminators::OPEN_POSITION_V2 => { - parse_open_position_v2_instruction(data, accounts, metadata) - } - _ => None, - } + EventDispatcher::dispatch_instruction( + Protocol::RaydiumClmm, + discriminator, + data, + accounts, + metadata, + ) } -/// 解析 Raydium CLMM inner instruction data -/// -/// Raydium CLMM 没有 inner instruction 事件 pub fn parse_raydium_clmm_inner_instruction_data( - _discriminator: &[u8], - _data: &[u8], - _metadata: EventMetadata, + discriminator: &[u8], + data: &[u8], + metadata: EventMetadata, ) -> Option { - None + EventDispatcher::dispatch_inner_instruction( + Protocol::RaydiumClmm, + discriminator, + data, + metadata, + ) } -/// 解析 Raydium CLMM 账户数据 -/// -/// 根据判别器路由到具体的账户解析函数 pub fn parse_raydium_clmm_account_data( discriminator: &[u8], account: &crate::streaming::grpc::AccountPretty, - metadata: crate::streaming::event_parser::common::EventMetadata, -) -> Option { - match discriminator { - discriminators::AMM_CONFIG => { - crate::streaming::event_parser::protocols::raydium_clmm::types::amm_config_parser( - account, metadata, - ) - } - discriminators::POOL_STATE => { - crate::streaming::event_parser::protocols::raydium_clmm::types::pool_state_parser( - account, metadata, - ) - } - discriminators::TICK_ARRAY_STATE => { - crate::streaming::event_parser::protocols::raydium_clmm::types::tick_array_state_parser( - account, metadata, - ) - } - _ => None, - } -} - -/// 解析打开仓位V2指令事件 -fn parse_open_position_v2_instruction( - data: &[u8], - accounts: &[Pubkey], - mut metadata: EventMetadata, + metadata: EventMetadata, ) -> Option { - metadata.event_type = EventType::RaydiumClmmOpenPositionV2; - - if data.len() < 51 || accounts.len() < 22 { - return None; - } - Some(DexEvent::RaydiumClmmOpenPositionV2Event(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], - mut metadata: EventMetadata, -) -> Option { - metadata.event_type = EventType::RaydiumClmmOpenPositionWithToken22Nft; - - if data.len() < 51 || accounts.len() < 20 { - return None; - } - Some(DexEvent::RaydiumClmmOpenPositionWithToken22NftEvent( - 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], - mut metadata: EventMetadata, -) -> Option { - metadata.event_type = EventType::RaydiumClmmIncreaseLiquidityV2; - - if data.len() < 34 || accounts.len() < 15 { - return None; - } - Some(DexEvent::RaydiumClmmIncreaseLiquidityV2Event(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], - mut metadata: EventMetadata, -) -> Option { - metadata.event_type = EventType::RaydiumClmmCreatePool; - - if data.len() < 24 || accounts.len() < 13 { - return None; - } - Some(DexEvent::RaydiumClmmCreatePoolEvent(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], - mut metadata: EventMetadata, -) -> Option { - metadata.event_type = EventType::RaydiumClmmDecreaseLiquidityV2; - - if data.len() < 32 || accounts.len() < 16 { - return None; - } - Some(DexEvent::RaydiumClmmDecreaseLiquidityV2Event(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], - mut metadata: EventMetadata, -) -> Option { - metadata.event_type = EventType::RaydiumClmmClosePosition; - - if accounts.len() < 6 { - return None; - } - Some(DexEvent::RaydiumClmmClosePositionEvent(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], - mut metadata: EventMetadata, -) -> Option { - metadata.event_type = EventType::RaydiumClmmSwap; - - if data.len() < 33 || accounts.len() < 10 { - return None; - } - - let amount = read_u64_le(data, 0)?; - let other_amount_threshold = read_u64_le(data, 8)?; - let sqrt_price_limit_x64 = read_u128_le(data, 16)?; - let is_base_input = read_u8_le(data, 32)?; - - Some(DexEvent::RaydiumClmmSwapEvent(RaydiumClmmSwapEvent { - metadata, - amount, - other_amount_threshold, - sqrt_price_limit_x64, - is_base_input: is_base_input == 1, - payer: accounts[0], - amm_config: accounts[1], - pool_state: accounts[2], - input_token_account: accounts[3], - output_token_account: accounts[4], - input_vault: accounts[5], - output_vault: accounts[6], - observation_state: accounts[7], - token_program: accounts[8], - tick_array: accounts[9], - remaining_accounts: accounts[10..].to_vec(), - })) -} - -fn parse_swap_v2_instruction( - data: &[u8], - accounts: &[Pubkey], - mut metadata: EventMetadata, -) -> Option { - metadata.event_type = EventType::RaydiumClmmSwapV2; - - if data.len() < 33 || accounts.len() < 13 { - return None; - } - - let amount = read_u64_le(data, 0)?; - let other_amount_threshold = read_u64_le(data, 8)?; - let sqrt_price_limit_x64 = read_u128_le(data, 16)?; - let is_base_input = read_u8_le(data, 32)?; - - Some(DexEvent::RaydiumClmmSwapV2Event(RaydiumClmmSwapV2Event { - metadata, - amount, - other_amount_threshold, - sqrt_price_limit_x64, - is_base_input: is_base_input == 1, - payer: accounts[0], - amm_config: accounts[1], - pool_state: accounts[2], - input_token_account: accounts[3], - output_token_account: accounts[4], - input_vault: accounts[5], - output_vault: accounts[6], - observation_state: accounts[7], - token_program: accounts[8], - token_program2022: accounts[9], - memo_program: accounts[10], - input_vault_mint: accounts[11], - output_vault_mint: accounts[12], - remaining_accounts: accounts[13..].to_vec(), - })) + EventDispatcher::dispatch_account(Protocol::RaydiumClmm, discriminator, account, metadata) } diff --git a/src/streaming/event_parser/protocols/raydium_clmm/types.rs b/src/streaming/event_parser/protocols/raydium_clmm/types.rs index 5f3695c..61677cb 100644 --- a/src/streaming/event_parser/protocols/raydium_clmm/types.rs +++ b/src/streaming/event_parser/protocols/raydium_clmm/types.rs @@ -2,18 +2,6 @@ use borsh::BorshDeserialize; use serde::{Deserialize, Serialize}; use solana_sdk::pubkey::Pubkey; -use crate::streaming::{ - event_parser::{ - common::{EventMetadata, EventType}, - protocols::raydium_clmm::{ - RaydiumClmmAmmConfigAccountEvent, RaydiumClmmPoolStateAccountEvent, - RaydiumClmmTickArrayStateAccountEvent, - }, - DexEvent, - }, - grpc::AccountPretty, -}; - #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)] pub struct AmmConfig { pub bump: u8, @@ -30,34 +18,6 @@ pub struct AmmConfig { pub const AMM_CONFIG_SIZE: usize = 1 + 2 + 32 + 4 * 2 + 2 + 4 * 2 + 32 + 8 * 3; -pub fn amm_config_decode(data: &[u8]) -> Option { - if data.len() < AMM_CONFIG_SIZE { - return None; - } - borsh::from_slice::(&data[..AMM_CONFIG_SIZE]).ok() -} - -pub fn amm_config_parser(account: &AccountPretty, mut metadata: EventMetadata) -> Option { - metadata.event_type = EventType::AccountRaydiumClmmAmmConfig; - - if account.data.len() < AMM_CONFIG_SIZE + 8 { - return None; - } - if let Some(amm_config) = amm_config_decode(&account.data[8..AMM_CONFIG_SIZE + 8]) { - Some(DexEvent::RaydiumClmmAmmConfigAccountEvent(RaydiumClmmAmmConfigAccountEvent { - metadata, - pubkey: account.pubkey, - executable: account.executable, - lamports: account.lamports, - owner: account.owner, - rent_epoch: account.rent_epoch, - amm_config: amm_config, - })) - } else { - None - } -} - #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)] pub struct RewardInfo { pub reward_state: u8, @@ -117,34 +77,6 @@ pub struct PoolState { pub const POOL_STATE_SIZE: usize = 1536; -pub fn pool_state_decode(data: &[u8]) -> Option { - if data.len() < POOL_STATE_SIZE { - return None; - } - borsh::from_slice::(&data[..POOL_STATE_SIZE]).ok() -} - -pub fn pool_state_parser(account: &AccountPretty, mut metadata: EventMetadata) -> Option { - metadata.event_type = EventType::AccountRaydiumClmmPoolState; - - if account.data.len() < POOL_STATE_SIZE + 8 { - return None; - } - if let Some(pool_state) = pool_state_decode(&account.data[8..POOL_STATE_SIZE + 8]) { - Some(DexEvent::RaydiumClmmPoolStateAccountEvent(RaydiumClmmPoolStateAccountEvent { - metadata, - pubkey: account.pubkey, - executable: account.executable, - lamports: account.lamports, - owner: account.owner, - rent_epoch: account.rent_epoch, - pool_state: pool_state, - })) - } else { - None - } -} - #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)] pub struct TickState { pub tick: i32, @@ -196,38 +128,3 @@ impl Default for TickArrayState { } pub const TICK_ARRAY_STATE_SIZE: usize = 10232; - -pub fn tick_array_state_decode(data: &[u8]) -> Option { - if data.len() < TICK_ARRAY_STATE_SIZE { - return None; - } - borsh::from_slice::(&data[..TICK_ARRAY_STATE_SIZE]).ok() -} - -pub fn tick_array_state_parser( - account: &AccountPretty, - mut metadata: EventMetadata, -) -> Option { - metadata.event_type = EventType::AccountRaydiumClmmTickArrayState; - - if account.data.len() < TICK_ARRAY_STATE_SIZE + 8 { - return None; - } - if let Some(tick_array_state) = - tick_array_state_decode(&account.data[8..TICK_ARRAY_STATE_SIZE + 8]) - { - Some(DexEvent::RaydiumClmmTickArrayStateAccountEvent( - RaydiumClmmTickArrayStateAccountEvent { - metadata, - pubkey: account.pubkey, - executable: account.executable, - lamports: account.lamports, - owner: account.owner, - rent_epoch: account.rent_epoch, - tick_array_state: tick_array_state, - }, - )) - } else { - None - } -} diff --git a/src/streaming/event_parser/protocols/raydium_cpmm/parser.rs b/src/streaming/event_parser/protocols/raydium_cpmm/parser.rs old mode 100755 new mode 100644 index 633011f..9451d5c --- a/src/streaming/event_parser/protocols/raydium_cpmm/parser.rs +++ b/src/streaming/event_parser/protocols/raydium_cpmm/parser.rs @@ -1,244 +1,42 @@ +use crate::streaming::event_parser::{ + common::EventMetadata, core::EventDispatcher, DexEvent, Protocol, +}; use solana_sdk::pubkey::Pubkey; -use crate::streaming::event_parser::{ - common::{read_u64_le, EventMetadata, EventType}, - protocols::raydium_cpmm::{ - discriminators, RaydiumCpmmDepositEvent, RaydiumCpmmInitializeEvent, RaydiumCpmmSwapEvent, - RaydiumCpmmWithdrawEvent, - }, - DexEvent, -}; +pub use sol_parser_sdk::instr::program_ids::RAYDIUM_CPMM_PROGRAM_ID; -/// Raydium CPMM程序ID -pub const RAYDIUM_CPMM_PROGRAM_ID: Pubkey = - solana_sdk::pubkey!("CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C"); - -/// 解析 Raydium CPMM instruction data -/// -/// 根据判别器路由到具体的 instruction 解析函数 pub fn parse_raydium_cpmm_instruction_data( discriminator: &[u8], data: &[u8], accounts: &[Pubkey], metadata: EventMetadata, ) -> Option { - match discriminator { - discriminators::SWAP_BASE_IN => parse_swap_base_input_instruction(data, accounts, metadata), - discriminators::SWAP_BASE_OUT => { - parse_swap_base_output_instruction(data, accounts, metadata) - } - discriminators::DEPOSIT => parse_deposit_instruction(data, accounts, metadata), - discriminators::INITIALIZE => parse_initialize_instruction(data, accounts, metadata), - discriminators::WITHDRAW => parse_withdraw_instruction(data, accounts, metadata), - _ => None, - } + EventDispatcher::dispatch_instruction( + Protocol::RaydiumCpmm, + discriminator, + data, + accounts, + metadata, + ) } -/// 解析 Raydium CPMM inner instruction data -/// -/// Raydium CPMM 没有 inner instruction 事件 pub fn parse_raydium_cpmm_inner_instruction_data( - _discriminator: &[u8], - _data: &[u8], - _metadata: EventMetadata, + discriminator: &[u8], + data: &[u8], + metadata: EventMetadata, ) -> Option { - None + EventDispatcher::dispatch_inner_instruction( + Protocol::RaydiumCpmm, + discriminator, + data, + metadata, + ) } -/// 解析 Raydium CPMM 账户数据 -/// -/// 根据判别器路由到具体的账户解析函数 pub fn parse_raydium_cpmm_account_data( discriminator: &[u8], account: &crate::streaming::grpc::AccountPretty, - metadata: crate::streaming::event_parser::common::EventMetadata, -) -> Option { - match discriminator { - discriminators::AMM_CONFIG => { - crate::streaming::event_parser::protocols::raydium_cpmm::types::amm_config_parser( - account, metadata, - ) - } - discriminators::POOL_STATE => { - crate::streaming::event_parser::protocols::raydium_cpmm::types::pool_state_parser( - account, metadata, - ) - } - _ => None, - } -} - -/// 解析提款指令事件 -fn parse_withdraw_instruction( - data: &[u8], - accounts: &[Pubkey], - mut metadata: EventMetadata, + metadata: EventMetadata, ) -> Option { - metadata.event_type = EventType::RaydiumCpmmWithdraw; - - if data.len() < 24 || accounts.len() < 14 { - return None; - } - Some(DexEvent::RaydiumCpmmWithdrawEvent(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], - token_0_account: accounts[4], - token_1_account: accounts[5], - token_0_vault: accounts[6], - token_1_vault: accounts[7], - token_program: accounts[8], - token_program2022: accounts[9], - vault_0_mint: accounts[10], - vault_1_mint: accounts[11], - lp_mint: accounts[12], - memo_program: accounts[13], - })) -} - -/// 解析初始化指令事件 -fn parse_initialize_instruction( - data: &[u8], - accounts: &[Pubkey], - mut metadata: EventMetadata, -) -> Option { - metadata.event_type = EventType::RaydiumCpmmInitialize; - - if data.len() < 24 || accounts.len() < 20 { - return None; - } - Some(DexEvent::RaydiumCpmmInitializeEvent(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], - token_0_mint: accounts[4], - token_1_mint: accounts[5], - lp_mint: accounts[6], - creator_token_0: accounts[7], - creator_token_1: accounts[8], - creator_lp_token: accounts[9], - token_0_vault: accounts[10], - token_1_vault: accounts[11], - create_pool_fee: accounts[12], - observation_state: accounts[13], - token_program: accounts[14], - token_0_program: accounts[15], - token_1_program: accounts[16], - associated_token_program: accounts[17], - system_program: accounts[18], - rent: accounts[19], - })) -} - -/// 解析存款指令事件 -fn parse_deposit_instruction( - data: &[u8], - accounts: &[Pubkey], - mut metadata: EventMetadata, -) -> Option { - metadata.event_type = EventType::RaydiumCpmmDeposit; - - if data.len() < 24 || accounts.len() < 13 { - return None; - } - Some(DexEvent::RaydiumCpmmDepositEvent(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], - token_0_account: accounts[4], - token_1_account: accounts[5], - token_0_vault: accounts[6], - token_1_vault: accounts[7], - token_program: accounts[8], - token_program2022: accounts[9], - vault_0_mint: accounts[10], - vault_1_mint: accounts[11], - lp_mint: accounts[12], - })) -} - -/// 解析买入指令事件 -fn parse_swap_base_input_instruction( - data: &[u8], - accounts: &[Pubkey], - mut metadata: EventMetadata, -) -> Option { - metadata.event_type = EventType::RaydiumCpmmSwapBaseInput; - - if data.len() < 16 || accounts.len() < 13 { - return None; - } - - let amount_in = read_u64_le(data, 0)?; - let minimum_amount_out = read_u64_le(data, 8)?; - - Some(DexEvent::RaydiumCpmmSwapEvent(RaydiumCpmmSwapEvent { - metadata, - amount_in, - minimum_amount_out, - payer: accounts[0], - authority: accounts[1], - amm_config: accounts[2], - pool_state: accounts[3], - input_token_account: accounts[4], - output_token_account: accounts[5], - input_vault: accounts[6], - output_vault: accounts[7], - input_token_program: accounts[8], - output_token_program: accounts[9], - input_token_mint: accounts[10], - output_token_mint: accounts[11], - observation_state: accounts[12], - ..Default::default() - })) -} - -fn parse_swap_base_output_instruction( - data: &[u8], - accounts: &[Pubkey], - mut metadata: EventMetadata, -) -> Option { - metadata.event_type = EventType::RaydiumCpmmSwapBaseOutput; - - if data.len() < 16 || accounts.len() < 13 { - return None; - } - - let max_amount_in = read_u64_le(data, 0)?; - let amount_out = read_u64_le(data, 8)?; - - Some(DexEvent::RaydiumCpmmSwapEvent(RaydiumCpmmSwapEvent { - metadata, - max_amount_in, - amount_out, - payer: accounts[0], - authority: accounts[1], - amm_config: accounts[2], - pool_state: accounts[3], - input_token_account: accounts[4], - output_token_account: accounts[5], - input_vault: accounts[6], - output_vault: accounts[7], - input_token_program: accounts[8], - output_token_program: accounts[9], - input_token_mint: accounts[10], - output_token_mint: accounts[11], - observation_state: accounts[12], - ..Default::default() - })) + EventDispatcher::dispatch_account(Protocol::RaydiumCpmm, discriminator, account, metadata) } diff --git a/src/streaming/event_parser/protocols/raydium_cpmm/types.rs b/src/streaming/event_parser/protocols/raydium_cpmm/types.rs index 015562e..e110359 100644 --- a/src/streaming/event_parser/protocols/raydium_cpmm/types.rs +++ b/src/streaming/event_parser/protocols/raydium_cpmm/types.rs @@ -2,17 +2,6 @@ use borsh::BorshDeserialize; use serde::{Deserialize, Serialize}; use solana_sdk::pubkey::Pubkey; -use crate::streaming::{ - event_parser::{ - common::{EventMetadata, EventType}, - protocols::raydium_cpmm::{ - RaydiumCpmmAmmConfigAccountEvent, RaydiumCpmmPoolStateAccountEvent, - }, - DexEvent, - }, - grpc::AccountPretty, -}; - #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)] pub struct AmmConfig { pub bump: u8, @@ -30,34 +19,6 @@ pub struct AmmConfig { pub const AMM_CONFIG_SIZE: usize = 228; -pub fn amm_config_decode(data: &[u8]) -> Option { - if data.len() < AMM_CONFIG_SIZE { - return None; - } - borsh::from_slice::(&data[..AMM_CONFIG_SIZE]).ok() -} - -pub fn amm_config_parser(account: &AccountPretty, mut metadata: EventMetadata) -> Option { - metadata.event_type = EventType::AccountRaydiumCpmmAmmConfig; - - if account.data.len() < AMM_CONFIG_SIZE + 8 { - return None; - } - if let Some(amm_config) = amm_config_decode(&account.data[8..AMM_CONFIG_SIZE + 8]) { - Some(DexEvent::RaydiumCpmmAmmConfigAccountEvent(RaydiumCpmmAmmConfigAccountEvent { - metadata, - pubkey: account.pubkey, - executable: account.executable, - lamports: account.lamports, - owner: account.owner, - rent_epoch: account.rent_epoch, - amm_config: amm_config, - })) - } else { - None - } -} - #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)] pub struct PoolState { pub amm_config: Pubkey, @@ -91,31 +52,3 @@ pub struct PoolState { } pub const POOL_STATE_SIZE: usize = 629; - -pub fn pool_state_decode(data: &[u8]) -> Option { - if data.len() < POOL_STATE_SIZE { - return None; - } - borsh::from_slice::(&data[..POOL_STATE_SIZE]).ok() -} - -pub fn pool_state_parser(account: &AccountPretty, mut metadata: EventMetadata) -> Option { - metadata.event_type = EventType::AccountRaydiumCpmmPoolState; - - if account.data.len() < POOL_STATE_SIZE + 8 { - return None; - } - if let Some(pool_state) = pool_state_decode(&account.data[8..POOL_STATE_SIZE + 8]) { - Some(DexEvent::RaydiumCpmmPoolStateAccountEvent(RaydiumCpmmPoolStateAccountEvent { - metadata, - pubkey: account.pubkey, - executable: account.executable, - lamports: account.lamports, - owner: account.owner, - rent_epoch: account.rent_epoch, - pool_state: pool_state, - })) - } else { - None - } -} diff --git a/src/streaming/event_parser/protocols/sol_parser_forward/mod.rs b/src/streaming/event_parser/protocols/sol_parser_forward/mod.rs index efa57b9..7c50d30 100644 --- a/src/streaming/event_parser/protocols/sol_parser_forward/mod.rs +++ b/src/streaming/event_parser/protocols/sol_parser_forward/mod.rs @@ -1,12 +1,7 @@ -//! 由 `sol-parser-sdk` 产出、经 `parser_sdk_bridge` 映射的协议事件类型;`native` 将 sdk 指令解析接到 Yellowstone/shred 路径。 +//! `sol-parser-sdk` compatibility event types and parser facades. pub mod events; pub mod native; -use solana_sdk::pubkey::Pubkey; - -pub const ORCA_WHIRLPOOL_PROGRAM_ID: Pubkey = - solana_sdk::pubkey!("whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc"); -pub const METEORA_POOLS_PROGRAM_ID: Pubkey = - solana_sdk::pubkey!("Eo7WjKq67rjJQSZxS6z3YkapzY3eMj6Xy8X5EQVn5UaB"); -pub const METEORA_DLMM_PROGRAM_ID: Pubkey = - solana_sdk::pubkey!("LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo"); +pub use sol_parser_sdk::instr::program_ids::{ + METEORA_DLMM_PROGRAM_ID, METEORA_POOLS_PROGRAM_ID, ORCA_WHIRLPOOL_PROGRAM_ID, +}; diff --git a/src/streaming/event_parser/protocols/sol_parser_forward/native.rs b/src/streaming/event_parser/protocols/sol_parser_forward/native.rs index 79da726..d1c555b 100644 --- a/src/streaming/event_parser/protocols/sol_parser_forward/native.rs +++ b/src/streaming/event_parser/protocols/sol_parser_forward/native.rs @@ -1,34 +1,10 @@ -//! 将 `sol-parser-sdk` 的 Orca Whirlpool / Meteora Pools / DLMM 顶层与 inner 指令解析接到 streamer `DexEvent`。 +//! Backward-compatible SDK parser facade. use crate::streaming::event_parser::common::EventMetadata; +use crate::streaming::event_parser::core::dispatcher::EventDispatcher; use crate::streaming::event_parser::{DexEvent, Protocol}; -use crate::streaming::parser_sdk_bridge::{ - block_timestamp_from_stream_meta, convert_parser_event, fuse_streamer_ix_ctx, -}; -use sol_parser_sdk::core::events::EventMetadata as PbEventMetadata; -use sol_parser_sdk::instr::all_inner::{ - meteora_amm as pools_inner, meteora_dlmm as dlmm_inner, orca as orca_inner, -}; -use sol_parser_sdk::instr::{meteora_amm, meteora_dlmm, orca_whirlpool}; use solana_sdk::pubkey::Pubkey; -#[inline] -fn block_time_us_for_sdk(sm: &EventMetadata) -> Option { - Some(sm.block_time_ms.saturating_mul(1000)) -} - -#[inline] -fn pb_meta_from_streamer(sm: &EventMetadata) -> PbEventMetadata { - PbEventMetadata { - signature: sm.signature, - slot: sm.slot, - tx_index: sm.tx_index.unwrap_or(0), - block_time_us: sm.block_time_ms.saturating_mul(1000), - grpc_recv_us: sm.recv_us, - recent_blockhash: sm.recent_blockhash.clone(), - } -} - pub fn dispatch_instruction( protocol: Protocol, instruction_discriminator: &[u8], @@ -36,44 +12,13 @@ pub fn dispatch_instruction( accounts: &[Pubkey], stream_meta: &EventMetadata, ) -> Option { - let mut full = Vec::with_capacity(instruction_discriminator.len() + instruction_data.len()); - full.extend_from_slice(instruction_discriminator); - full.extend_from_slice(instruction_data); - - let tx_index = stream_meta.tx_index.unwrap_or(0); - let bt_us = block_time_us_for_sdk(stream_meta); - - let pb = match protocol { - Protocol::OrcaWhirlpool => orca_whirlpool::parse_instruction( - &full, - accounts, - stream_meta.signature, - stream_meta.slot, - tx_index, - bt_us, - )?, - Protocol::MeteoraPools => meteora_amm::parse_instruction( - &full, - accounts, - stream_meta.signature, - stream_meta.slot, - tx_index, - bt_us, - )?, - Protocol::MeteoraDlmm => meteora_dlmm::parse_instruction( - &full, - accounts, - stream_meta.signature, - stream_meta.slot, - tx_index, - bt_us, - )?, - _ => return None, - }; - - let ts = block_timestamp_from_stream_meta(stream_meta); - let ev = convert_parser_event(pb, Some(&ts), stream_meta.recv_us)?; - Some(fuse_streamer_ix_ctx(ev, stream_meta)) + EventDispatcher::dispatch_instruction( + protocol, + instruction_discriminator, + instruction_data, + accounts, + stream_meta.clone(), + ) } pub fn dispatch_inner_instruction( @@ -82,17 +27,10 @@ pub fn dispatch_inner_instruction( inner_instruction_data: &[u8], stream_meta: &EventMetadata, ) -> Option { - let disc: [u8; 16] = inner_instruction_discriminator.try_into().ok()?; - let pm = pb_meta_from_streamer(stream_meta); - - let pb = match protocol { - Protocol::OrcaWhirlpool => orca_inner::parse(&disc, inner_instruction_data, pm)?, - Protocol::MeteoraPools => pools_inner::parse(&disc, inner_instruction_data, pm)?, - Protocol::MeteoraDlmm => dlmm_inner::parse(&disc, inner_instruction_data, pm)?, - _ => return None, - }; - - let ts = block_timestamp_from_stream_meta(stream_meta); - let ev = convert_parser_event(pb, Some(&ts), stream_meta.recv_us)?; - Some(fuse_streamer_ix_ctx(ev, stream_meta)) + EventDispatcher::dispatch_inner_instruction( + protocol, + inner_instruction_discriminator, + inner_instruction_data, + stream_meta.clone(), + ) } diff --git a/src/streaming/event_parser/protocols/types.rs b/src/streaming/event_parser/protocols/types.rs index 34dbde2..406d0f9 100755 --- a/src/streaming/event_parser/protocols/types.rs +++ b/src/streaming/event_parser/protocols/types.rs @@ -1,16 +1,9 @@ -use crate::streaming::event_parser::protocols::{ - bonk::parser::BONK_PROGRAM_ID, - meteora_damm_v2::parser::METEORA_DAMM_V2_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, - sol_parser_forward::{ - METEORA_DLMM_PROGRAM_ID, METEORA_POOLS_PROGRAM_ID, ORCA_WHIRLPOOL_PROGRAM_ID, - }, -}; use anyhow::{anyhow, Result}; +use sol_parser_sdk::instr::program_ids::{ + BONK_PROGRAM_ID, METEORA_DAMM_V2_PROGRAM_ID, METEORA_DLMM_PROGRAM_ID, METEORA_POOLS_PROGRAM_ID, + ORCA_WHIRLPOOL_PROGRAM_ID, PUMPFUN_PROGRAM_ID, PUMPSWAP_PROGRAM_ID, PUMP_FEES_PROGRAM_ID, + RAYDIUM_AMM_V4_PROGRAM_ID, RAYDIUM_CLMM_PROGRAM_ID, RAYDIUM_CPMM_PROGRAM_ID, +}; use solana_sdk::pubkey::Pubkey; /// 支持的协议 @@ -18,7 +11,10 @@ use solana_sdk::pubkey::Pubkey; pub enum Protocol { PumpSwap, PumpFun, + PumpFees, + /// Backward-compatible alias for Raydium Launchpad / LaunchLab. Bonk, + RaydiumLaunchpad, RaydiumCpmm, RaydiumClmm, RaydiumAmmV4, @@ -33,7 +29,9 @@ impl Protocol { match self { Protocol::PumpSwap => vec![PUMPSWAP_PROGRAM_ID], Protocol::PumpFun => vec![PUMPFUN_PROGRAM_ID], + Protocol::PumpFees => vec![PUMP_FEES_PROGRAM_ID], Protocol::Bonk => vec![BONK_PROGRAM_ID], + Protocol::RaydiumLaunchpad => 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], @@ -50,7 +48,9 @@ impl std::fmt::Display for Protocol { match self { Protocol::PumpSwap => write!(f, "PumpSwap"), Protocol::PumpFun => write!(f, "PumpFun"), + Protocol::PumpFees => write!(f, "PumpFees"), Protocol::Bonk => write!(f, "Bonk"), + Protocol::RaydiumLaunchpad => write!(f, "RaydiumLaunchpad"), Protocol::RaydiumCpmm => write!(f, "RaydiumCpmm"), Protocol::RaydiumClmm => write!(f, "RaydiumClmm"), Protocol::RaydiumAmmV4 => write!(f, "RaydiumAmmV4"), @@ -69,7 +69,10 @@ impl std::str::FromStr for Protocol { match s.to_lowercase().as_str() { "pumpswap" => Ok(Protocol::PumpSwap), "pumpfun" => Ok(Protocol::PumpFun), + "pumpfees" | "pump_fees" => Ok(Protocol::PumpFees), "bonk" => Ok(Protocol::Bonk), + "raydiumlaunchpad" | "raydium_launchpad" | "raydium_launchlab" | "launchpad" + | "launchlab" => Ok(Protocol::RaydiumLaunchpad), "raydiumcpmm" | "raydium_cpmm" => Ok(Protocol::RaydiumCpmm), "raydiumclmm" | "raydium_clmm" => Ok(Protocol::RaydiumClmm), "raydiumammv4" | "raydium_amm_v4" => Ok(Protocol::RaydiumAmmV4), @@ -90,6 +93,8 @@ mod tests { #[test] fn parses_display_style_protocol_names() { for protocol in [ + Protocol::PumpFees, + Protocol::RaydiumLaunchpad, Protocol::RaydiumCpmm, Protocol::RaydiumClmm, Protocol::RaydiumAmmV4, @@ -106,6 +111,8 @@ mod tests { #[test] fn parses_snake_case_protocol_aliases() { assert_eq!(Protocol::from_str("raydium_cpmm").unwrap(), Protocol::RaydiumCpmm); + assert_eq!(Protocol::from_str("pump_fees").unwrap(), Protocol::PumpFees); + assert_eq!(Protocol::from_str("raydium_launchpad").unwrap(), Protocol::RaydiumLaunchpad); assert_eq!(Protocol::from_str("raydium_clmm").unwrap(), Protocol::RaydiumClmm); assert_eq!(Protocol::from_str("raydium_amm_v4").unwrap(), Protocol::RaydiumAmmV4); assert_eq!(Protocol::from_str("meteora_damm_v2").unwrap(), Protocol::MeteoraDammV2); diff --git a/src/streaming/parser_sdk_bridge/convert.rs b/src/streaming/parser_sdk_bridge/convert.rs index 88bbcf6..1946946 100644 --- a/src/streaming/parser_sdk_bridge/convert.rs +++ b/src/streaming/parser_sdk_bridge/convert.rs @@ -81,7 +81,7 @@ pub(crate) fn convert_parser_event( e.metadata.clone(), bt, recv_wall_us, - ProtocolType::PumpFun, + ProtocolType::PumpFees, EventType::PumpFeesCreateFeeSharingConfig, pump_fees_program(), ); @@ -94,7 +94,7 @@ pub(crate) fn convert_parser_event( e.metadata.clone(), bt, recv_wall_us, - ProtocolType::PumpFun, + ProtocolType::PumpFees, EventType::PumpFeesInitializeFeeConfig, pump_fees_program(), ); @@ -107,7 +107,7 @@ pub(crate) fn convert_parser_event( e.metadata.clone(), bt, recv_wall_us, - ProtocolType::PumpFun, + ProtocolType::PumpFees, EventType::PumpFeesResetFeeSharingConfig, pump_fees_program(), ); @@ -120,7 +120,7 @@ pub(crate) fn convert_parser_event( e.metadata.clone(), bt, recv_wall_us, - ProtocolType::PumpFun, + ProtocolType::PumpFees, EventType::PumpFeesRevokeFeeSharingAuthority, pump_fees_program(), ); @@ -133,7 +133,7 @@ pub(crate) fn convert_parser_event( e.metadata.clone(), bt, recv_wall_us, - ProtocolType::PumpFun, + ProtocolType::PumpFees, EventType::PumpFeesTransferFeeSharingAuthority, pump_fees_program(), ); @@ -146,7 +146,7 @@ pub(crate) fn convert_parser_event( e.metadata.clone(), bt, recv_wall_us, - ProtocolType::PumpFun, + ProtocolType::PumpFees, EventType::PumpFeesUpdateAdmin, pump_fees_program(), ); @@ -157,7 +157,7 @@ pub(crate) fn convert_parser_event( e.metadata.clone(), bt, recv_wall_us, - ProtocolType::PumpFun, + ProtocolType::PumpFees, EventType::PumpFeesUpdateFeeConfig, pump_fees_program(), ); @@ -170,7 +170,7 @@ pub(crate) fn convert_parser_event( e.metadata.clone(), bt, recv_wall_us, - ProtocolType::PumpFun, + ProtocolType::PumpFees, EventType::PumpFeesUpdateFeeShares, pump_fees_program(), ); @@ -183,7 +183,7 @@ pub(crate) fn convert_parser_event( e.metadata.clone(), bt, recv_wall_us, - ProtocolType::PumpFun, + ProtocolType::PumpFees, EventType::PumpFeesUpsertFeeTiers, pump_fees_program(), ); @@ -309,12 +309,17 @@ pub(crate) fn convert_parser_event( } PbDexEvent::RaydiumCpmmSwap(e) => { + let event_type = if e.base_input { + EventType::RaydiumCpmmSwapBaseInput + } else { + EventType::RaydiumCpmmSwapBaseOutput + }; let meta = adapt_pm( e.metadata.clone(), bt, recv_wall_us, ProtocolType::RaydiumCpmm, - EventType::RaydiumCpmmSwapBaseInput, + event_type, raydium_cpmm_program(), ); Some(DexEvent::RaydiumCpmmSwapEvent(raydium_cpmm_swap_from_parser(e, meta))) @@ -457,12 +462,17 @@ pub(crate) fn convert_parser_event( } PbDexEvent::RaydiumAmmV4Swap(e) => { + let event_type = if e.max_amount_in != 0 || (e.amount_out != 0 && e.amount_in == 0) { + EventType::RaydiumAmmV4SwapBaseOut + } else { + EventType::RaydiumAmmV4SwapBaseIn + }; let meta = adapt_pm( e.metadata.clone(), bt, recv_wall_us, ProtocolType::RaydiumAmmV4, - EventType::RaydiumAmmV4SwapBaseIn, + event_type, raydium_amm_v4_program(), ); Some(DexEvent::RaydiumAmmV4SwapEvent(raydium_amm_v4_swap_from_parser(e, meta))) diff --git a/src/streaming/parser_sdk_bridge/filter.rs b/src/streaming/parser_sdk_bridge/filter.rs index b229288..562d319 100644 --- a/src/streaming/parser_sdk_bridge/filter.rs +++ b/src/streaming/parser_sdk_bridge/filter.rs @@ -43,6 +43,15 @@ fn protocol_matches_event(p: &Protocol, ev: &DexEvent) -> bool { | (Protocol::PumpFun, DexEvent::PumpFunMigrateBondingCurveCreatorEvent(_)) | (Protocol::PumpFun, DexEvent::PumpFunBondingCurveAccountEvent(_)) | (Protocol::PumpFun, DexEvent::PumpFunGlobalAccountEvent(_)) => true, + (Protocol::PumpFees, DexEvent::PumpFeesCreateFeeSharingConfigEvent(_)) + | (Protocol::PumpFees, DexEvent::PumpFeesInitializeFeeConfigEvent(_)) + | (Protocol::PumpFees, DexEvent::PumpFeesResetFeeSharingConfigEvent(_)) + | (Protocol::PumpFees, DexEvent::PumpFeesRevokeFeeSharingAuthorityEvent(_)) + | (Protocol::PumpFees, DexEvent::PumpFeesTransferFeeSharingAuthorityEvent(_)) + | (Protocol::PumpFees, DexEvent::PumpFeesUpdateAdminEvent(_)) + | (Protocol::PumpFees, DexEvent::PumpFeesUpdateFeeConfigEvent(_)) + | (Protocol::PumpFees, DexEvent::PumpFeesUpdateFeeSharesEvent(_)) + | (Protocol::PumpFees, DexEvent::PumpFeesUpsertFeeTiersEvent(_)) => true, (Protocol::PumpSwap, DexEvent::PumpSwapBuyEvent(_)) | (Protocol::PumpSwap, DexEvent::PumpSwapSellEvent(_)) | (Protocol::PumpSwap, DexEvent::PumpSwapCreatePoolEvent(_)) @@ -56,7 +65,14 @@ fn protocol_matches_event(p: &Protocol, ev: &DexEvent) -> bool { | (Protocol::Bonk, DexEvent::BonkMigrateToCpswapEvent(_)) | (Protocol::Bonk, DexEvent::BonkPoolStateAccountEvent(_)) | (Protocol::Bonk, DexEvent::BonkGlobalConfigAccountEvent(_)) - | (Protocol::Bonk, DexEvent::BonkPlatformConfigAccountEvent(_)) => true, + | (Protocol::Bonk, DexEvent::BonkPlatformConfigAccountEvent(_)) + | (Protocol::RaydiumLaunchpad, DexEvent::BonkTradeEvent(_)) + | (Protocol::RaydiumLaunchpad, DexEvent::BonkPoolCreateEvent(_)) + | (Protocol::RaydiumLaunchpad, DexEvent::BonkMigrateToAmmEvent(_)) + | (Protocol::RaydiumLaunchpad, DexEvent::BonkMigrateToCpswapEvent(_)) + | (Protocol::RaydiumLaunchpad, DexEvent::BonkPoolStateAccountEvent(_)) + | (Protocol::RaydiumLaunchpad, DexEvent::BonkGlobalConfigAccountEvent(_)) + | (Protocol::RaydiumLaunchpad, DexEvent::BonkPlatformConfigAccountEvent(_)) => true, (Protocol::RaydiumCpmm, DexEvent::RaydiumCpmmSwapEvent(_)) | (Protocol::RaydiumCpmm, DexEvent::RaydiumCpmmDepositEvent(_)) | (Protocol::RaydiumCpmm, DexEvent::RaydiumCpmmWithdrawEvent(_)) diff --git a/src/streaming/parser_sdk_bridge/program_ids.rs b/src/streaming/parser_sdk_bridge/program_ids.rs index cd23ebd..988676e 100644 --- a/src/streaming/parser_sdk_bridge/program_ids.rs +++ b/src/streaming/parser_sdk_bridge/program_ids.rs @@ -1,41 +1,40 @@ //! Protocol program ids used by bridged events. -use crate::streaming::event_parser::protocols::sol_parser_forward; - +use sol_parser_sdk::instr::program_ids; use solana_sdk::pubkey::Pubkey; pub(crate) fn pumpswap_program() -> Pubkey { - crate::streaming::event_parser::protocols::pumpswap::parser::PUMPSWAP_PROGRAM_ID + program_ids::PUMPSWAP_PROGRAM_ID } pub(crate) fn pump_program() -> Pubkey { - crate::streaming::event_parser::protocols::pumpfun::parser::PUMPFUN_PROGRAM_ID + program_ids::PUMPFUN_PROGRAM_ID } pub(crate) fn pump_fees_program() -> Pubkey { - sol_parser_sdk::instr::program_ids::PUMP_FEES_PROGRAM_ID + program_ids::PUMP_FEES_PROGRAM_ID } pub(crate) fn bonk_program() -> Pubkey { - crate::streaming::event_parser::protocols::bonk::parser::BONK_PROGRAM_ID + program_ids::BONK_PROGRAM_ID } pub(crate) fn raydium_cpmm_program() -> Pubkey { - crate::streaming::event_parser::protocols::raydium_cpmm::parser::RAYDIUM_CPMM_PROGRAM_ID + program_ids::RAYDIUM_CPMM_PROGRAM_ID } pub(crate) fn raydium_clmm_program() -> Pubkey { - crate::streaming::event_parser::protocols::raydium_clmm::parser::RAYDIUM_CLMM_PROGRAM_ID + program_ids::RAYDIUM_CLMM_PROGRAM_ID } pub(crate) fn raydium_amm_v4_program() -> Pubkey { - crate::streaming::event_parser::protocols::raydium_amm_v4::parser::RAYDIUM_AMM_V4_PROGRAM_ID + program_ids::RAYDIUM_AMM_V4_PROGRAM_ID } pub(crate) fn meteora_damm_program() -> Pubkey { - crate::streaming::event_parser::protocols::meteora_damm_v2::parser::METEORA_DAMM_V2_PROGRAM_ID + program_ids::METEORA_DAMM_V2_PROGRAM_ID } pub(crate) fn orca_whirlpool_program() -> Pubkey { - sol_parser_forward::ORCA_WHIRLPOOL_PROGRAM_ID + program_ids::ORCA_WHIRLPOOL_PROGRAM_ID } pub(crate) fn meteora_pools_program() -> Pubkey { - sol_parser_forward::METEORA_POOLS_PROGRAM_ID + program_ids::METEORA_POOLS_PROGRAM_ID } pub(crate) fn meteora_dlmm_program() -> Pubkey { - sol_parser_forward::METEORA_DLMM_PROGRAM_ID + program_ids::METEORA_DLMM_PROGRAM_ID } diff --git a/src/streaming/shred_stream.rs b/src/streaming/shred_stream.rs index 40a211e..b6a9ad5 100755 --- a/src/streaming/shred_stream.rs +++ b/src/streaming/shred_stream.rs @@ -6,7 +6,9 @@ use solana_sdk::pubkey::Pubkey; use crate::common::AnyResult; use crate::streaming::common::{MetricsEventType, MetricsManager, SubscriptionHandle}; -use crate::streaming::event_parser::common::filter::EventTypeFilter; +use crate::streaming::event_parser::common::filter::{ + build_sdk_shred_parse_event_filter, EventTypeFilter, +}; use crate::streaming::event_parser::common::high_performance_clock::elapsed_micros_since; use crate::streaming::event_parser::{DexEvent, Protocol}; use crate::streaming::parser_sdk_bridge::adapt_parser_event; @@ -34,9 +36,11 @@ impl ShredStreamGrpc { metrics_handle = MetricsManager::global().start_auto_monitoring().await; } + let sdk_parse_filter = + build_sdk_shred_parse_event_filter(&protocols, event_type_filter.as_ref()); let queue = self .sdk_shredstream_client - .subscribe() + .subscribe_with_filter(sdk_parse_filter) .await .map_err(|e| anyhow::anyhow!(e.to_string()))?;