diff --git a/.gitignore b/.gitignore index c77fa9e..a3ccb4c 100755 --- a/.gitignore +++ b/.gitignore @@ -20,4 +20,7 @@ Cargo.lock # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ -.cargo/ \ No newline at end of file +.cargo/ + +tmp_*.rs +tmp_*.log \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index da4d8f7..4ab14d7 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sol-trade-sdk" -version = "0.1.0" +version = "0.2.0" edition = "2021" authors = ["William ", "sgxiang ", "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" } diff --git a/README.md b/README.md index 2ee8458..f2d9636 100755 --- a/README.md +++ b/README.md @@ -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 diff --git a/README_CN.md b/README_CN.md index 3564765..915b01e 100755 --- a/README_CN.md +++ b/README_CN.md @@ -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" } ``` ## 使用示例 diff --git a/src/streaming/event_parser/common/types.rs b/src/streaming/event_parser/common/types.rs index 02aae00..478f4ea 100755 --- a/src/streaming/event_parser/common/types.rs +++ b/src/streaming/event_parser/common/types.rs @@ -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 { @@ -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); } diff --git a/src/streaming/event_parser/core/traits.rs b/src/streaming/event_parser/core/traits.rs index ef34626..20a8bbb 100755 --- a/src/streaming/event_parser/core/traits.rs +++ b/src/streaming/event_parser/core/traits.rs @@ -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, + accounts: &[Pubkey], ) -> Result>> { 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 = 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, bot_wallet: Option, ) -> Result>> { - let events = self.parse_instruction_events_from_versioned_transaction(versioned_tx, signature, slot) - .await.unwrap_or_else(|_e| vec![]); + let accounts: Vec = 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, ) -> Result>> { 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 = 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 = 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>, bot_wallet: Option) -> Vec> { + fn process_events( + &self, + mut events: Vec>, + bot_wallet: Option, + ) -> Vec> { 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::() { dev_address = Some(token_info.user); - } else if let Some(trade_info) = - event.as_any_mut().downcast_mut::() + } else if let Some(trade_info) = event.as_any_mut().downcast_mut::() { 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::() - { + if let Some(pool_info) = event.as_any().downcast_ref::() { bonk_dev_address = Some(pool_info.creator); - } else if let Some(trade_info) = event - .as_any_mut() - .downcast_mut::() - { + } else if let Some(trade_info) = event.as_any_mut().downcast_mut::() { if Some(trade_info.payer) == bonk_dev_address { trade_info.is_dev_create_token_trade = true; } else if Some(trade_info.payer) == bot_wallet { diff --git a/src/streaming/event_parser/protocols/bonk/parser.rs b/src/streaming/event_parser/protocols/bonk/parser.rs index 094f749..4e20a6f 100755 --- a/src/streaming/event_parser/protocols/bonk/parser.rs +++ b/src/streaming/event_parser/protocols/bonk/parser.rs @@ -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