feat: optimize event parser performance and stability. update version.
This commit is contained in:
+4
-1
@@ -20,4 +20,7 @@ Cargo.lock
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
|
||||
.cargo/
|
||||
.cargo/
|
||||
|
||||
tmp_*.rs
|
||||
tmp_*.log
|
||||
+1
-22
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "sol-trade-sdk"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
edition = "2021"
|
||||
authors = ["William <byteblock6@gmail.com>", "sgxiang <sgxiang@gmail.com>", "wei <1415121722@qq.com>"]
|
||||
repository = "https://github.com/0xfnzero/sol-trade-sdk"
|
||||
@@ -21,17 +21,13 @@ solana-rpc-client-api = "2.1.16"
|
||||
solana-transaction-status = "2.1.16"
|
||||
solana-account-decoder = "2.1.16"
|
||||
solana-hash = "2.1.16"
|
||||
solana-security-txt = "1.1.1"
|
||||
solana-entry = "2.1.16"
|
||||
solana-rpc-client-nonce-utils = "2.1.16"
|
||||
solana-perf = "2.1.16"
|
||||
solana-metrics = "2.1.16"
|
||||
|
||||
spl-token = "8.0.0"
|
||||
spl-token-2022 = { version = "8.0.0", features = ["no-entrypoint"] }
|
||||
spl-associated-token-account = "6.0.0"
|
||||
mpl-token-metadata = "5.1.0"
|
||||
|
||||
borsh = { version = "1.5.3", features = ["derive"] }
|
||||
isahc = "1.7.2"
|
||||
serde = { version = "1.0.215", features = ["derive"] }
|
||||
@@ -52,9 +48,6 @@ rustls = { version = "0.23.23", features = ["ring"] }
|
||||
rustls-native-certs = "0.8.1"
|
||||
tokio-rustls = "0.26.1"
|
||||
core_affinity = "0.8"
|
||||
|
||||
dotenvy = "0.15.7"
|
||||
pretty_env_logger = "0.5.0"
|
||||
log = "0.4.22"
|
||||
chrono = "0.4.39"
|
||||
regex = "1"
|
||||
@@ -68,23 +61,9 @@ prost-types = "0.13.5"
|
||||
num_enum = "0.7.3"
|
||||
num-derive = "0.4.2"
|
||||
num-traits = "0.2.19"
|
||||
uint = "0.10.0"
|
||||
clap = { version = "4.5.31", features = ["derive"] }
|
||||
|
||||
hex = "0.4.3"
|
||||
bytemuck = { version = "1.4.0" }
|
||||
safe-transmute = "0.11.0"
|
||||
enumflags2 = "0.6.4"
|
||||
static_assertions = "1.1.0"
|
||||
demand = "1.2.2"
|
||||
arrayref = "0.3.6"
|
||||
default-env = "0.1.1"
|
||||
|
||||
borsh-derive = "1.5.5"
|
||||
axum = { version = "0.8.1", features = ["macros"] }
|
||||
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
|
||||
tokio-tungstenite = { version = "0.26.1", features = ["native-tls"] }
|
||||
indicatif = "0.17.11"
|
||||
toml = "0.8.20"
|
||||
|
||||
pumpfun_program = { version = "4.3.0", package = "pumpfun" }
|
||||
|
||||
@@ -29,7 +29,7 @@ Add the dependency to your `Cargo.toml`:
|
||||
|
||||
```toml
|
||||
# Add to your Cargo.toml
|
||||
sol-trade-sdk = { path = "./sol-trade-sdk", version = "0.1.0" }
|
||||
sol-trade-sdk = { path = "./sol-trade-sdk", version = "0.2.0" }
|
||||
```
|
||||
|
||||
## Usage Examples
|
||||
|
||||
+1
-1
@@ -29,7 +29,7 @@ git clone https://github.com/0xfnzero/sol-trade-sdk
|
||||
|
||||
```toml
|
||||
# 添加到您的 Cargo.toml
|
||||
sol-trade-sdk = { path = "./sol-trade-sdk", version = "0.1.0" }
|
||||
sol-trade-sdk = { path = "./sol-trade-sdk", version = "0.2.0" }
|
||||
```
|
||||
|
||||
## 使用示例
|
||||
|
||||
@@ -48,6 +48,29 @@ pub enum EventType {
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl EventType {
|
||||
pub fn to_string(&self) -> String {
|
||||
match self {
|
||||
EventType::PumpSwapBuy => "PumpSwapBuy".to_string(),
|
||||
EventType::PumpSwapSell => "PumpSwapSell".to_string(),
|
||||
EventType::PumpSwapCreatePool => "PumpSwapCreatePool".to_string(),
|
||||
EventType::PumpSwapDeposit => "PumpSwapDeposit".to_string(),
|
||||
EventType::PumpSwapWithdraw => "PumpSwapWithdraw".to_string(),
|
||||
EventType::PumpFunCreateToken => "PumpFunCreateToken".to_string(),
|
||||
EventType::PumpFunBuy => "PumpFunBuy".to_string(),
|
||||
EventType::PumpFunSell => "PumpFunSell".to_string(),
|
||||
EventType::BonkBuyExactIn => "BonkBuyExactIn".to_string(),
|
||||
EventType::BonkBuyExactOut => "BonkBuyExactOut".to_string(),
|
||||
EventType::BonkSellExactIn => "BonkSellExactIn".to_string(),
|
||||
EventType::BonkSellExactOut => "BonkSellExactOut".to_string(),
|
||||
EventType::BonkInitialize => "BonkInitialize".to_string(),
|
||||
EventType::RaydiumCpmmSwapBaseInput => "RaydiumCpmmSwapBaseInput".to_string(),
|
||||
EventType::RaydiumCpmmSwapBaseOutput => "RaydiumCpmmSwapBaseOutput".to_string(),
|
||||
EventType::Unknown => "Unknown".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 解析结果
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ParseResult<T> {
|
||||
@@ -133,9 +156,10 @@ impl EventMetadata {
|
||||
}
|
||||
}
|
||||
pub fn set_id(&mut self, id: String) {
|
||||
let _id = format!("{}-{}-{}", self.signature, self.event_type.to_string(), id);
|
||||
// 对传入的 id 进行哈希处理
|
||||
let mut hasher = DefaultHasher::new();
|
||||
id.hash(&mut hasher);
|
||||
_id.hash(&mut hasher);
|
||||
let hash_value = hasher.finish();
|
||||
self.id = format!("{:x}", hash_value);
|
||||
}
|
||||
|
||||
@@ -5,11 +5,15 @@ use solana_sdk::{
|
||||
use solana_transaction_status::{
|
||||
EncodedTransactionWithStatusMeta, UiCompiledInstruction, UiInstruction,
|
||||
};
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Debug;
|
||||
use std::{collections::HashMap, str::FromStr};
|
||||
|
||||
use crate::streaming::event_parser::{
|
||||
common::{utils::*, EventMetadata, EventType, ProtocolType}, protocols::{pumpfun::{PumpFunCreateTokenEvent, PumpFunTradeEvent}, bonk::{BonkPoolCreateEvent, BonkTradeEvent}},
|
||||
common::{utils::*, EventMetadata, EventType, ProtocolType},
|
||||
protocols::{
|
||||
bonk::{BonkPoolCreateEvent, BonkTradeEvent},
|
||||
pumpfun::{PumpFunCreateTokenEvent, PumpFunTradeEvent},
|
||||
},
|
||||
};
|
||||
|
||||
/// 统一事件接口 - 所有协议的事件都需要实现此trait
|
||||
@@ -70,11 +74,13 @@ pub trait EventParser: Send + Sync {
|
||||
versioned_tx: &VersionedTransaction,
|
||||
signature: &str,
|
||||
slot: Option<u64>,
|
||||
accounts: &[Pubkey],
|
||||
) -> Result<Vec<Box<dyn UnifiedEvent>>> {
|
||||
let mut instruction_events = Vec::new();
|
||||
// 获取交易的指令和账户
|
||||
let compiled_instructions = versioned_tx.message.instructions();
|
||||
let accounts = versioned_tx.message.static_account_keys();
|
||||
let mut accounts: Vec<Pubkey> = accounts.to_vec();
|
||||
|
||||
// 检查交易中是否包含程序
|
||||
let has_program = accounts.iter().any(|account| self.should_handle(account));
|
||||
if has_program {
|
||||
@@ -82,20 +88,19 @@ pub trait EventParser: Send + Sync {
|
||||
for instruction in compiled_instructions {
|
||||
if let Some(program_id) = accounts.get(instruction.program_id_index as usize) {
|
||||
if self.should_handle(program_id) {
|
||||
// 验证账户索引的有效性
|
||||
let all_accounts_valid = instruction
|
||||
.accounts
|
||||
.iter()
|
||||
.all(|&acc_idx| (acc_idx as usize) < accounts.len());
|
||||
|
||||
if all_accounts_valid {
|
||||
if let Ok(events) = self
|
||||
.parse_instruction(instruction, accounts, signature, slot)
|
||||
.await
|
||||
{
|
||||
instruction_events.extend(events);
|
||||
let max_idx = instruction.accounts.iter().max().unwrap_or(&0);
|
||||
// 补齐accounts(使用Pubkey::default())
|
||||
if *max_idx as usize > accounts.len() {
|
||||
for _i in accounts.len()..*max_idx as usize {
|
||||
accounts.push(Pubkey::default());
|
||||
}
|
||||
}
|
||||
if let Ok(events) = self
|
||||
.parse_instruction(instruction, &accounts, signature, slot)
|
||||
.await
|
||||
{
|
||||
instruction_events.extend(events);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -110,8 +115,16 @@ pub trait EventParser: Send + Sync {
|
||||
slot: Option<u64>,
|
||||
bot_wallet: Option<Pubkey>,
|
||||
) -> Result<Vec<Box<dyn UnifiedEvent>>> {
|
||||
let events = self.parse_instruction_events_from_versioned_transaction(versioned_tx, signature, slot)
|
||||
.await.unwrap_or_else(|_e| vec![]);
|
||||
let accounts: Vec<Pubkey> = versioned_tx.message.static_account_keys().to_vec();
|
||||
let events = self
|
||||
.parse_instruction_events_from_versioned_transaction(
|
||||
versioned_tx,
|
||||
signature,
|
||||
slot,
|
||||
&accounts,
|
||||
)
|
||||
.await
|
||||
.unwrap_or_else(|_e| vec![]);
|
||||
Ok(self.process_events(events, bot_wallet))
|
||||
}
|
||||
|
||||
@@ -123,23 +136,46 @@ pub trait EventParser: Send + Sync {
|
||||
bot_wallet: Option<Pubkey>,
|
||||
) -> Result<Vec<Box<dyn UnifiedEvent>>> {
|
||||
let transaction = tx.transaction;
|
||||
let mut instruction_events = Vec::new();
|
||||
|
||||
// 解析指令事件
|
||||
if let Some(versioned_tx) = transaction.decode() {
|
||||
instruction_events = self
|
||||
.parse_instruction_events_from_versioned_transaction(&versioned_tx, signature, slot)
|
||||
.await
|
||||
.unwrap_or_else(|_e| vec![]);
|
||||
}
|
||||
|
||||
// 解析内联指令事件
|
||||
let mut inner_instruction_events = Vec::new();
|
||||
// 检查交易元数据
|
||||
let meta = tx
|
||||
.meta
|
||||
.as_ref()
|
||||
.ok_or_else(|| anyhow::anyhow!("Missing transaction metadata"))?;
|
||||
|
||||
let mut address_table_lookups: Vec<Pubkey> = vec![];
|
||||
if meta.err.is_none() {
|
||||
let loaded_addresses = meta.loaded_addresses.as_ref().unwrap();
|
||||
for lookup in &loaded_addresses.writable {
|
||||
address_table_lookups.push(Pubkey::from_str(lookup).unwrap());
|
||||
}
|
||||
for lookup in &loaded_addresses.readonly {
|
||||
address_table_lookups.push(Pubkey::from_str(lookup).unwrap());
|
||||
}
|
||||
}
|
||||
let mut accounts: Vec<Pubkey> = vec![];
|
||||
|
||||
let mut instruction_events = Vec::new();
|
||||
|
||||
// 解析指令事件
|
||||
if let Some(versioned_tx) = transaction.decode() {
|
||||
accounts = versioned_tx.message.static_account_keys().to_vec();
|
||||
accounts.extend(address_table_lookups.clone());
|
||||
|
||||
instruction_events = self
|
||||
.parse_instruction_events_from_versioned_transaction(
|
||||
&versioned_tx,
|
||||
signature,
|
||||
slot,
|
||||
&accounts,
|
||||
)
|
||||
.await
|
||||
.unwrap_or_else(|_e| vec![]);
|
||||
} else {
|
||||
accounts.extend(address_table_lookups.clone());
|
||||
}
|
||||
|
||||
// 解析内联指令事件
|
||||
let mut inner_instruction_events = Vec::new();
|
||||
// 检查交易是否成功
|
||||
if meta.err.is_none() {
|
||||
let inner_instructions = meta.inner_instructions.as_ref().unwrap();
|
||||
@@ -147,6 +183,23 @@ pub trait EventParser: Send + Sync {
|
||||
for instruction in &inner_instruction.instructions {
|
||||
match instruction {
|
||||
UiInstruction::Compiled(compiled) => {
|
||||
// 解析嵌套指令
|
||||
let compiled_instruction = CompiledInstruction {
|
||||
program_id_index: compiled.program_id_index,
|
||||
accounts: compiled.accounts.clone(),
|
||||
data: bs58::decode(compiled.data.clone()).into_vec().unwrap(),
|
||||
};
|
||||
if let Ok(events) = self
|
||||
.parse_instruction(
|
||||
&compiled_instruction,
|
||||
&accounts,
|
||||
signature,
|
||||
slot,
|
||||
)
|
||||
.await
|
||||
{
|
||||
instruction_events.extend(events);
|
||||
}
|
||||
if let Ok(events) = self
|
||||
.parse_inner_instruction(compiled, signature, slot)
|
||||
.await
|
||||
@@ -175,14 +228,17 @@ pub trait EventParser: Send + Sync {
|
||||
Ok(self.process_events(instruction_events, bot_wallet))
|
||||
}
|
||||
|
||||
fn process_events(&self, mut events: Vec<Box<dyn UnifiedEvent>>, bot_wallet: Option<Pubkey>) -> Vec<Box<dyn UnifiedEvent>> {
|
||||
fn process_events(
|
||||
&self,
|
||||
mut events: Vec<Box<dyn UnifiedEvent>>,
|
||||
bot_wallet: Option<Pubkey>,
|
||||
) -> Vec<Box<dyn UnifiedEvent>> {
|
||||
let mut dev_address = None;
|
||||
let mut bonk_dev_address = None;
|
||||
for event in &mut events {
|
||||
if let Some(token_info) = event.as_any().downcast_ref::<PumpFunCreateTokenEvent>() {
|
||||
dev_address = Some(token_info.user);
|
||||
} else if let Some(trade_info) =
|
||||
event.as_any_mut().downcast_mut::<PumpFunTradeEvent>()
|
||||
} else if let Some(trade_info) = event.as_any_mut().downcast_mut::<PumpFunTradeEvent>()
|
||||
{
|
||||
if Some(trade_info.user) == dev_address {
|
||||
trade_info.is_dev_create_token_trade = true;
|
||||
@@ -192,15 +248,9 @@ pub trait EventParser: Send + Sync {
|
||||
trade_info.is_dev_create_token_trade = false;
|
||||
}
|
||||
}
|
||||
if let Some(pool_info) = event
|
||||
.as_any()
|
||||
.downcast_ref::<BonkPoolCreateEvent>()
|
||||
{
|
||||
if let Some(pool_info) = event.as_any().downcast_ref::<BonkPoolCreateEvent>() {
|
||||
bonk_dev_address = Some(pool_info.creator);
|
||||
} else if let Some(trade_info) = event
|
||||
.as_any_mut()
|
||||
.downcast_mut::<BonkTradeEvent>()
|
||||
{
|
||||
} else if let Some(trade_info) = event.as_any_mut().downcast_mut::<BonkTradeEvent>() {
|
||||
if Some(trade_info.payer) == bonk_dev_address {
|
||||
trade_info.is_dev_create_token_trade = true;
|
||||
} else if Some(trade_info.payer) == bot_wallet {
|
||||
|
||||
@@ -5,8 +5,8 @@ use crate::streaming::event_parser::{
|
||||
common::{utils::*, EventMetadata, EventType, ProtocolType},
|
||||
core::traits::{EventParser, GenericEventParseConfig, GenericEventParser, UnifiedEvent},
|
||||
protocols::bonk::{
|
||||
discriminators, ConstantCurve, CurveParams, FixedCurve, LinearCurve, MintParams,
|
||||
BonkPoolCreateEvent, BonkTradeEvent, TradeDirection, VestingParams,
|
||||
discriminators, BonkPoolCreateEvent, BonkTradeEvent, ConstantCurve, CurveParams,
|
||||
FixedCurve, LinearCurve, MintParams, TradeDirection, VestingParams,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -60,11 +60,7 @@ impl BonkEventParser {
|
||||
},
|
||||
];
|
||||
|
||||
let inner = GenericEventParser::new(
|
||||
BONK_PROGRAM_ID,
|
||||
ProtocolType::Bonk,
|
||||
configs,
|
||||
);
|
||||
let inner = GenericEventParser::new(BONK_PROGRAM_ID, ProtocolType::Bonk, configs);
|
||||
|
||||
Self { inner }
|
||||
}
|
||||
@@ -98,6 +94,19 @@ impl BonkEventParser {
|
||||
metadata.signature,
|
||||
event.pool_state.to_string()
|
||||
));
|
||||
if metadata.event_type == EventType::BonkBuyExactIn
|
||||
|| metadata.event_type == EventType::BonkBuyExactOut
|
||||
{
|
||||
if event.trade_direction != TradeDirection::Buy {
|
||||
return None;
|
||||
}
|
||||
} else if metadata.event_type == EventType::BonkSellExactIn
|
||||
|| metadata.event_type == EventType::BonkSellExactOut
|
||||
{
|
||||
if event.trade_direction != TradeDirection::Sell {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
Some(Box::new(BonkTradeEvent {
|
||||
metadata: metadata,
|
||||
..event
|
||||
|
||||
Reference in New Issue
Block a user