mirror of
https://github.com/0xfnzero/solana-streamer.git
synced 2026-08-16 10:28:05 +00:00
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.
This commit is contained in:
@@ -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<SdkGrpcEventType> = Vec::new();
|
||||
let mut raw: Vec<SdkGrpcEventType> = 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<SdkGrpcEventType> = Vec::new();
|
||||
let mut raw: Vec<SdkGrpcEventType> = 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<SdkGrpcEventTypeFilter> {
|
||||
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<SdkGrpcEventType>) {
|
||||
let mut i = 0;
|
||||
while i < v.len() {
|
||||
@@ -163,51 +168,199 @@ fn dedup_sdk_grpc_event_types(v: &mut Vec<SdkGrpcEventType>) {
|
||||
}
|
||||
}
|
||||
|
||||
fn streamer_event_to_sdk_grpc_types(t: &EventType) -> Vec<SdkGrpcEventType> {
|
||||
fn push_streamer_event_sdk_grpc_types(t: &EventType, out: &mut Vec<SdkGrpcEventType>) -> 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<SdkGrpcEventType>) {
|
||||
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());
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
pub mod filter;
|
||||
pub mod high_performance_clock;
|
||||
pub mod types;
|
||||
pub mod utils;
|
||||
pub use types::*;
|
||||
pub use utils::*;
|
||||
|
||||
@@ -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<Pubkey> = 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<Pubkey>,
|
||||
Option<Pubkey>,
|
||||
Option<Pubkey>,
|
||||
Option<Pubkey>,
|
||||
Option<Pubkey>,
|
||||
Option<Pubkey>,
|
||||
) {
|
||||
let mut swap_data = SwapData::default();
|
||||
let mut from_mint: Option<Pubkey> = None;
|
||||
let mut to_mint: Option<Pubkey> = None;
|
||||
let mut user_from_token: Option<Pubkey> = None;
|
||||
let mut user_to_token: Option<Pubkey> = None;
|
||||
let mut from_vault: Option<Pubkey> = None;
|
||||
let mut to_vault: Option<Pubkey> = 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<I: InnerInstructionLike>(
|
||||
event: &DexEvent,
|
||||
instructions: impl Iterator<Item = I>,
|
||||
current_index: i32,
|
||||
accounts: &[Pubkey],
|
||||
) -> Option<SwapData> {
|
||||
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<Pubkey> {
|
||||
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<SwapData> {
|
||||
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<SwapData> {
|
||||
extract_swap_data_from_instructions(
|
||||
event,
|
||||
inner_instruction.instructions.iter().cloned(),
|
||||
current_index,
|
||||
accounts,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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<i64> {
|
||||
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<u64> {
|
||||
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<i32> {
|
||||
if data.len() < offset + 4 {
|
||||
return None;
|
||||
}
|
||||
let bytes: [u8; 4] = data[offset..offset + 4].try_into().ok()?;
|
||||
Some(i32::from_le_bytes(bytes))
|
||||
}
|
||||
|
||||
pub fn read_u128_le(data: &[u8], offset: usize) -> Option<u128> {
|
||||
if data.len() < offset + 16 {
|
||||
return None;
|
||||
}
|
||||
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<u8> {
|
||||
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<Option<bool>> {
|
||||
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<u32> {
|
||||
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<u16> {
|
||||
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<u8> {
|
||||
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..])
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user