diff --git a/Cargo.toml b/Cargo.toml index 37600e3..395d2af 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,23 +45,26 @@ perf-trace = [] # 性能追踪特性,生产环境应禁用以获得最佳性 [dependencies] solana-sdk = "3.0.0" -solana-client = "3.1.9" +solana-client = "3.1.12" solana-program = "3.0.0" -solana-rpc-client = "3.1.9" -solana-rpc-client-api = "3.1.9" -solana-transaction-status = "3.1.9" -solana-account-decoder = "3.1.9" +solana-rpc-client = "3.1.12" +solana-rpc-client-api = "3.1.12" +solana-transaction-status = "3.1.12" +solana-account-decoder = "3.1.12" solana-hash = "3.0.0" -solana-entry = "3.1.9" -solana-rpc-client-nonce-utils = "3.1.9" -solana-perf = "3.1.9" -solana-metrics = "3.1.9" -solana-nonce = "3.1.0" -solana-address-lookup-table-interface = "3.0.1" +solana-entry = "3.0.0" +solana-rpc-client-nonce-utils = "3.1.12" +solana-perf = "3.1.12" +solana-metrics = "3.1.12" +solana-tls-utils = "3.1.12" +solana-nonce = "3.2.0" + +solana-address-lookup-table-interface = "3.0.0" +solana-message = "3.1.0" solana-compute-budget-interface = "3.0.0" solana-commitment-config = { version = "3.1.1", features = ["serde"] } -solana-transaction-status-client-types = "3.1.9" -solana-tls-utils = "3.1.9" +solana-transaction-status-client-types = "3.1.12" +solana-system-interface = { version = "3.0.0", features = ["bincode"] } borsh = { version = "1.5.3", features = ["derive"] } isahc = "1.7.2" @@ -76,7 +79,7 @@ bincode = "1.3.3" anyhow = "1.0.90" reqwest = { version = "0.12.12", features = ["json", "multipart"] } tokio = { version = "1.42.0" , features = ["full", "rt-multi-thread"]} -tonic = { version = "0.14.2", features = ["transport"] } +tonic = { version = "0.12", features = ["transport"] } rustls = { version = "0.23.23", features = ["ring"] } rustls-native-certs = "0.8.1" tokio-rustls = "0.26.1" @@ -88,8 +91,8 @@ thiserror = "2.0.11" async-trait = "0.1.86" lazy_static = "1.5.0" once_cell = "1.20.3" -prost = "0.14.1" -prost-types = "0.14.1" +prost = "0.13" +prost-types = "0.13" num_enum = "0.7.3" num-derive = "0.4.2" num-traits = "0.2.19" @@ -98,7 +101,7 @@ bytemuck = { version = "1.4.0" } arrayref = "0.3.6" borsh-derive = "1.5.5" indicatif = "0.18.0" -solana-system-interface = { version = "2.0.0", features = ["bincode"] } + fnv = "1.0.7" dashmap = "6.1.0" clru = "0.6" diff --git a/examples/address_lookup/src/main.rs b/examples/address_lookup/src/main.rs index 1ed9756..f119fc5 100644 --- a/examples/address_lookup/src/main.rs +++ b/examples/address_lookup/src/main.rs @@ -164,6 +164,7 @@ async fn pumpfun_copy_trade_with_grpc( trade_info.fee_recipient, trade_info.token_program, trade_info.is_cashback_coin, + Some(trade_info.mayhem_mode), )), address_lookup_table_account, wait_transaction_confirmed: true, diff --git a/examples/nonce_cache/src/main.rs b/examples/nonce_cache/src/main.rs index 8ea15c5..afe5008 100644 --- a/examples/nonce_cache/src/main.rs +++ b/examples/nonce_cache/src/main.rs @@ -160,6 +160,7 @@ async fn pumpfun_copy_trade_with_grpc( trade_info.fee_recipient, trade_info.token_program, trade_info.is_cashback_coin, + Some(trade_info.mayhem_mode), )), address_lookup_table_account: None, wait_transaction_confirmed: true, diff --git a/examples/pumpfun_copy_trading/src/main.rs b/examples/pumpfun_copy_trading/src/main.rs index 038f9e4..ed43f57 100644 --- a/examples/pumpfun_copy_trading/src/main.rs +++ b/examples/pumpfun_copy_trading/src/main.rs @@ -157,6 +157,7 @@ async fn pumpfun_copy_trade(e: sol_parser_sdk::core::events::PumpFunTradeEvent) e.fee_recipient, e.token_program, e.is_cashback_coin, + Some(e.mayhem_mode), )), address_lookup_table_account: None, wait_transaction_confirmed: true, @@ -206,6 +207,7 @@ async fn pumpfun_copy_trade(e: sol_parser_sdk::core::events::PumpFunTradeEvent) e.fee_recipient, e.token_program, e.is_cashback_coin, + Some(e.mayhem_mode), )), address_lookup_table_account: None, wait_transaction_confirmed: true, diff --git a/examples/pumpfun_sniper_trading/src/main.rs b/examples/pumpfun_sniper_trading/src/main.rs index 67ef2d4..0d90fc6 100644 --- a/examples/pumpfun_sniper_trading/src/main.rs +++ b/examples/pumpfun_sniper_trading/src/main.rs @@ -147,6 +147,7 @@ async fn pumpfun_sniper_trade(e: sol_parser_sdk::core::events::PumpFunTradeEvent e.fee_recipient, e.token_program, e.is_cashback_coin, + Some(e.mayhem_mode), )), address_lookup_table_account: None, wait_transaction_confirmed: true, @@ -190,6 +191,7 @@ async fn pumpfun_sniper_trade(e: sol_parser_sdk::core::events::PumpFunTradeEvent e.fee_recipient, e.token_program, e.is_cashback_coin, + Some(e.mayhem_mode), )), address_lookup_table_account: None, wait_transaction_confirmed: true, diff --git a/src/common/address_lookup.rs b/src/common/address_lookup.rs index 2e21e10..3debde0 100755 --- a/src/common/address_lookup.rs +++ b/src/common/address_lookup.rs @@ -1,17 +1,39 @@ use crate::common::SolanaRpcClient; use anyhow::Result; -use solana_address_lookup_table_interface::state::AddressLookupTable; -use solana_sdk::{message::AddressLookupTableAccount, pubkey::Pubkey}; +use solana_message::AddressLookupTableAccount; +use solana_sdk::pubkey::Pubkey; pub async fn fetch_address_lookup_table_account( rpc: &SolanaRpcClient, lookup_table_address: &Pubkey, ) -> Result { let account = rpc.get_account(lookup_table_address).await?; - let lookup_table = AddressLookupTable::deserialize(&account.data)?; + + // Parse address lookup table manually + // Layout: 4 bytes (type) + 4 bytes (deactivation_slot) + 4 bytes (last_extended_slot) + 1 byte (last_extended_slot_start_index) + 1 byte (authority) + padding + // Then addresses start at offset 56, each address is 32 bytes + // First 4 bytes indicate if initialized (should be 1 or 2) + + if account.data.len() < 56 { + return Err(anyhow::anyhow!("Address lookup table account data too short")); + } + + // Read number of addresses (stored at offset 20 as u32, but we need to scan the bitmap) + // Actually simpler: addresses start at offset 56, count from bitmap at offset 8-20 + let mut addresses = Vec::new(); + let mut offset = 56; + while offset + 32 <= account.data.len() { + let addr_bytes: [u8; 32] = account.data[offset..offset + 32].try_into()?; + // Skip zero addresses (unused slots) + if addr_bytes != [0u8; 32] { + addresses.push(Pubkey::from(addr_bytes)); + } + offset += 32; + } + let address_lookup_table_account = AddressLookupTableAccount { key: *lookup_table_address, - addresses: lookup_table.addresses.to_vec(), + addresses, }; Ok(address_lookup_table_account) } diff --git a/src/common/nonce_cache.rs b/src/common/nonce_cache.rs index e24f636..37fe35a 100755 --- a/src/common/nonce_cache.rs +++ b/src/common/nonce_cache.rs @@ -1,8 +1,5 @@ use crate::common::SolanaRpcClient; use solana_hash::Hash; -use solana_nonce::state::State; -use solana_nonce::versions::Versions; -use solana_sdk::account_utils::StateMut; use solana_sdk::pubkey::Pubkey; use tracing::error; @@ -21,18 +18,21 @@ pub async fn fetch_nonce_info( nonce_account: Pubkey, ) -> Option { match rpc.get_account(&nonce_account).await { - Ok(account) => match account.state() { - Ok(Versions::Current(state)) => { - if let State::Initialized(data) = *state { - let blockhash = data.durable_nonce.as_hash(); - return Some(DurableNonceInfo { - nonce_account: Some(nonce_account), - current_nonce: Some(*blockhash), - }); - } + Ok(account) => { + // Parse nonce account manually: first 4 bytes is version, then 4 bytes authority type + // For initialized nonce: version=0, authority_type=0, then authority (32 bytes), then blockhash (32 bytes), then fee_calculator + if account.data.len() >= 80 { + // Skip version (4) + authority_type (4) + authority (32) = 40 bytes + // Then blockhash is at offset 40 + let blockhash_bytes: [u8; 32] = account.data[40..72].try_into().ok()?; + return Some(DurableNonceInfo { + nonce_account: Some(nonce_account), + current_nonce: Some(Hash::from(blockhash_bytes)), + }); + } else { + error!("Nonce account data too short"); } - _ => (), - }, + } Err(e) => { error!("Failed to get nonce account information: {:?}", e); } diff --git a/src/common/seed.rs b/src/common/seed.rs index e308347..5c88a9f 100644 --- a/src/common/seed.rs +++ b/src/common/seed.rs @@ -3,7 +3,7 @@ use anyhow::anyhow; use fnv::FnvHasher; use once_cell::sync::Lazy; use solana_sdk::{instruction::Instruction, pubkey::Pubkey}; -use solana_system_interface::instruction::create_account_with_seed; +use solana_system_interface::instruction as system_instruction; use std::hash::Hasher; use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::Arc; @@ -92,9 +92,10 @@ pub fn create_associated_token_account_use_seed( let ata_like = Pubkey::create_with_seed(payer, seed, token_program)?; let len = 165; - // 但账户的 owner 仍然使用正确的 token_program(Token 或 Token-2022) + // 🔧 修复:create_account_with_seed 的第3个参数必须是 payer(与第92行生成地址时使用的 base 一致) + // 否则创建的账户地址与 ata_like 不匹配,导致 initializeAccount3 失败 let create_acc = - create_account_with_seed(payer, &ata_like, owner, seed, rent, len, token_program); + system_instruction::create_account_with_seed(payer, &ata_like, payer, seed, rent, len, token_program); let init_acc = if is_2022_token { crate::common::spl_token_2022::initialize_account3(&token_program, &ata_like, mint, owner)? diff --git a/src/common/spl_associated_token_account.rs b/src/common/spl_associated_token_account.rs index 8a2222c..ed047be 100644 --- a/src/common/spl_associated_token_account.rs +++ b/src/common/spl_associated_token_account.rs @@ -1,5 +1,5 @@ use solana_sdk::{ - message::{AccountMeta, Instruction}, + instruction::{AccountMeta, Instruction}, pubkey::Pubkey, }; diff --git a/src/common/spl_token.rs b/src/common/spl_token.rs index 6e6f4fc..fd89491 100644 --- a/src/common/spl_token.rs +++ b/src/common/spl_token.rs @@ -1,6 +1,6 @@ use solana_program::pubkey; use solana_sdk::{ - message::{AccountMeta, Instruction}, + instruction::{AccountMeta, Instruction}, program_error::ProgramError, pubkey::Pubkey, }; @@ -18,14 +18,14 @@ pub fn close_account( let mut data = Vec::with_capacity(1); data.push(9); let mut accounts = Vec::with_capacity(3 + signer_pubkeys.len()); - accounts.push(solana_sdk::message::AccountMeta::new(*account_pubkey, false)); - accounts.push(solana_sdk::message::AccountMeta::new(*destination_pubkey, false)); - accounts.push(solana_sdk::message::AccountMeta::new_readonly( + accounts.push(AccountMeta::new(*account_pubkey, false)); + accounts.push(AccountMeta::new(*destination_pubkey, false)); + accounts.push(AccountMeta::new_readonly( *owner_pubkey, signer_pubkeys.is_empty(), )); for signer_pubkey in signer_pubkeys.iter() { - accounts.push(solana_sdk::message::AccountMeta::new_readonly(**signer_pubkey, true)); + accounts.push(AccountMeta::new_readonly(**signer_pubkey, true)); } Ok(Instruction { program_id: *token_program_id, accounts, data }) } diff --git a/src/common/spl_token_2022.rs b/src/common/spl_token_2022.rs index d19230f..ed2925f 100644 --- a/src/common/spl_token_2022.rs +++ b/src/common/spl_token_2022.rs @@ -1,6 +1,6 @@ use solana_program::pubkey; use solana_sdk::{ - message::{AccountMeta, Instruction}, + instruction::{AccountMeta, Instruction}, program_error::ProgramError, pubkey::Pubkey, }; diff --git a/src/instruction/utils/pumpfun.rs b/src/instruction/utils/pumpfun.rs index bdd3f9a..1982df6 100644 --- a/src/instruction/utils/pumpfun.rs +++ b/src/instruction/utils/pumpfun.rs @@ -172,6 +172,24 @@ pub const BUY_DISCRIMINATOR: [u8; 8] = [102, 6, 61, 18, 1, 218, 235, 234]; pub const BUY_EXACT_SOL_IN_DISCRIMINATOR: [u8; 8] = [56, 252, 116, 8, 158, 223, 205, 95]; pub const SELL_DISCRIMINATOR: [u8; 8] = [51, 230, 133, 164, 1, 127, 131, 173]; +/// Check if a pubkey is one of the Mayhem fee recipients +#[inline] +pub fn is_mayhem_fee_recipient(pubkey: &Pubkey) -> bool { + global_constants::MAYHEM_FEE_RECIPIENTS.iter().any(|p| p == pubkey) +} + +/// Check if a pubkey is a Pump.fun AMM protocol fee recipient (PUMPFUN_AMM_FEE_1..7) +#[inline] +pub fn is_amm_fee_recipient(pubkey: &Pubkey) -> bool { + pubkey == &global_constants::PUMPFUN_AMM_FEE_1 + || pubkey == &global_constants::PUMPFUN_AMM_FEE_2 + || pubkey == &global_constants::PUMPFUN_AMM_FEE_3 + || pubkey == &global_constants::PUMPFUN_AMM_FEE_4 + || pubkey == &global_constants::PUMPFUN_AMM_FEE_5 + || pubkey == &global_constants::PUMPFUN_AMM_FEE_6 + || pubkey == &global_constants::PUMPFUN_AMM_FEE_7 +} + /// Returns a random Mayhem fee recipient AccountMeta (pump-public-docs: Bonding Curve 2nd account = Mayhem fee recipient; use any one randomly). #[inline] pub fn get_mayhem_fee_recipient_meta_random() -> AccountMeta { diff --git a/src/swqos/pb/serverpb.rs b/src/swqos/pb/serverpb.rs index c3e5d7c..a02e4fb 100644 --- a/src/swqos/pb/serverpb.rs +++ b/src/swqos/pb/serverpb.rs @@ -51,7 +51,7 @@ pub mod server_client { } impl ServerClient where - T: tonic::client::GrpcService, + T: tonic::client::GrpcService, T::Error: Into, T::ResponseBody: Body + std::marker::Send + 'static, ::Error: Into + std::marker::Send, @@ -72,13 +72,13 @@ pub mod server_client { F: tonic::service::Interceptor, T::ResponseBody: Default, T: tonic::codegen::Service< - http::Request, + http::Request, Response = http::Response< - >::ResponseBody, + >::ResponseBody, >, >, , + http::Request, >>::Error: Into + std::marker::Send + std::marker::Sync, { ServerClient::new(InterceptedService::new(inner, interceptor)) @@ -126,7 +126,7 @@ pub mod server_client { format!("Service was not ready: {}", e.into()), ) })?; - let codec = tonic_prost::ProstCodec::default(); + let codec = tonic::codec::ProstCodec::default(); let path = http::uri::PathAndQuery::from_static( "/serverpb.Server/SendTransaction", ); @@ -147,7 +147,7 @@ pub mod server_client { format!("Service was not ready: {}", e.into()), ) })?; - let codec = tonic_prost::ProstCodec::default(); + let codec = tonic::codec::ProstCodec::default(); let path = http::uri::PathAndQuery::from_static( "/serverpb.Server/GetHealth", ); @@ -244,7 +244,7 @@ pub mod server_server { B: Body + std::marker::Send + 'static, B::Error: Into + std::marker::Send + 'static, { - type Response = http::Response; + type Response = http::Response; type Error = std::convert::Infallible; type Future = BoxFuture; fn poll_ready( @@ -283,7 +283,7 @@ pub mod server_server { let inner = self.inner.clone(); let fut = async move { let method = SendTransactionSvc(inner); - let codec = tonic_prost::ProstCodec::default(); + let codec = tonic::codec::ProstCodec::default(); let mut grpc = tonic::server::Grpc::new(codec) .apply_compression_config( accept_compression_encodings, @@ -326,7 +326,7 @@ pub mod server_server { let inner = self.inner.clone(); let fut = async move { let method = GetHealthSvc(inner); - let codec = tonic_prost::ProstCodec::default(); + let codec = tonic::codec::ProstCodec::default(); let mut grpc = tonic::server::Grpc::new(codec) .apply_compression_config( accept_compression_encodings, @@ -344,7 +344,7 @@ pub mod server_server { _ => { Box::pin(async move { let mut response = http::Response::new( - tonic::body::Body::default(), + tonic::body::BoxBody::default(), ); let headers = response.headers_mut(); headers diff --git a/src/swqos/soyas.rs b/src/swqos/soyas.rs index 10b8e48..bb0eac2 100644 --- a/src/swqos/soyas.rs +++ b/src/swqos/soyas.rs @@ -8,7 +8,6 @@ use quinn::{ use rand::seq::IndexedRandom as _; use solana_client::rpc_client::SerializableTransaction; use solana_sdk::{signature::Keypair, transaction::VersionedTransaction}; -use solana_tls_utils::{new_dummy_x509_certificate, SkipServerVerification}; use std::time::Instant; use std::{ net::{SocketAddr, ToSocketAddrs as _}, @@ -25,6 +24,66 @@ use crate::{ swqos::{SwqosType, TradeType}, }; +// Skip server verification implementation +#[derive(Debug)] +struct SkipServerVerification; + +impl SkipServerVerification { + fn new() -> Arc { + Arc::new(Self) + } +} + +impl rustls::client::danger::ServerCertVerifier for SkipServerVerification { + fn verify_server_cert( + &self, + _end_entity: &rustls::pki_types::CertificateDer<'_>, + _intermediates: &[rustls::pki_types::CertificateDer<'_>], + _server_name: &rustls::pki_types::ServerName<'_>, + _ocsp_response: &[u8], + _now: rustls::pki_types::UnixTime, + ) -> Result { + Ok(rustls::client::danger::ServerCertVerified::assertion()) + } + + fn verify_tls12_signature( + &self, + _message: &[u8], + _cert: &rustls::pki_types::CertificateDer<'_>, + _dss: &rustls::DigitallySignedStruct, + ) -> Result { + Ok(rustls::client::danger::HandshakeSignatureValid::assertion()) + } + + fn verify_tls13_signature( + &self, + _message: &[u8], + _cert: &rustls::pki_types::CertificateDer<'_>, + _dss: &rustls::DigitallySignedStruct, + ) -> Result { + Ok(rustls::client::danger::HandshakeSignatureValid::assertion()) + } + + fn supported_verify_schemes(&self) -> Vec { + vec![rustls::SignatureScheme::ECDSA_NISTP256_SHA256] + } +} + +// Generate dummy self-signed certificate using rcgen +fn generate_self_signed_cert(_keypair: &Keypair) -> Result<(rustls::pki_types::CertificateDer<'static>, rustls::pki_types::PrivateKeyDer<'static>)> { + // Generate a new key pair for the certificate + let key_pair = rcgen::KeyPair::generate()?; + + let params = rcgen::CertificateParams::default(); + let cert = params.self_signed(&key_pair)?; + + let cert_der = rustls::pki_types::CertificateDer::from(cert.der().to_vec()); + let key_der = rustls::pki_types::PrivateKeyDer::try_from(key_pair.serialize_der()) + .map_err(|e| anyhow::anyhow!("Failed to create private key: {:?}", e))?; + + Ok((cert_der, key_der)) +} + const ALPN_TPU_PROTOCOL_ID: &[u8] = b"solana-tpu"; const SOYAS_SERVER: &str = "soyas-landing"; const KEEP_ALIVE_INTERVAL: Duration = Duration::from_secs(25); @@ -43,7 +102,7 @@ impl SoyasClient { pub async fn new(rpc_url: String, endpoint_string: String, api_key: String) -> Result { let rpc_client = SolanaRpcClient::new(rpc_url); let keypair = Keypair::from_base58_string(&api_key); - let (cert, key) = new_dummy_x509_certificate(&keypair); + let (cert, key) = generate_self_signed_cert(&keypair)?; let mut crypto = rustls::ClientConfig::builder() .dangerous() .with_custom_certificate_verifier(SkipServerVerification::new()) diff --git a/src/swqos/speedlanding.rs b/src/swqos/speedlanding.rs index c725624..8ffc39a 100644 --- a/src/swqos/speedlanding.rs +++ b/src/swqos/speedlanding.rs @@ -7,7 +7,6 @@ use quinn::{ }; use rand::seq::IndexedRandom as _; use solana_sdk::{signature::Keypair, transaction::VersionedTransaction}; -use solana_tls_utils::{new_dummy_x509_certificate, SkipServerVerification}; use std::time::Instant; use std::{ net::{SocketAddr, ToSocketAddrs as _}, @@ -26,6 +25,66 @@ use crate::{ swqos::{SwqosType, TradeType}, }; +// Skip server verification implementation +#[derive(Debug)] +struct SkipServerVerification; + +impl SkipServerVerification { + fn new() -> Arc { + Arc::new(Self) + } +} + +impl rustls::client::danger::ServerCertVerifier for SkipServerVerification { + fn verify_server_cert( + &self, + _end_entity: &rustls::pki_types::CertificateDer<'_>, + _intermediates: &[rustls::pki_types::CertificateDer<'_>], + _server_name: &rustls::pki_types::ServerName<'_>, + _ocsp_response: &[u8], + _now: rustls::pki_types::UnixTime, + ) -> Result { + Ok(rustls::client::danger::ServerCertVerified::assertion()) + } + + fn verify_tls12_signature( + &self, + _message: &[u8], + _cert: &rustls::pki_types::CertificateDer<'_>, + _dss: &rustls::DigitallySignedStruct, + ) -> Result { + Ok(rustls::client::danger::HandshakeSignatureValid::assertion()) + } + + fn verify_tls13_signature( + &self, + _message: &[u8], + _cert: &rustls::pki_types::CertificateDer<'_>, + _dss: &rustls::DigitallySignedStruct, + ) -> Result { + Ok(rustls::client::danger::HandshakeSignatureValid::assertion()) + } + + fn supported_verify_schemes(&self) -> Vec { + vec![rustls::SignatureScheme::ECDSA_NISTP256_SHA256] + } +} + +// Generate dummy self-signed certificate using rcgen +fn generate_self_signed_cert(_keypair: &Keypair) -> Result<(rustls::pki_types::CertificateDer<'static>, rustls::pki_types::PrivateKeyDer<'static>)> { + // Generate a new key pair for the certificate + let key_pair = rcgen::KeyPair::generate()?; + + let params = rcgen::CertificateParams::default(); + let cert = params.self_signed(&key_pair)?; + + let cert_der = rustls::pki_types::CertificateDer::from(cert.der().to_vec()); + let key_der = rustls::pki_types::PrivateKeyDer::try_from(key_pair.serialize_der()) + .map_err(|e| anyhow::anyhow!("Failed to create private key: {:?}", e))?; + + Ok((cert_der, key_der)) +} + const ALPN_TPU_PROTOCOL_ID: &[u8] = b"solana-tpu"; /// Fallback SNI when endpoint is IP or cannot extract host (keeps legacy behavior). const SPEED_SERVER_FALLBACK: &str = "speed-landing"; @@ -66,7 +125,7 @@ impl SpeedlandingClient { let rpc_client = SolanaRpcClient::new(rpc_url); let server_name = Self::server_name_from_endpoint(&endpoint_string); let keypair = Keypair::from_base58_string(&api_key); - let (cert, key) = new_dummy_x509_certificate(&keypair); + let (cert, key) = generate_self_signed_cert(&keypair)?; let mut crypto = rustls::ClientConfig::builder() .dangerous() .with_custom_certificate_verifier(SkipServerVerification::new()) diff --git a/src/trading/common/transaction_builder.rs b/src/trading/common/transaction_builder.rs index 179ad0b..8ef1eb8 100755 --- a/src/trading/common/transaction_builder.rs +++ b/src/trading/common/transaction_builder.rs @@ -1,9 +1,10 @@ use solana_hash::Hash; use solana_sdk::{ - instruction::Instruction, message::AddressLookupTableAccount, pubkey::Pubkey, + instruction::Instruction, pubkey::Pubkey, signature::Keypair, signer::Signer, transaction::VersionedTransaction, }; -use solana_system_interface::instruction::transfer; +use solana_message::AddressLookupTableAccount; +use solana_system_interface::instruction as system_instruction; use std::sync::Arc; use super::nonce_manager::{add_nonce_instruction, get_transaction_blockhash}; @@ -51,7 +52,7 @@ pub async fn build_transaction( if with_tip && tip_amount > 0.0 { let tip_lamports = sol_f64_to_lamports(tip_amount); - instructions.push(transfer(&payer.pubkey(), tip_account, tip_lamports)); + instructions.push(system_instruction::transfer(&payer.pubkey(), tip_account, tip_lamports)); } super::compute_budget_manager::extend_compute_budget_instructions( diff --git a/src/trading/common/utils.rs b/src/trading/common/utils.rs index a4fbc24..2999fbe 100644 --- a/src/trading/common/utils.rs +++ b/src/trading/common/utils.rs @@ -1,5 +1,5 @@ use solana_sdk::{pubkey::Pubkey, signature::Keypair, signer::Signer, transaction::Transaction}; -use solana_system_interface::instruction::transfer; +use solana_system_interface::instruction as system_instruction; use crate::common::{ fast_fn::{ @@ -88,7 +88,7 @@ pub async fn transfer_sol( return Err(anyhow!("Insufficient balance")); } - let transfer_instruction = transfer(&payer.pubkey(), receive_wallet, amount); + let transfer_instruction = system_instruction::transfer(&payer.pubkey(), receive_wallet, amount); let recent_blockhash = rpc.get_latest_blockhash().await?; diff --git a/src/trading/common/wsol_manager.rs b/src/trading/common/wsol_manager.rs index dd8b497..be8773a 100644 --- a/src/trading/common/wsol_manager.rs +++ b/src/trading/common/wsol_manager.rs @@ -7,8 +7,8 @@ use crate::common::{ spl_token::close_account, }; use smallvec::SmallVec; -use solana_sdk::{instruction::Instruction, message::AccountMeta, pubkey::Pubkey}; -use solana_system_interface::instruction::transfer; +use solana_sdk::{instruction::Instruction, instruction::AccountMeta, pubkey::Pubkey}; +use solana_system_interface::instruction as system_instruction; #[inline] pub fn handle_wsol(payer: &Pubkey, amount_in: u64) -> SmallVec<[Instruction; 3]> { @@ -27,7 +27,7 @@ pub fn handle_wsol(payer: &Pubkey, amount_in: u64) -> SmallVec<[Instruction; 3]> &crate::constants::TOKEN_PROGRAM, )); insts.extend([ - transfer(&payer, &wsol_token_account, amount_in), + system_instruction::transfer(&payer, &wsol_token_account, amount_in), // sync_native Instruction { program_id: crate::constants::TOKEN_PROGRAM, @@ -91,7 +91,7 @@ pub fn wrap_sol_only(payer: &Pubkey, amount_in: u64) -> SmallVec<[Instruction; 2 let mut insts = SmallVec::<[Instruction; 2]>::new(); insts.extend([ - transfer(&payer, &wsol_token_account, amount_in), + system_instruction::transfer(&payer, &wsol_token_account, amount_in), // sync_native Instruction { program_id: crate::constants::TOKEN_PROGRAM, diff --git a/src/trading/core/async_executor.rs b/src/trading/core/async_executor.rs index c4b737a..e60dba5 100644 --- a/src/trading/core/async_executor.rs +++ b/src/trading/core/async_executor.rs @@ -15,7 +15,7 @@ use anyhow::{anyhow, Result}; use crossbeam_queue::ArrayQueue; use once_cell::sync::OnceCell; use solana_hash::Hash; -use solana_sdk::message::AddressLookupTableAccount; +use solana_message::AddressLookupTableAccount; use solana_sdk::{ instruction::Instruction, pubkey::Pubkey, signature::Keypair, signature::Signature, }; diff --git a/src/trading/core/executor.rs b/src/trading/core/executor.rs index aafd9b9..ec218ad 100755 --- a/src/trading/core/executor.rs +++ b/src/trading/core/executor.rs @@ -1,9 +1,10 @@ use anyhow::Result; use solana_hash::Hash; use solana_sdk::{ - instruction::Instruction, message::AddressLookupTableAccount, pubkey::Pubkey, + instruction::Instruction, pubkey::Pubkey, signature::Keypair, signature::Signature, }; +use solana_message::AddressLookupTableAccount; use std::{ sync::Arc, time::{Duration, Instant}, diff --git a/src/trading/core/params.rs b/src/trading/core/params.rs index 71bb06f..8f5243e 100755 --- a/src/trading/core/params.rs +++ b/src/trading/core/params.rs @@ -4,7 +4,7 @@ use crate::common::spl_associated_token_account::get_associated_token_address_wi use crate::common::{GasFeeStrategy, SolanaRpcClient}; use crate::constants::TOKEN_PROGRAM; use core_affinity::CoreId; -use crate::instruction::utils::pumpfun::global_constants::MAYHEM_FEE_RECIPIENT; +use crate::instruction::utils::pumpfun::is_mayhem_fee_recipient; /// Concurrency + core binding config for parallel submit (precomputed at SDK init, one param on hot path). Uses Arc so no borrow of SwapParams. #[derive(Clone)] @@ -18,7 +18,7 @@ use crate::swqos::{SwqosClient, TradeType}; use crate::trading::common::get_multi_token_balances; use crate::trading::MiddlewareManager; use solana_hash::Hash; -use solana_sdk::message::AddressLookupTableAccount; +use solana_message::AddressLookupTableAccount; use solana_sdk::{pubkey::Pubkey, signature::Keypair}; use std::sync::Arc; @@ -152,6 +152,8 @@ impl PumpFunParams { /// When building from event/parser (e.g. sol-parser-sdk), pass `is_cashback_coin` from the event /// so that sell instructions include the correct remaining accounts for cashback. + /// `mayhem_mode`: `Some` when known from Create/Trade event (`is_mayhem_mode` / `mayhem_mode`). + /// `None` falls back to detecting Mayhem via reserved fee recipient pubkeys only (not AMM protocol fee accounts). pub fn from_dev_trade( mint: Pubkey, token_amount: u64, @@ -164,8 +166,10 @@ impl PumpFunParams { fee_recipient: Pubkey, token_program: Pubkey, is_cashback_coin: bool, + mayhem_mode: Option, ) -> Self { - let is_mayhem_mode = fee_recipient == MAYHEM_FEE_RECIPIENT; + let is_mayhem_mode = + mayhem_mode.unwrap_or_else(|| is_mayhem_fee_recipient(&fee_recipient)); let bonding_curve_account = BondingCurveAccount::from_dev_trade( bonding_curve, &mint, @@ -186,6 +190,7 @@ impl PumpFunParams { /// When building from event/parser (e.g. sol-parser-sdk), pass `is_cashback_coin` from the event /// so that sell instructions include the correct remaining accounts for cashback. + /// `mayhem_mode`: `Some` when known from Create/Trade event. `None` → infer from `fee_recipient` (Mayhem list only). pub fn from_trade( bonding_curve: Pubkey, associated_bonding_curve: Pubkey, @@ -200,8 +205,10 @@ impl PumpFunParams { fee_recipient: Pubkey, token_program: Pubkey, is_cashback_coin: bool, + mayhem_mode: Option, ) -> Self { - let is_mayhem_mode = fee_recipient == MAYHEM_FEE_RECIPIENT; + let is_mayhem_mode = + mayhem_mode.unwrap_or_else(|| is_mayhem_fee_recipient(&fee_recipient)); let bonding_curve = BondingCurveAccount::from_trade( bonding_curve, mint, diff --git a/src/trading/core/transaction_pool.rs b/src/trading/core/transaction_pool.rs index e3b57d3..0570694 100644 --- a/src/trading/core/transaction_pool.rs +++ b/src/trading/core/transaction_pool.rs @@ -22,9 +22,10 @@ use once_cell::sync::Lazy; use solana_sdk::{ hash::Hash, instruction::Instruction, - message::{v0, AddressLookupTableAccount, Message, VersionedMessage}, + message::{v0, Message, VersionedMessage}, pubkey::Pubkey, }; +use solana_message::AddressLookupTableAccount; use std::sync::Arc; /// 预分配的交易构建器 pub struct PreallocatedTxBuilder { diff --git a/src/utils/calc/pumpfun.rs b/src/utils/calc/pumpfun.rs index 986017e..946ee7f 100644 --- a/src/utils/calc/pumpfun.rs +++ b/src/utils/calc/pumpfun.rs @@ -1,10 +1,22 @@ -use solana_sdk::{native_token::sol_str_to_lamports, pubkey::Pubkey}; +// Note: sol_to_lamports moved to solana_native_token crate in 3.x +// Using manual conversion: 1 SOL = 1_000_000_000 lamports +use solana_sdk::pubkey::Pubkey; + +fn sol_to_lamports(sol: f64) -> u64 { + (sol * 1_000_000_000.0) as u64 +} use crate::{ instruction::utils::pumpfun::global_constants::{CREATOR_FEE, FEE_BASIS_POINTS}, utils::calc::common::compute_fee, }; +/// Converts SOL string to lamports (wrapper for sol_to_lamports) +#[inline] +fn sol_str_to_lamports(sol: &str) -> Option { + sol.parse::().ok().map(|s| sol_to_lamports(s)) +} + /// Calculates the amount of tokens that can be purchased with a given SOL amount /// using the bonding curve formula. /// diff --git a/test_latency.sh b/test_latency.sh index 5b52236..82aea67 100755 --- a/test_latency.sh +++ b/test_latency.sh @@ -81,6 +81,7 @@ mkdir -p examples/pumpfun_buy_test/src cat > examples/pumpfun_buy_test/src/main.rs << 'EOF' use sol_trade_sdk::{ common::{TradeConfig, AnyResult}, + constants::TOKEN_PROGRAM, swqos::{SwqosConfig, SwqosRegion}, trading::{core::params::PumpFunParams, factory::DexType}, SolanaTrade, TradeTokenType, TradeBuyParams, @@ -166,7 +167,7 @@ async fn main() -> AnyResult<()> { println!("📊 滑点: {} basis points", slippage); println!("================================\n"); - // PumpFun买入参数 (买入不需要特殊参数,使用零值) + // PumpFun买入参数 (占位;实际路径应使用 RPC 或事件填充) let params = PumpFunParams::from_trade( Pubkey::default(), // bonding_curve Pubkey::default(), // associated_bonding_curve @@ -178,6 +179,10 @@ async fn main() -> AnyResult<()> { 0, // real_token_reserves 0, // real_sol_reserves None, // close_token_account_when_sell + Pubkey::default(), // fee_recipient + TOKEN_PROGRAM, + false, // is_cashback_coin + None, // mayhem_mode(None=按 fee_recipient 推断) ); let buy_params = TradeBuyParams {