From 48a6c6283a9f1d4655b1f6bfafb5fc9a43fd7f4e Mon Sep 17 00:00:00 2001 From: ysq Date: Tue, 9 Sep 2025 17:02:24 +0800 Subject: [PATCH] refactor: optimize trading execution architecture and add wSOL management - Simplify TradeExecutor interface by integrating middleware and swqos clients into params - Refactor parallel execution module with dedicated buy/sell execute functions - Add SOL wrapping/unwrapping functionality for wSOL management - Remove redundant parameter passing in sell operations - Delete example main.rs file - Optimize transaction building with integrated parameter structures BREAKING CHANGE: TradeExecutor interface simplified, middleware and swqos_clients now passed through params --- Cargo.toml | 1 + README.md | 2 +- README_CN.md | 2 +- examples/address_lookup/src/main.rs | 8 +- examples/bonk_copy_trading/src/main.rs | 3 +- examples/bonk_sniper_trading/src/main.rs | 3 +- examples/middleware_system/src/main.rs | 2 +- examples/nonce_cache/src/main.rs | 2 +- examples/pumpfun_copy_trading/src/main.rs | 3 +- examples/pumpfun_sniper_trading/src/main.rs | 3 +- examples/pumpswap_direct_trading/src/main.rs | 3 +- examples/pumpswap_trading/src/main.rs | 3 +- examples/raydium_amm_v4_trading/src/main.rs | 3 +- examples/raydium_cpmm_trading/src/main.rs | 3 +- examples/trading_client/src/main.rs | 1 - examples/wsol_wrapper/Cargo.toml | 10 + examples/wsol_wrapper/src/main.rs | 69 +++ src/common/fast_fn.rs | 146 ++++- src/common/mod.rs | 1 + src/common/seed.rs | 114 ++++ src/common/types.rs | 6 +- src/constants/accounts.rs | 2 + src/instruction/bonk.rs | 20 +- src/instruction/pumpfun.rs | 42 +- src/instruction/pumpswap.rs | 49 +- src/instruction/raydium_amm_v4.rs | 32 +- src/instruction/raydium_cpmm.rs | 32 +- src/lib.rs | 93 ++- src/main.rs | 618 ------------------- src/trading/common/wsol_manager.rs | 22 +- src/trading/core/executor.rs | 61 +- src/trading/core/parallel.rs | 48 +- src/trading/core/params.rs | 12 +- src/trading/core/traits.rs | 20 +- 34 files changed, 586 insertions(+), 853 deletions(-) create mode 100644 examples/wsol_wrapper/Cargo.toml create mode 100644 examples/wsol_wrapper/src/main.rs create mode 100644 src/common/seed.rs delete mode 100755 src/main.rs diff --git a/Cargo.toml b/Cargo.toml index 1e743fe..da63201 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,7 @@ members = [ "examples/address_lookup", "examples/nonce_cache", "examples/pumpswap_direct_trading", + "examples/wsol_wrapper", ] [lib] diff --git a/README.md b/README.md index 895e8a8..1df6ca0 100644 --- a/README.md +++ b/README.md @@ -106,6 +106,7 @@ Please ensure that the parameters your trading logic depends on are available in | Middleware System | `middleware_system` | Custom instruction middleware example | `cargo run --package middleware_system` | [examples/middleware_system](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/middleware_system/src/main.rs) | | Address Lookup | `address_lookup` | Address lookup table example | `cargo run --package address_lookup` | [examples/address_lookup](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/address_lookup/src/main.rs) | | Nonce | `nonce_cache` | Nonce example | `cargo run --package nonce_cache` | [examples/nonce_cache](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/nonce_cache/src/main.rs) | +| WSOL Wrapper | `wsol_wrapper` | Wrap/unwrap SOL to/from WSOL example | `cargo run --package wsol_wrapper` | [examples/wsol_wrapper](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/wsol_wrapper/src/main.rs) | ### SWQOS Service Configuration @@ -181,7 +182,6 @@ let trade_config = TradeConfig { commitment: CommitmentConfig::confirmed(), priority_fee, // Use custom priority fee swqos_configs, - lookup_table_key: None, }; ``` diff --git a/README_CN.md b/README_CN.md index 2062088..f4dc11f 100755 --- a/README_CN.md +++ b/README_CN.md @@ -106,6 +106,7 @@ sol-trade-sdk = "0.6.1" | 中间件系统 | `middleware_system` | 自定义指令中间件示例 | `cargo run --package middleware_system` | [examples/middleware_system](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/middleware_system/src/main.rs) | | 地址查找表 | `address_lookup` | 地址查找表示例 | `cargo run --package address_lookup` | [examples/address_lookup](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/address_lookup/src/main.rs) | | Nonce | `nonce_cache` | Nonce示例 | `cargo run --package nonce_cache` | [examples/nonce_cache](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/nonce_cache/src/main.rs) | +| WSOL 包装器 | `wsol_wrapper` | SOL与WSOL相互转换示例 | `cargo run --package wsol_wrapper` | [examples/wsol_wrapper](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/wsol_wrapper/src/main.rs) | ### SWQOS 服务配置说明 @@ -181,7 +182,6 @@ let trade_config = TradeConfig { commitment: CommitmentConfig::confirmed(), priority_fee, // 使用自定义优先费用 swqos_configs, - lookup_table_key: None, }; ``` diff --git a/examples/address_lookup/src/main.rs b/examples/address_lookup/src/main.rs index 9604443..95180f4 100644 --- a/examples/address_lookup/src/main.rs +++ b/examples/address_lookup/src/main.rs @@ -133,7 +133,6 @@ async fn create_solana_trade_client() -> AnyResult { commitment: CommitmentConfig::confirmed(), priority_fee: priority_fee, swqos_configs, - lookup_table_key: Some(Pubkey::from_str("use_your_lookup_table_key_here").unwrap()), }; let solana_trade_client = SolanaTrade::new(Arc::new(payer), trade_config).await; @@ -152,9 +151,9 @@ async fn pumpfun_copy_trade_with_grpc(trade_info: PumpFunTradeEvent) -> AnyResul let slippage_basis_points = Some(100); let recent_blockhash = client.rpc.get_latest_blockhash().await?; + let lookup_table_key = Pubkey::from_str("use_your_lookup_table_key_here").unwrap(); // Setup lookup table cache - setup_lookup_table_cache(client.rpc.clone(), client.trade_config.lookup_table_key.unwrap()) - .await?; + setup_lookup_table_cache(client.rpc.clone(), lookup_table_key).await?; // Buy tokens println!("Buying tokens from PumpFun..."); @@ -168,8 +167,9 @@ async fn pumpfun_copy_trade_with_grpc(trade_info: PumpFunTradeEvent) -> AnyResul recent_blockhash, None, Box::new(PumpFunParams::from_trade(&trade_info, None)), - None, // You can also pass a new address lookup table account here, but you still need to update the AddressLookupTableCache + Some(lookup_table_key), // you still need to update the AddressLookupTableCache true, + false, ) .await?; diff --git a/examples/bonk_copy_trading/src/main.rs b/examples/bonk_copy_trading/src/main.rs index 210b999..e70297e 100644 --- a/examples/bonk_copy_trading/src/main.rs +++ b/examples/bonk_copy_trading/src/main.rs @@ -119,7 +119,6 @@ async fn create_solana_trade_client() -> AnyResult { commitment: CommitmentConfig::confirmed(), priority_fee: priority_fee, swqos_configs, - lookup_table_key: None, }; let solana_trade_client = SolanaTrade::new(Arc::new(payer), trade_config).await; @@ -152,6 +151,7 @@ async fn bonk_copy_trade_with_grpc(trade_info: BonkTradeEvent) -> AnyResult<()> Box::new(BonkParams::from_trade(trade_info.clone())), None, true, + false, ) .await?; @@ -178,6 +178,7 @@ async fn bonk_copy_trade_with_grpc(trade_info: BonkTradeEvent) -> AnyResult<()> Box::new(BonkParams::from_trade(trade_info.clone())), None, true, + false, ) .await?; diff --git a/examples/bonk_sniper_trading/src/main.rs b/examples/bonk_sniper_trading/src/main.rs index d31328f..c8623e0 100644 --- a/examples/bonk_sniper_trading/src/main.rs +++ b/examples/bonk_sniper_trading/src/main.rs @@ -88,7 +88,6 @@ async fn create_solana_trade_client() -> AnyResult { commitment: CommitmentConfig::confirmed(), priority_fee: priority_fee, swqos_configs, - lookup_table_key: None, }; let solana_trade_client = SolanaTrade::new(Arc::new(payer), trade_config).await; @@ -121,6 +120,7 @@ async fn bonk_sniper_trade_with_shreds(trade_info: BonkTradeEvent) -> AnyResult< Box::new(BonkParams::from_dev_trade(trade_info.clone())), None, true, + false, ) .await?; @@ -152,6 +152,7 @@ async fn bonk_sniper_trade_with_shreds(trade_info: BonkTradeEvent) -> AnyResult< )), None, true, + false, ) .await?; diff --git a/examples/middleware_system/src/main.rs b/examples/middleware_system/src/main.rs index 3bc3914..5496bff 100644 --- a/examples/middleware_system/src/main.rs +++ b/examples/middleware_system/src/main.rs @@ -72,7 +72,6 @@ async fn create_solana_trade_client() -> AnyResult { commitment: CommitmentConfig::confirmed(), priority_fee: PriorityFee::default(), swqos_configs, - lookup_table_key: None, }; let solana_trade_client = SolanaTrade::new(Arc::new(payer), trade_config).await; @@ -103,6 +102,7 @@ async fn test_middleware() -> AnyResult<()> { Box::new(PumpSwapParams::from_pool_address_by_rpc(&client.rpc, &pool_address).await?), None, true, + false, ) .await?; println!("tip: This transaction will not succeed because we're using a test account. You can modify the code to initialize the payer with your own private key"); diff --git a/examples/nonce_cache/src/main.rs b/examples/nonce_cache/src/main.rs index 54927ef..5f832ca 100644 --- a/examples/nonce_cache/src/main.rs +++ b/examples/nonce_cache/src/main.rs @@ -114,7 +114,6 @@ async fn create_solana_trade_client() -> AnyResult { commitment: CommitmentConfig::confirmed(), priority_fee: priority_fee, swqos_configs, - lookup_table_key: None, }; let solana_trade_client = SolanaTrade::new(Arc::new(payer), trade_config).await; @@ -153,6 +152,7 @@ async fn pumpfun_copy_trade_with_grpc(trade_info: PumpFunTradeEvent) -> AnyResul Box::new(PumpFunParams::from_trade(&trade_info, None)), None, true, + false, ) .await?; diff --git a/examples/pumpfun_copy_trading/src/main.rs b/examples/pumpfun_copy_trading/src/main.rs index 60aed62..a5ff3e7 100644 --- a/examples/pumpfun_copy_trading/src/main.rs +++ b/examples/pumpfun_copy_trading/src/main.rs @@ -113,7 +113,6 @@ async fn create_solana_trade_client() -> AnyResult { commitment: CommitmentConfig::confirmed(), priority_fee: priority_fee, swqos_configs, - lookup_table_key: None, }; let solana_trade_client = SolanaTrade::new(Arc::new(payer), trade_config).await; @@ -146,6 +145,7 @@ async fn pumpfun_copy_trade_with_grpc(trade_info: PumpFunTradeEvent) -> AnyResul Box::new(PumpFunParams::from_trade(&trade_info, None)), None, true, + false, ) .await?; @@ -172,6 +172,7 @@ async fn pumpfun_copy_trade_with_grpc(trade_info: PumpFunTradeEvent) -> AnyResul Box::new(PumpFunParams::from_trade(&trade_info, Some(true))), None, true, + false, ) .await?; diff --git a/examples/pumpfun_sniper_trading/src/main.rs b/examples/pumpfun_sniper_trading/src/main.rs index c64d9c8..587c70a 100644 --- a/examples/pumpfun_sniper_trading/src/main.rs +++ b/examples/pumpfun_sniper_trading/src/main.rs @@ -81,7 +81,6 @@ async fn create_solana_trade_client() -> AnyResult { commitment: CommitmentConfig::confirmed(), priority_fee: priority_fee, swqos_configs, - lookup_table_key: None, }; let solana_trade_client = SolanaTrade::new(Arc::new(payer), trade_config).await; @@ -114,6 +113,7 @@ async fn pumpfun_sniper_trade_with_shreds(trade_info: PumpFunTradeEvent) -> AnyR Box::new(PumpFunParams::from_dev_trade(&trade_info, None)), None, true, + false, ) .await?; @@ -140,6 +140,7 @@ async fn pumpfun_sniper_trade_with_shreds(trade_info: PumpFunTradeEvent) -> AnyR Box::new(PumpFunParams::immediate_sell(trade_info.creator_vault, true)), None, true, + false, ) .await?; diff --git a/examples/pumpswap_direct_trading/src/main.rs b/examples/pumpswap_direct_trading/src/main.rs index 98bea2b..b93830a 100644 --- a/examples/pumpswap_direct_trading/src/main.rs +++ b/examples/pumpswap_direct_trading/src/main.rs @@ -33,6 +33,7 @@ async fn main() -> Result<(), Box> { Box::new(PumpSwapParams::from_pool_address_by_rpc(&client.rpc, &pool).await?), None, true, + false, ) .await?; @@ -57,6 +58,7 @@ async fn main() -> Result<(), Box> { Box::new(PumpSwapParams::from_pool_address_by_rpc(&client.rpc, &pool).await?), None, true, + false, ) .await?; @@ -84,7 +86,6 @@ async fn create_solana_trade_client() -> AnyResult { commitment: CommitmentConfig::confirmed(), priority_fee: priority_fee, swqos_configs, - lookup_table_key: None, }; let solana_trade_client = SolanaTrade::new(Arc::new(payer), trade_config).await; diff --git a/examples/pumpswap_trading/src/main.rs b/examples/pumpswap_trading/src/main.rs index 2bf5aaa..92c337c 100644 --- a/examples/pumpswap_trading/src/main.rs +++ b/examples/pumpswap_trading/src/main.rs @@ -136,7 +136,6 @@ async fn create_solana_trade_client() -> AnyResult { commitment: CommitmentConfig::confirmed(), priority_fee: priority_fee, swqos_configs, - lookup_table_key: None, }; let solana_trade_client = SolanaTrade::new(Arc::new(payer), trade_config).await; @@ -188,6 +187,7 @@ async fn pumpswap_trade_with_grpc(mint_pubkey: Pubkey, params: PumpSwapParams) - Box::new(params.clone()), None, true, + false, ) .await?; @@ -216,6 +216,7 @@ async fn pumpswap_trade_with_grpc(mint_pubkey: Pubkey, params: PumpSwapParams) - Box::new(params.clone()), None, true, + false, ) .await?; diff --git a/examples/raydium_amm_v4_trading/src/main.rs b/examples/raydium_amm_v4_trading/src/main.rs index 69bf04a..1404e5d 100644 --- a/examples/raydium_amm_v4_trading/src/main.rs +++ b/examples/raydium_amm_v4_trading/src/main.rs @@ -113,7 +113,6 @@ async fn create_solana_trade_client() -> AnyResult { commitment: CommitmentConfig::confirmed(), priority_fee: priority_fee, swqos_configs, - lookup_table_key: None, }; let solana_trade_client = SolanaTrade::new(Arc::new(payer), trade_config).await; @@ -159,6 +158,7 @@ async fn raydium_amm_v4_copy_trade_with_grpc(trade_info: RaydiumAmmV4SwapEvent) Box::new(params), None, true, + false, ) .await?; @@ -186,6 +186,7 @@ async fn raydium_amm_v4_copy_trade_with_grpc(trade_info: RaydiumAmmV4SwapEvent) Box::new(params), None, true, + false, ) .await?; diff --git a/examples/raydium_cpmm_trading/src/main.rs b/examples/raydium_cpmm_trading/src/main.rs index 9bd7440..d02ed5f 100644 --- a/examples/raydium_cpmm_trading/src/main.rs +++ b/examples/raydium_cpmm_trading/src/main.rs @@ -123,7 +123,6 @@ async fn create_solana_trade_client() -> AnyResult { commitment: CommitmentConfig::confirmed(), priority_fee: priority_fee, swqos_configs, - lookup_table_key: None, }; let solana_trade_client = SolanaTrade::new(Arc::new(payer), trade_config).await; @@ -163,6 +162,7 @@ async fn raydium_cpmm_copy_trade_with_grpc(trade_info: RaydiumCpmmSwapEvent) -> Box::new(buy_params), None, true, + false, ) .await?; @@ -192,6 +192,7 @@ async fn raydium_cpmm_copy_trade_with_grpc(trade_info: RaydiumCpmmSwapEvent) -> Box::new(sell_params), None, true, + false, ) .await?; diff --git a/examples/trading_client/src/main.rs b/examples/trading_client/src/main.rs index 9b84fe9..72c2b33 100644 --- a/examples/trading_client/src/main.rs +++ b/examples/trading_client/src/main.rs @@ -56,6 +56,5 @@ fn create_trade_config(rpc_url: String, swqos_configs: Vec) -> Trad commitment: CommitmentConfig::confirmed(), priority_fee: PriorityFee::default(), swqos_configs, - lookup_table_key: None, } } diff --git a/examples/wsol_wrapper/Cargo.toml b/examples/wsol_wrapper/Cargo.toml new file mode 100644 index 0000000..b33d376 --- /dev/null +++ b/examples/wsol_wrapper/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "wsol_wrapper" +version = "0.1.0" +edition = "2021" + +[dependencies] +sol-trade-sdk = { path = "../.." } +solana-sdk = "2.3.0" +tokio = { version = "1", features = ["full"] } +anyhow = "1.0" \ No newline at end of file diff --git a/examples/wsol_wrapper/src/main.rs b/examples/wsol_wrapper/src/main.rs new file mode 100644 index 0000000..93f6a8e --- /dev/null +++ b/examples/wsol_wrapper/src/main.rs @@ -0,0 +1,69 @@ +use sol_trade_sdk::{ + common::{PriorityFee, TradeConfig}, + SolanaTrade, +}; +use solana_sdk::{commitment_config::CommitmentConfig, signature::Keypair}; +use std::sync::Arc; + +#[tokio::main] +async fn main() -> Result<(), Box> { + println!("🔄 WSOL Wrapper Example"); + println!("This example demonstrates how to wrap SOL to WSOL and unwrap WSOL back to SOL"); + + // Initialize SolanaTrade client + let solana_trade = create_solana_trade_client().await?; + + // Example 1: Wrap SOL to WSOL + println!("\n📦 Example 1: Wrapping SOL to WSOL"); + let wrap_amount = 1_000_000; // 0.001 SOL in lamports + println!("Wrapping {} lamports (0.001 SOL) to WSOL...", wrap_amount); + + match solana_trade.wrap_sol_to_wsol(wrap_amount).await { + Ok(signature) => { + println!("✅ Successfully wrapped SOL to WSOL!"); + println!("Transaction signature: {}", signature); + println!("Explorer: https://solscan.io/tx/{}", signature); + } + Err(e) => { + println!("❌ Failed to wrap SOL to WSOL: {}", e); + } + } + + // Wait a moment before unwrapping + println!("\n⏳ Waiting 3 seconds before unwrapping..."); + tokio::time::sleep(tokio::time::Duration::from_secs(3)).await; + + // Example 2: Close WSOL account and unwrap all remaining balance + println!("\n🔒 Example 2: Closing WSOL account and unwrapping remaining balance"); + println!("Closing WSOL account and unwrapping all remaining balance to SOL..."); + + match solana_trade.close_wsol().await { + Ok(signature) => { + println!("✅ Successfully closed WSOL account and unwrapped remaining balance!"); + println!("Transaction signature: {}", signature); + println!("Explorer: https://solscan.io/tx/{}", signature); + } + Err(e) => { + println!("❌ Failed to close WSOL account: {}", e); + } + } + + println!("\n🎉 WSOL Wrapper example completed!"); + Ok(()) +} + +/// Create and initialize SolanaTrade client +async fn create_solana_trade_client() -> Result> { + println!("🚀 Initializing SolanaTrade client..."); + let payer = Keypair::from_base58_string("use_your_payer_keypair_here"); + let rpc_url = "https://api.mainnet-beta.solana.com".to_string(); + let trade_config = TradeConfig { + rpc_url, + commitment: CommitmentConfig::confirmed(), + priority_fee: PriorityFee::default(), + swqos_configs: vec![], + }; + let solana_trade = SolanaTrade::new(Arc::new(payer), trade_config).await; + println!("✅ SolanaTrade client initialized successfully!"); + Ok(solana_trade) +} diff --git a/src/common/fast_fn.rs b/src/common/fast_fn.rs index 819bc52..94ba969 100644 --- a/src/common/fast_fn.rs +++ b/src/common/fast_fn.rs @@ -25,21 +25,22 @@ pub enum InstructionCacheKey { owner: Pubkey, mint: Pubkey, token_program: Pubkey, + use_seed: bool, }, /// Close wSOL Account CloseWsolAccount { payer: Pubkey, wsol_token_account: Pubkey }, } /// Global instruction cache for storing common instructions -static INSTRUCTION_CACHE: Lazy>> = +static INSTRUCTION_CACHE: Lazy>>> = Lazy::new(|| { RwLock::new(CLruCache::new(NonZeroUsize::new(MAX_INSTRUCTION_CACHE_SIZE).unwrap())) }); /// Get cached instruction, compute and cache if not exists -pub fn get_cached_instruction(cache_key: InstructionCacheKey, compute_fn: F) -> Instruction +pub fn get_cached_instructions(cache_key: InstructionCacheKey, compute_fn: F) -> Vec where - F: FnOnce() -> Instruction, + F: FnOnce() -> Vec, { // Try to get from cache (using read lock) { @@ -63,41 +64,75 @@ where // --------------------- Associated Token Account --------------------- +pub fn create_associated_token_account_idempotent_fast_use_seed( + payer: &Pubkey, + owner: &Pubkey, + mint: &Pubkey, + token_program: &Pubkey, + use_seed: bool, +) -> Vec { + _create_associated_token_account_idempotent_fast(payer, owner, mint, token_program, use_seed) +} + pub fn create_associated_token_account_idempotent_fast( payer: &Pubkey, owner: &Pubkey, mint: &Pubkey, token_program: &Pubkey, -) -> Instruction { +) -> Vec { + _create_associated_token_account_idempotent_fast(payer, owner, mint, token_program, false) +} + +pub fn _create_associated_token_account_idempotent_fast( + payer: &Pubkey, + owner: &Pubkey, + mint: &Pubkey, + token_program: &Pubkey, + use_seed: bool, +) -> Vec { // Create cache key let cache_key = InstructionCacheKey::CreateAssociatedTokenAccount { payer: *payer, owner: *owner, mint: *mint, token_program: *token_program, + use_seed, }; - // Use cache to get instruction - get_cached_instruction(cache_key, || { - // Get Associated Token Address using cache - let associated_token_address = - get_associated_token_address_with_program_id_fast(owner, mint, token_program); - - // Create Associated Token Account instruction - // Reference implementation of spl_associated_token_account::instruction::create_associated_token_account - Instruction { - program_id: ASSOCIATED_TOKEN_PROGRAM_ID, - accounts: vec![ - AccountMeta::new(*payer, true), // Payer (signer, writable) - AccountMeta::new(associated_token_address, false), // ATA address (writable, non-signer) - AccountMeta::new_readonly(*owner, false), // Token account owner (readonly, non-signer) - AccountMeta::new_readonly(*mint, false), // Token mint address (readonly, non-signer) - crate::constants::SYSTEM_PROGRAM_META, - AccountMeta::new_readonly(*token_program, false), // Token program (readonly, non-signer) - ], - data: vec![1], - } - }) + // Only use seed if the mint address is not wSOL or SOL + // token 2022 测试不成功(TODO) + if use_seed + && !mint.eq(&crate::constants::WSOL_TOKEN_ACCOUNT) + && !mint.eq(&crate::constants::SOL_TOKEN_ACCOUNT) + && token_program.eq(&spl_token::ID) + { + // Use cache to get instruction + get_cached_instructions(cache_key, || { + super::seed::create_associated_token_account_use_seed(payer, owner, mint, token_program) + .unwrap() + }) + } else { + // Use cache to get instruction + get_cached_instructions(cache_key, || { + // Get Associated Token Address using cache + let associated_token_address = + get_associated_token_address_with_program_id_fast(owner, mint, token_program); + // Create Associated Token Account instruction + // Reference implementation of spl_associated_token_account::instruction::create_associated_token_account + vec![Instruction { + program_id: ASSOCIATED_TOKEN_PROGRAM_ID, + accounts: vec![ + AccountMeta::new(*payer, true), // Payer (signer, writable) + AccountMeta::new(associated_token_address, false), // ATA address (writable, non-signer) + AccountMeta::new_readonly(*owner, false), // Token account owner (readonly, non-signer) + AccountMeta::new_readonly(*mint, false), // Token mint address (readonly, non-signer) + crate::constants::SYSTEM_PROGRAM_META, + AccountMeta::new_readonly(*token_program, false), // Token program (readonly, non-signer) + ], + data: vec![1], + }] + }) + } } // --------------------- PDA --------------------- @@ -150,22 +185,52 @@ struct AtaCacheKey { wallet_address: Pubkey, token_mint_address: Pubkey, token_program_id: Pubkey, + use_seed: bool, } /// Global ATA cache for storing Associated Token Address computation results static ATA_CACHE: Lazy>> = Lazy::new(|| RwLock::new(CLruCache::new(NonZeroUsize::new(MAX_ATA_CACHE_SIZE).unwrap()))); +pub fn get_associated_token_address_with_program_id_fast_use_seed( + wallet_address: &Pubkey, + token_mint_address: &Pubkey, + token_program_id: &Pubkey, + use_seed: bool, +) -> Pubkey { + _get_associated_token_address_with_program_id_fast( + wallet_address, + token_mint_address, + token_program_id, + use_seed, + ) +} + /// Get cached Associated Token Address, compute and cache if not exists pub fn get_associated_token_address_with_program_id_fast( wallet_address: &Pubkey, token_mint_address: &Pubkey, token_program_id: &Pubkey, +) -> Pubkey { + _get_associated_token_address_with_program_id_fast( + wallet_address, + token_mint_address, + token_program_id, + false, + ) +} + +fn _get_associated_token_address_with_program_id_fast( + wallet_address: &Pubkey, + token_mint_address: &Pubkey, + token_program_id: &Pubkey, + use_seed: bool, ) -> Pubkey { let cache_key = AtaCacheKey { wallet_address: *wallet_address, token_mint_address: *token_mint_address, token_program_id: *token_program_id, + use_seed, }; // Try to get from cache (using read lock) @@ -177,11 +242,26 @@ pub fn get_associated_token_address_with_program_id_fast( } // Cache miss, compute new ATA - let ata = get_associated_token_address_with_program_id( - wallet_address, - token_mint_address, - token_program_id, - ); + // Only use seed if the token mint address is not wSOL or SOL + // token 2022 测试不成功(TODO) + let ata = if use_seed + && !token_mint_address.eq(&crate::constants::WSOL_TOKEN_ACCOUNT) + && !token_mint_address.eq(&crate::constants::SOL_TOKEN_ACCOUNT) + && token_program_id.eq(&spl_token::ID) + { + super::seed::get_associated_token_address_with_program_id_use_seed( + wallet_address, + token_mint_address, + token_program_id, + ) + .unwrap() + } else { + get_associated_token_address_with_program_id( + wallet_address, + token_mint_address, + token_program_id, + ) + }; // Store computation result in cache (using write lock) { @@ -206,20 +286,20 @@ pub fn fast_init(payer: &Pubkey) { &crate::constants::TOKEN_PROGRAM, ); // Get Close wSOL Account instruction - get_cached_instruction( + get_cached_instructions( crate::common::fast_fn::InstructionCacheKey::CloseWsolAccount { payer: *payer, wsol_token_account, }, || { - spl_token::instruction::close_account( + vec![spl_token::instruction::close_account( &crate::constants::TOKEN_PROGRAM, &wsol_token_account, &payer, &payer, &[], ) - .unwrap() + .unwrap()] }, ); } diff --git a/src/common/mod.rs b/src/common/mod.rs index 7541932..abf78c9 100755 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -3,6 +3,7 @@ pub mod bonding_curve; pub mod fast_fn; pub mod global; pub mod nonce_cache; +pub mod seed; pub mod subscription_handle; pub mod types; diff --git a/src/common/seed.rs b/src/common/seed.rs new file mode 100644 index 0000000..be37b64 --- /dev/null +++ b/src/common/seed.rs @@ -0,0 +1,114 @@ +use crate::common::SolanaRpcClient; +use anyhow::anyhow; +use fnv::FnvHasher; +use solana_sdk::{instruction::Instruction, program_pack::Pack, pubkey::Pubkey}; +use solana_system_interface::instruction::create_account_with_seed; +use std::hash::Hasher; +use std::sync::Arc; +use tokio::time::{sleep, Duration}; + +// Global rent values for token accounts +pub static mut SPL_TOKEN_RENT: Option = None; +pub static mut SPL_TOKEN_2022_RENT: Option = None; + +pub async fn update_rents(client: &SolanaRpcClient) -> Result<(), anyhow::Error> { + let rent = fetch_rent_for_token_account(client, false).await?; + unsafe { + SPL_TOKEN_RENT = Some(rent); + } + let rent = fetch_rent_for_token_account(client, true).await?; + unsafe { + SPL_TOKEN_2022_RENT = Some(rent); + } + Ok(()) +} + +pub fn start_rent_updater(client: Arc) { + tokio::spawn(async move { + loop { + if let Err(_e) = update_rents(&client).await {} + sleep(Duration::from_secs(60 * 60)).await; + } + }); +} + +async fn fetch_rent_for_token_account( + client: &SolanaRpcClient, + is_2022_token: bool, +) -> Result { + Ok(client + .get_minimum_balance_for_rent_exemption(if is_2022_token { + spl_token_2022::state::Account::LEN as usize + } else { + spl_token::state::Account::LEN as usize + }) + .await?) +} + +pub fn create_associated_token_account_use_seed( + payer: &Pubkey, + owner: &Pubkey, + mint: &Pubkey, + token_program: &Pubkey, +) -> Result, anyhow::Error> { + let is_2022_token = token_program == &spl_token_2022::id(); + let rent = + if is_2022_token { unsafe { SPL_TOKEN_2022_RENT } } else { unsafe { SPL_TOKEN_RENT } }; + if rent.is_none() { + return Err(anyhow!("Rent is required when using seed")); + } + let mut buf = [0u8; 8]; + let mut hasher = FnvHasher::default(); + hasher.write(mint.as_ref()); + let hash = hasher.finish(); + let v = (hash & 0xFFFF_FFFF) as u32; + for i in 0..8 { + let nibble = ((v >> (28 - i * 4)) & 0xF) as u8; + buf[i] = match nibble { + 0..=9 => b'0' + nibble, + _ => b'a' + (nibble - 10), + }; + } + let seed = unsafe { std::str::from_utf8_unchecked(&buf) }; + let ata_like = Pubkey::create_with_seed(payer, seed, token_program)?; + + let len = if is_2022_token { + spl_token_2022::state::Account::LEN as u64 + } else { + spl_token::state::Account::LEN as u64 + }; + let create_acc = + create_account_with_seed(payer, &ata_like, owner, seed, rent.unwrap(), len, token_program); + + let init_acc = if is_2022_token { + spl_token_2022::instruction::initialize_account3(&token_program, &ata_like, mint, owner)? + } else { + spl_token::instruction::initialize_account3(&token_program, &ata_like, mint, owner)? + }; + + Ok(vec![create_acc, init_acc]) +} + +pub fn get_associated_token_address_with_program_id_use_seed( + wallet_address: &Pubkey, + token_mint_address: &Pubkey, + token_program_id: &Pubkey, +) -> Result { + let mut buf = [0u8; 8]; + let mut hasher = FnvHasher::default(); + hasher.write(token_mint_address.as_ref()); + let hash = hasher.finish(); + let v = (hash & 0xFFFF_FFFF) as u32; + for i in 0..8 { + let nibble = ((v >> (28 - i * 4)) & 0xF) as u8; + buf[i] = match nibble { + 0..=9 => b'0' + nibble, + _ => b'a' + (nibble - 10), + }; + } + let is_2022_token = token_program_id == &spl_token_2022::id(); + let seed = unsafe { std::str::from_utf8_unchecked(&buf) }; + let token_program = if is_2022_token { &spl_token_2022::id() } else { &spl_token::id() }; + let ata_like = Pubkey::create_with_seed(wallet_address, seed, token_program)?; + Ok(ata_like) +} diff --git a/src/common/types.rs b/src/common/types.rs index 9cefc3e..3cc7714 100755 --- a/src/common/types.rs +++ b/src/common/types.rs @@ -9,7 +9,7 @@ use crate::{ }; use serde::Deserialize; use solana_client::rpc_client::RpcClient; -use solana_sdk::{commitment_config::CommitmentConfig, pubkey::Pubkey, signature::Keypair}; +use solana_sdk::{commitment_config::CommitmentConfig, signature::Keypair}; #[derive(Debug, Clone)] pub struct TradeConfig { @@ -17,7 +17,6 @@ pub struct TradeConfig { pub swqos_configs: Vec, pub priority_fee: PriorityFee, pub commitment: CommitmentConfig, - pub lookup_table_key: Option, } impl TradeConfig { @@ -26,9 +25,8 @@ impl TradeConfig { swqos_configs: Vec, priority_fee: PriorityFee, commitment: CommitmentConfig, - lookup_table_key: Option, ) -> Self { - Self { rpc_url, swqos_configs, priority_fee, commitment, lookup_table_key } + Self { rpc_url, swqos_configs, priority_fee, commitment } } } diff --git a/src/constants/accounts.rs b/src/constants/accounts.rs index 26752b6..640f2d9 100644 --- a/src/constants/accounts.rs +++ b/src/constants/accounts.rs @@ -24,6 +24,8 @@ pub const TOKEN_PROGRAM_2022_META: solana_sdk::instruction::AccountMeta = is_writable: false, }; +pub const SOL_TOKEN_ACCOUNT: Pubkey = pubkey!("So11111111111111111111111111111111111111112"); + pub const WSOL_TOKEN_ACCOUNT: Pubkey = pubkey!("So11111111111111111111111111111111111111112"); pub const WSOL_TOKEN_ACCOUNT_META: solana_sdk::instruction::AccountMeta = solana_sdk::instruction::AccountMeta { diff --git a/src/instruction/bonk.rs b/src/instruction/bonk.rs index 1038e9e..96128cd 100755 --- a/src/instruction/bonk.rs +++ b/src/instruction/bonk.rs @@ -94,12 +94,14 @@ impl InstructionBuilder for BonkInstructionBuilder { .extend(crate::trading::common::handle_wsol(¶ms.payer.pubkey(), amount_in)); } - instructions.push(crate::common::fast_fn::create_associated_token_account_idempotent_fast( - ¶ms.payer.pubkey(), - ¶ms.payer.pubkey(), - ¶ms.mint, - &protocol_params.mint_token_program, - )); + instructions.extend( + crate::common::fast_fn::create_associated_token_account_idempotent_fast( + ¶ms.payer.pubkey(), + ¶ms.payer.pubkey(), + ¶ms.mint, + &protocol_params.mint_token_program, + ), + ); let mut data = [0u8; 32]; data[..8].copy_from_slice(&BUY_EXECT_IN_DISCRIMINATOR); @@ -131,7 +133,7 @@ impl InstructionBuilder for BonkInstructionBuilder { instructions.push(Instruction::new_with_bytes(accounts::BONK, &data, accounts.to_vec())); if protocol_params.auto_handle_wsol { - instructions.push(crate::trading::common::close_wsol(¶ms.payer.pubkey())); + instructions.extend(crate::trading::common::close_wsol(¶ms.payer.pubkey())); } Ok(instructions) @@ -213,7 +215,7 @@ impl InstructionBuilder for BonkInstructionBuilder { // ======================================== let mut instructions = Vec::with_capacity(3); - instructions.push(crate::trading::common::create_wsol_ata(¶ms.payer.pubkey())); + instructions.extend(crate::trading::common::create_wsol_ata(¶ms.payer.pubkey())); let mut data = [0u8; 32]; data[..8].copy_from_slice(&SELL_EXECT_IN_DISCRIMINATOR); @@ -245,7 +247,7 @@ impl InstructionBuilder for BonkInstructionBuilder { instructions.push(Instruction::new_with_bytes(accounts::BONK, &data, accounts.to_vec())); if protocol_params.auto_handle_wsol { - instructions.push(crate::trading::common::close_wsol(¶ms.payer.pubkey())); + instructions.extend(crate::trading::common::close_wsol(¶ms.payer.pubkey())); } Ok(instructions) diff --git a/src/instruction/pumpfun.rs b/src/instruction/pumpfun.rs index 95d3554..7c70575 100755 --- a/src/instruction/pumpfun.rs +++ b/src/instruction/pumpfun.rs @@ -76,13 +76,16 @@ impl InstructionBuilder for PumpFunInstructionBuilder { protocol_params.associated_bonding_curve }; - let user_token_account = crate::common::fast_fn::get_associated_token_address_with_program_id_fast( - ¶ms.payer.pubkey(), - ¶ms.mint, - &crate::constants::TOKEN_PROGRAM, - ); + let user_token_account = + crate::common::fast_fn::get_associated_token_address_with_program_id_fast_use_seed( + ¶ms.payer.pubkey(), + ¶ms.mint, + &crate::constants::TOKEN_PROGRAM, + params.open_seed_optimize, + ); - let user_volume_accumulator = get_user_volume_accumulator_pda(¶ms.payer.pubkey()).unwrap(); + let user_volume_accumulator = + get_user_volume_accumulator_pda(¶ms.payer.pubkey()).unwrap(); // ======================================== // Build instructions @@ -90,12 +93,15 @@ impl InstructionBuilder for PumpFunInstructionBuilder { let mut instructions = Vec::with_capacity(2); // Create associated token account - instructions.push(crate::common::fast_fn::create_associated_token_account_idempotent_fast( - ¶ms.payer.pubkey(), - ¶ms.payer.pubkey(), - ¶ms.mint, - &crate::constants::TOKEN_PROGRAM, - )); + instructions.extend( + crate::common::fast_fn::create_associated_token_account_idempotent_fast_use_seed( + ¶ms.payer.pubkey(), + ¶ms.payer.pubkey(), + ¶ms.mint, + &crate::constants::TOKEN_PROGRAM, + params.open_seed_optimize, + ), + ); let mut buy_data = [0u8; 24]; buy_data[..8].copy_from_slice(&[102, 6, 61, 18, 1, 218, 235, 234]); // Method ID @@ -185,11 +191,13 @@ impl InstructionBuilder for PumpFunInstructionBuilder { protocol_params.associated_bonding_curve }; - let user_token_account = crate::common::fast_fn::get_associated_token_address_with_program_id_fast( - ¶ms.payer.pubkey(), - ¶ms.mint, - &crate::constants::TOKEN_PROGRAM, - ); + let user_token_account = + crate::common::fast_fn::get_associated_token_address_with_program_id_fast_use_seed( + ¶ms.payer.pubkey(), + ¶ms.mint, + &crate::constants::TOKEN_PROGRAM, + params.open_seed_optimize, + ); // ======================================== // Build instructions diff --git a/src/instruction/pumpswap.rs b/src/instruction/pumpswap.rs index 380a5cc..1654972 100755 --- a/src/instruction/pumpswap.rs +++ b/src/instruction/pumpswap.rs @@ -4,9 +4,12 @@ use crate::{ accounts, fee_recipient_ata, get_user_volume_accumulator_pda, BUY_DISCRIMINATOR, SELL_DISCRIMINATOR, }, - trading::core::{ - params::{BuyParams, PumpSwapParams, SellParams}, - traits::InstructionBuilder, + trading::{ + common::wsol_manager, + core::{ + params::{BuyParams, PumpSwapParams, SellParams}, + traits::InstructionBuilder, + }, }, utils::calc::pumpswap::{buy_quote_input_internal, sell_base_input_internal}, }; @@ -95,16 +98,18 @@ impl InstructionBuilder for PumpSwapInstructionBuilder { } let user_base_token_account = - crate::common::fast_fn::get_associated_token_address_with_program_id_fast( + crate::common::fast_fn::get_associated_token_address_with_program_id_fast_use_seed( ¶ms.payer.pubkey(), &base_mint, &base_token_program, + params.open_seed_optimize, ); let user_quote_token_account = - crate::common::fast_fn::get_associated_token_address_with_program_id_fast( + crate::common::fast_fn::get_associated_token_address_with_program_id_fast_use_seed( ¶ms.payer.pubkey(), "e_mint, "e_token_program, + params.open_seed_optimize, ); let fee_recipient_ata = fee_recipient_ata(accounts::FEE_RECIPIENT, quote_mint); @@ -118,12 +123,15 @@ impl InstructionBuilder for PumpSwapInstructionBuilder { .extend(crate::trading::common::handle_wsol(¶ms.payer.pubkey(), sol_amount)); } - instructions.push(crate::common::fast_fn::create_associated_token_account_idempotent_fast( - ¶ms.payer.pubkey(), - ¶ms.payer.pubkey(), - if quote_mint_is_wsol { &base_mint } else { "e_mint }, - if quote_mint_is_wsol { &base_token_program } else { "e_token_program }, - )); + instructions.extend( + crate::common::fast_fn::create_associated_token_account_idempotent_fast_use_seed( + ¶ms.payer.pubkey(), + ¶ms.payer.pubkey(), + if quote_mint_is_wsol { &base_mint } else { "e_mint }, + if quote_mint_is_wsol { &base_token_program } else { "e_token_program }, + params.open_seed_optimize, + ), + ); // Create buy instruction let mut accounts = Vec::with_capacity(23); @@ -181,7 +189,7 @@ impl InstructionBuilder for PumpSwapInstructionBuilder { }); if auto_handle_wsol { // Close wSOL ATA account, reclaim rent - instructions.push(crate::trading::common::close_wsol(¶ms.payer.pubkey())); + instructions.extend(crate::trading::common::close_wsol(¶ms.payer.pubkey())); } Ok(instructions) } @@ -259,16 +267,18 @@ impl InstructionBuilder for PumpSwapInstructionBuilder { let fee_recipient_ata = fee_recipient_ata(accounts::FEE_RECIPIENT, quote_mint); let user_base_token_account = - crate::common::fast_fn::get_associated_token_address_with_program_id_fast( + crate::common::fast_fn::get_associated_token_address_with_program_id_fast_use_seed( ¶ms.payer.pubkey(), &base_mint, &base_token_program, + params.open_seed_optimize, ); let user_quote_token_account = - crate::common::fast_fn::get_associated_token_address_with_program_id_fast( + crate::common::fast_fn::get_associated_token_address_with_program_id_fast_use_seed( ¶ms.payer.pubkey(), "e_mint, "e_token_program, + params.open_seed_optimize, ); // ======================================== @@ -276,12 +286,9 @@ impl InstructionBuilder for PumpSwapInstructionBuilder { // ======================================== let mut instructions = Vec::with_capacity(3); - instructions.push(crate::common::fast_fn::create_associated_token_account_idempotent_fast( - ¶ms.payer.pubkey(), - ¶ms.payer.pubkey(), - &crate::constants::WSOL_TOKEN_ACCOUNT, - &crate::constants::TOKEN_PROGRAM, - )); + if auto_handle_wsol { + instructions.extend(wsol_manager::create_wsol_ata(¶ms.payer.pubkey())); + } // Create sell instruction let mut accounts = Vec::with_capacity(23); @@ -340,7 +347,7 @@ impl InstructionBuilder for PumpSwapInstructionBuilder { }); if auto_handle_wsol { - instructions.push(crate::trading::common::close_wsol(¶ms.payer.pubkey())); + instructions.extend(crate::trading::common::close_wsol(¶ms.payer.pubkey())); } Ok(instructions) } diff --git a/src/instruction/raydium_amm_v4.rs b/src/instruction/raydium_amm_v4.rs index c09b20e..6e8524b 100755 --- a/src/instruction/raydium_amm_v4.rs +++ b/src/instruction/raydium_amm_v4.rs @@ -68,12 +68,14 @@ impl InstructionBuilder for RaydiumAmmV4InstructionBuilder { .extend(crate::trading::common::handle_wsol(¶ms.payer.pubkey(), amount_in)); } - instructions.push(crate::common::fast_fn::create_associated_token_account_idempotent_fast( - ¶ms.payer.pubkey(), - ¶ms.payer.pubkey(), - ¶ms.mint, - &crate::constants::TOKEN_PROGRAM, - )); + instructions.extend( + crate::common::fast_fn::create_associated_token_account_idempotent_fast( + ¶ms.payer.pubkey(), + ¶ms.payer.pubkey(), + ¶ms.mint, + &crate::constants::TOKEN_PROGRAM, + ), + ); // Create buy instruction let accounts: [AccountMeta; 17] = [ @@ -109,7 +111,7 @@ impl InstructionBuilder for RaydiumAmmV4InstructionBuilder { if protocol_params.auto_handle_wsol { // Close wSOL ATA account, reclaim rent - instructions.push(crate::trading::common::close_wsol(¶ms.payer.pubkey())); + instructions.extend(crate::trading::common::close_wsol(¶ms.payer.pubkey())); } Ok(instructions) @@ -160,12 +162,14 @@ impl InstructionBuilder for RaydiumAmmV4InstructionBuilder { // ======================================== let mut instructions = Vec::with_capacity(3); - instructions.push(crate::common::fast_fn::create_associated_token_account_idempotent_fast( - ¶ms.payer.pubkey(), - ¶ms.payer.pubkey(), - &crate::constants::WSOL_TOKEN_ACCOUNT, - &crate::constants::TOKEN_PROGRAM, - )); + instructions.extend( + crate::common::fast_fn::create_associated_token_account_idempotent_fast( + ¶ms.payer.pubkey(), + ¶ms.payer.pubkey(), + &crate::constants::WSOL_TOKEN_ACCOUNT, + &crate::constants::TOKEN_PROGRAM, + ), + ); // Create buy instruction let accounts: [AccountMeta; 17] = [ @@ -200,7 +204,7 @@ impl InstructionBuilder for RaydiumAmmV4InstructionBuilder { )); if protocol_params.auto_handle_wsol { - instructions.push(crate::trading::common::close_wsol(¶ms.payer.pubkey())); + instructions.extend(crate::trading::common::close_wsol(¶ms.payer.pubkey())); } Ok(instructions) diff --git a/src/instruction/raydium_cpmm.rs b/src/instruction/raydium_cpmm.rs index 067b7e2..43ce65f 100755 --- a/src/instruction/raydium_cpmm.rs +++ b/src/instruction/raydium_cpmm.rs @@ -103,12 +103,14 @@ impl InstructionBuilder for RaydiumCpmmInstructionBuilder { .extend(crate::trading::common::handle_wsol(¶ms.payer.pubkey(), amount_in)); } - instructions.push(crate::common::fast_fn::create_associated_token_account_idempotent_fast( - ¶ms.payer.pubkey(), - ¶ms.payer.pubkey(), - ¶ms.mint, - &mint_token_program, - )); + instructions.extend( + crate::common::fast_fn::create_associated_token_account_idempotent_fast( + ¶ms.payer.pubkey(), + ¶ms.payer.pubkey(), + ¶ms.mint, + &mint_token_program, + ), + ); // Create buy instruction let accounts: [AccountMeta; 13] = [ @@ -140,7 +142,7 @@ impl InstructionBuilder for RaydiumCpmmInstructionBuilder { if protocol_params.auto_handle_wsol { // Close wSOL ATA account, reclaim rent - instructions.push(crate::trading::common::close_wsol(¶ms.payer.pubkey())); + instructions.extend(crate::trading::common::close_wsol(¶ms.payer.pubkey())); } Ok(instructions) @@ -221,12 +223,14 @@ impl InstructionBuilder for RaydiumCpmmInstructionBuilder { // ======================================== let mut instructions = Vec::with_capacity(3); - instructions.push(crate::common::fast_fn::create_associated_token_account_idempotent_fast( - ¶ms.payer.pubkey(), - ¶ms.payer.pubkey(), - &crate::constants::WSOL_TOKEN_ACCOUNT, - &crate::constants::TOKEN_PROGRAM, - )); + instructions.extend( + crate::common::fast_fn::create_associated_token_account_idempotent_fast( + ¶ms.payer.pubkey(), + ¶ms.payer.pubkey(), + &crate::constants::WSOL_TOKEN_ACCOUNT, + &crate::constants::TOKEN_PROGRAM, + ), + ); // Create sell instruction let accounts: [AccountMeta; 13] = [ @@ -258,7 +262,7 @@ impl InstructionBuilder for RaydiumCpmmInstructionBuilder { if protocol_params.auto_handle_wsol { // Close wSOL ATA account, reclaim rent - instructions.push(crate::trading::common::close_wsol(¶ms.payer.pubkey())); + instructions.extend(crate::trading::common::close_wsol(¶ms.payer.pubkey())); } Ok(instructions) diff --git a/src/lib.rs b/src/lib.rs index d082b73..aae5c57 100755 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,10 +22,10 @@ use crate::trading::MiddlewareManager; use crate::trading::SellParams; use crate::trading::TradeFactory; use common::{PriorityFee, SolanaRpcClient, TradeConfig}; +use parking_lot::Mutex; use rustls::crypto::{ring::default_provider, CryptoProvider}; use solana_sdk::hash::Hash; use solana_sdk::{pubkey::Pubkey, signature::Keypair}; -use parking_lot::Mutex; use std::sync::Arc; use swqos::SwqosClient; @@ -34,8 +34,7 @@ pub struct SolanaTrade { pub rpc: Arc, pub rpc_client: Vec>, pub swqos_clients: Vec>, - pub priority_fee: PriorityFee, - pub trade_config: TradeConfig, + pub priority_fee: Arc, pub middleware_manager: Option>, } @@ -49,7 +48,6 @@ impl Clone for SolanaTrade { rpc_client: self.rpc_client.clone(), swqos_clients: self.swqos_clients.clone(), priority_fee: self.priority_fee.clone(), - trade_config: self.trade_config.clone(), middleware_manager: self.middleware_manager.clone(), } } @@ -68,7 +66,7 @@ impl SolanaTrade { let rpc_url = trade_config.rpc_url.clone(); let swqos_configs = trade_config.swqos_configs.clone(); - let priority_fee = trade_config.priority_fee.clone(); + let priority_fee = Arc::new(trade_config.priority_fee.clone()); let commitment = trade_config.commitment.clone(); let mut swqos_clients: Vec> = vec![]; @@ -79,6 +77,8 @@ impl SolanaTrade { } let rpc = Arc::new(SolanaRpcClient::new_with_commitment(rpc_url.clone(), commitment)); + common::seed::update_rents(&rpc).await.unwrap(); + common::seed::start_rent_updater(rpc.clone()); let rpc_client = SwqosConfig::get_swqos_client( rpc_url.clone(), @@ -92,7 +92,6 @@ impl SolanaTrade { rpc_client: vec![rpc_client], swqos_clients, priority_fee, - trade_config: trade_config.clone(), middleware_manager: None, }; @@ -157,6 +156,7 @@ impl SolanaTrade { extension_params: Box, lookup_table_key: Option, wait_transaction_confirmed: bool, + open_seed_optimize: bool, ) -> Result<(), anyhow::Error> { if slippage_basis_points.is_none() { println!( @@ -167,23 +167,24 @@ impl SolanaTrade { let executor = TradeFactory::create_executor(dex_type.clone()); let protocol_params = extension_params; - let final_lookup_table_key = lookup_table_key.or(self.trade_config.lookup_table_key); - let mut buy_params = BuyParams { rpc: Some(self.rpc.clone()), payer: self.payer.clone(), mint: mint, sol_amount: sol_amount, slippage_basis_points: slippage_basis_points, - priority_fee: self.trade_config.priority_fee.clone(), - lookup_table_key: final_lookup_table_key, + priority_fee: self.priority_fee.clone(), + lookup_table_key, recent_blockhash, - data_size_limit: 0, + data_size_limit: 256 * 1024, wait_transaction_confirmed: wait_transaction_confirmed, protocol_params: protocol_params.clone(), + open_seed_optimize, + swqos_clients: self.swqos_clients.clone(), + middleware_manager: self.middleware_manager.clone(), }; if custom_priority_fee.is_some() { - buy_params.priority_fee = custom_priority_fee.unwrap(); + buy_params.priority_fee = Arc::new(custom_priority_fee.unwrap()); } // Validate protocol params @@ -205,9 +206,7 @@ impl SolanaTrade { return Err(anyhow::anyhow!("Invalid protocol params for Trade")); } - executor - .buy_with_tip(buy_params, self.swqos_clients.clone(), self.middleware_manager.clone()) - .await + executor.buy_with_tip(buy_params).await } /// Execute a sell order for a specified token @@ -249,6 +248,7 @@ impl SolanaTrade { extension_params: Box, lookup_table_key: Option, wait_transaction_confirmed: bool, + open_seed_optimize: bool, ) -> Result<(), anyhow::Error> { if slippage_basis_points.is_none() { println!( @@ -259,23 +259,28 @@ impl SolanaTrade { let executor = TradeFactory::create_executor(dex_type.clone()); let protocol_params = extension_params; - let final_lookup_table_key = lookup_table_key.or(self.trade_config.lookup_table_key); - let mut sell_params = SellParams { rpc: Some(self.rpc.clone()), payer: self.payer.clone(), mint: mint, token_amount: Some(token_amount), slippage_basis_points: slippage_basis_points, - priority_fee: self.trade_config.priority_fee.clone(), - lookup_table_key: final_lookup_table_key, + priority_fee: self.priority_fee.clone(), + lookup_table_key, recent_blockhash, wait_transaction_confirmed: wait_transaction_confirmed, protocol_params: protocol_params.clone(), with_tip: with_tip, + open_seed_optimize, + swqos_clients: if !with_tip { + self.rpc_client.clone() + } else { + self.swqos_clients.clone() + }, + middleware_manager: self.middleware_manager.clone(), }; if custom_priority_fee.is_some() { - sell_params.priority_fee = custom_priority_fee.unwrap(); + sell_params.priority_fee = Arc::new(custom_priority_fee.unwrap()); } // Validate protocol params @@ -301,7 +306,7 @@ impl SolanaTrade { if !with_tip { self.rpc_client.clone() } else { self.swqos_clients.clone() }; // Execute sell based on tip preference - executor.sell_with_tip(sell_params, _swqos_clients, self.middleware_manager.clone()).await + executor.sell_with_tip(sell_params).await } /// Execute a sell order for a percentage of the specified token amount @@ -349,6 +354,7 @@ impl SolanaTrade { extension_params: Box, lookup_table_key: Option, wait_transaction_confirmed: bool, + open_seed_optimize: bool, ) -> Result<(), anyhow::Error> { if percent == 0 || percent > 100 { return Err(anyhow::anyhow!("Percentage must be between 1 and 100")); @@ -365,7 +371,52 @@ impl SolanaTrade { extension_params, lookup_table_key, wait_transaction_confirmed, + open_seed_optimize, ) .await } + + /// Wraps SOL into wSOL (Wrapped SOL) + /// + /// This function creates a wSOL associated token account (if it doesn't exist), + /// transfers the specified amount of SOL to that account, and then syncs the native + /// token balance to make SOL usable as an SPL token. + /// + /// # Arguments + /// - `amount`: The amount of SOL to wrap (in lamports) + /// + /// # Returns + /// - `Ok(String)`: Transaction signature + /// - `Err(anyhow::Error)`: If the transaction fails + pub async fn wrap_sol_to_wsol(&self, amount: u64) -> Result { + use crate::trading::common::wsol_manager::handle_wsol; + use solana_sdk::transaction::Transaction; + let recent_blockhash = self.rpc.get_latest_blockhash().await?; + let instructions = handle_wsol(&self.payer.pubkey(), amount); + let mut transaction = + Transaction::new_with_payer(&instructions, Some(&self.payer.pubkey())); + transaction.sign(&[&*self.payer], recent_blockhash); + let signature = self.rpc.send_and_confirm_transaction(&transaction).await?; + Ok(signature.to_string()) + } + /// Closes the wSOL account and unwraps SOL back to native SOL + /// + /// This function closes the wSOL associated token account, which automatically + /// transfers any remaining wSOL balance back to the account owner as native SOL. + /// This is useful for cleaning up wSOL accounts and recovering wrapped SOL. + /// + /// # Returns + /// - `Ok(String)`: Transaction signature + /// - `Err(anyhow::Error)`: If the transaction fails + pub async fn close_wsol(&self) -> Result { + use crate::trading::common::wsol_manager::close_wsol; + use solana_sdk::transaction::Transaction; + let recent_blockhash = self.rpc.get_latest_blockhash().await?; + let instructions = close_wsol(&self.payer.pubkey()); + let mut transaction = + Transaction::new_with_payer(&instructions, Some(&self.payer.pubkey())); + transaction.sign(&[&*self.payer], recent_blockhash); + let signature = self.rpc.send_and_confirm_transaction(&transaction).await?; + Ok(signature.to_string()) + } } diff --git a/src/main.rs b/src/main.rs deleted file mode 100755 index fb31e9e..0000000 --- a/src/main.rs +++ /dev/null @@ -1,618 +0,0 @@ -use std::{str::FromStr, sync::Arc}; - -use sol_trade_sdk::{ - common::{AnyResult, PriorityFee, TradeConfig}, - swqos::{SwqosConfig, SwqosRegion}, - trading::{ - core::params::{BonkParams, PumpFunParams, PumpSwapParams, RaydiumCpmmParams}, - factory::DexType, - middleware::builtin::LoggingMiddleware, - MiddlewareManager, - }, - SolanaTrade, -}; -use sol_trade_sdk::{ - solana_streamer_sdk::{ - match_event, - streaming::{ - event_parser::{ - protocols::{ - bonk::{BonkPoolCreateEvent, BonkTradeEvent}, - pumpfun::{PumpFunCreateTokenEvent, PumpFunTradeEvent}, - pumpswap::{ - PumpSwapBuyEvent, PumpSwapCreatePoolEvent, PumpSwapDepositEvent, - PumpSwapSellEvent, PumpSwapWithdrawEvent, - }, - raydium_cpmm::RaydiumCpmmSwapEvent, - }, - Protocol, UnifiedEvent, - }, - ShredStreamGrpc, YellowstoneGrpc, - }, - }, - trading::core::params::RaydiumAmmV4Params, -}; -use solana_sdk::{commitment_config::CommitmentConfig, pubkey::Pubkey, signature::Keypair}; -use solana_streamer_sdk::streaming::{ - event_parser::protocols::{ - bonk::parser::BONK_PROGRAM_ID, pumpfun::parser::PUMPFUN_PROGRAM_ID, - pumpswap::parser::PUMPSWAP_PROGRAM_ID, raydium_amm_v4::parser::RAYDIUM_AMM_V4_PROGRAM_ID, - raydium_clmm::parser::RAYDIUM_CLMM_PROGRAM_ID, - raydium_cpmm::parser::RAYDIUM_CPMM_PROGRAM_ID, - }, - yellowstone_grpc::{AccountFilter, TransactionFilter}, -}; - -#[tokio::main] -async fn main() -> Result<(), Box> { - test_create_solana_trade_client().await?; - test_middleware().await?; - test_pumpswap().await?; - test_bonk().await?; - test_raydium_cpmm().await?; - test_raydium_amm_v4().await?; - test_grpc().await?; - test_shreds().await?; - Ok(()) -} - -/// Create SolanaTrade client -/// Initializes a new SolanaTrade client with configuration -async fn test_create_solana_trade_client() -> AnyResult { - println!("Creating SolanaTrade client..."); - - let payer = Keypair::new(); - let rpc_url = "https://mainnet.helius-rpc.com/?api-key=xxxxxx".to_string(); - - let swqos_configs = create_swqos_configs(&rpc_url); - let trade_config = create_trade_config(rpc_url, swqos_configs); - - let solana_trade_client = SolanaTrade::new(Arc::new(payer), trade_config).await; - println!("SolanaTrade client created successfully!"); - - Ok(solana_trade_client) -} - -fn create_swqos_configs(rpc_url: &str) -> Vec { - vec![ - SwqosConfig::Jito("your api_token".to_string(), SwqosRegion::Frankfurt, None), - SwqosConfig::NextBlock("your api_token".to_string(), SwqosRegion::Frankfurt, None), - SwqosConfig::Bloxroute("your api_token".to_string(), SwqosRegion::Frankfurt, None), - SwqosConfig::ZeroSlot("your api_token".to_string(), SwqosRegion::Frankfurt, None), - SwqosConfig::Temporal("your api_token".to_string(), SwqosRegion::Frankfurt, None), - SwqosConfig::Node1("your api_token".to_string(), SwqosRegion::Frankfurt, None), - SwqosConfig::FlashBlock("your api_token".to_string(), SwqosRegion::Frankfurt, None), - SwqosConfig::BlockRazor("your api_token".to_string(), SwqosRegion::Frankfurt, None), - SwqosConfig::Astralane("your api_token".to_string(), SwqosRegion::Frankfurt, None), - SwqosConfig::Default(rpc_url.to_string()), - ] -} - -fn create_trade_config(rpc_url: String, swqos_configs: Vec) -> TradeConfig { - TradeConfig { - rpc_url, - commitment: CommitmentConfig::confirmed(), - priority_fee: PriorityFee::default(), - swqos_configs, - lookup_table_key: None, - } -} -async fn test_middleware() -> AnyResult<()> { - let mut client = test_create_solana_trade_client().await?; - // SDK example middleware that prints instruction information - // You can reference LoggingMiddleware to implement the InstructionMiddleware trait for your own middleware - let middleware_manager = MiddlewareManager::new().add_middleware(Box::new(LoggingMiddleware)); - client = client.with_middleware_manager(middleware_manager); - let mint_pubkey = Pubkey::from_str("xxxxx")?; - let buy_sol_cost = 100_000; - let slippage_basis_points = Some(100); - let recent_blockhash = client.rpc.get_latest_blockhash().await?; - let pool_address = Pubkey::from_str("xxxx")?; - // Buy tokens - println!("Buying tokens from PumpSwap..."); - client - .buy( - DexType::PumpSwap, - mint_pubkey, - buy_sol_cost, - slippage_basis_points, - recent_blockhash, - None, - // Through RPC call, adds latency. Can optimize by using from_buy_trade or manually initializing PumpSwapParams - Box::new(PumpSwapParams::from_pool_address_by_rpc(&client.rpc, &pool_address).await?), - None, - true, - ) - .await?; - Ok(()) -} - -async fn test_pumpfun_copy_trade_with_grpc(trade_info: PumpFunTradeEvent) -> AnyResult<()> { - println!("Testing PumpFun trading..."); - - let client = test_create_solana_trade_client().await?; - let mint_pubkey = Pubkey::from_str("xxxxxx")?; - let buy_sol_cost = 100_000; - let slippage_basis_points = Some(100); - let recent_blockhash = client.rpc.get_latest_blockhash().await?; - - // Buy tokens - println!("Buying tokens from PumpFun..."); - client - .buy( - DexType::PumpFun, - mint_pubkey, - buy_sol_cost, - slippage_basis_points, - recent_blockhash, - None, - Box::new(PumpFunParams::from_trade(&trade_info, None)), - None, - true, - ) - .await?; - - // Sell tokens - println!("Selling tokens from PumpFun..."); - let amount_token = 0; - client - .sell( - DexType::PumpFun, - mint_pubkey, - amount_token, - slippage_basis_points, - recent_blockhash, - None, - false, - Box::new(PumpFunParams::from_trade(&trade_info, None)), - None, - true, - ) - .await?; - - Ok(()) -} - -async fn test_pumpfun_sniper_trade_with_shreds(trade_info: PumpFunTradeEvent) -> AnyResult<()> { - println!("Testing PumpFun trading..."); - - if !trade_info.is_dev_create_token_trade { - return Ok(()); - } - - let client = test_create_solana_trade_client().await?; - let mint_pubkey = trade_info.mint; - let slippage_basis_points = Some(100); - let recent_blockhash = client.rpc.get_latest_blockhash().await?; - - // Buy tokens - println!("Buying tokens from PumpFun..."); - let buy_sol_amount = 100_000; - client - .buy( - DexType::PumpFun, - mint_pubkey, - buy_sol_amount, - slippage_basis_points, - recent_blockhash, - None, - Box::new(PumpFunParams::from_trade(&trade_info, None)), - None, - true, - ) - .await?; - - // Sell tokens - println!("Selling tokens from PumpFun..."); - let amount_token = 0; - client - .sell( - DexType::PumpFun, - mint_pubkey, - amount_token, - slippage_basis_points, - recent_blockhash, - None, - false, - Box::new(PumpFunParams::from_trade(&trade_info, None)), - None, - true, - ) - .await?; - - Ok(()) -} - -async fn test_pumpswap() -> AnyResult<()> { - println!("Testing PumpSwap trading..."); - - let client = test_create_solana_trade_client().await?; - let mint_pubkey = Pubkey::from_str("2zMMhcVQEXDtdE6vsFS7S7D5oUodfJHE8vd1gnBouauv")?; - let buy_sol_cost = 100_000; - let slippage_basis_points = Some(100); - let recent_blockhash = client.rpc.get_latest_blockhash().await?; - let pool_address = Pubkey::from_str("xxxxxxx")?; - - // Buy tokens - println!("Buying tokens from PumpSwap..."); - client - .buy( - DexType::PumpSwap, - mint_pubkey, - buy_sol_cost, - slippage_basis_points, - recent_blockhash, - None, - // Through RPC call, adds latency. Can optimize by using from_buy_trade or manually initializing PumpSwapParams - Box::new(PumpSwapParams::from_pool_address_by_rpc(&client.rpc, &pool_address).await?), - None, - true, - ) - .await?; - - // Sell tokens - println!("Selling tokens from PumpSwap..."); - let amount_token = 0; - client - .sell( - DexType::PumpSwap, - mint_pubkey, - amount_token, - slippage_basis_points, - recent_blockhash, - None, - false, - // Through RPC call, adds latency. Can optimize by using from_sell_trade or manually initializing PumpSwapParams - Box::new(PumpSwapParams::from_pool_address_by_rpc(&client.rpc, &pool_address).await?), - None, - true, - ) - .await?; - - Ok(()) -} - -async fn test_bonk_copy_trade_with_grpc(trade_info: BonkTradeEvent) -> AnyResult<()> { - println!("Testing Bonk trading..."); - - let client = test_create_solana_trade_client().await?; - let mint_pubkey = Pubkey::from_str("xxxxxxx")?; - let buy_sol_cost = 100_000; - let slippage_basis_points = Some(100); - let recent_blockhash = client.rpc.get_latest_blockhash().await?; - - // Buy tokens - println!("Buying tokens from letsbonk.fun..."); - client - .buy( - DexType::Bonk, - mint_pubkey, - buy_sol_cost, - slippage_basis_points, - recent_blockhash, - None, - Box::new(BonkParams::from_trade(trade_info.clone())), - None, - true, - ) - .await?; - - // Sell tokens - println!("Selling tokens from letsbonk.fun..."); - let amount_token = 0; - client - .sell( - DexType::Bonk, - mint_pubkey, - amount_token, - slippage_basis_points, - recent_blockhash, - None, - false, - Box::new(BonkParams::from_trade(trade_info)), - None, - true, - ) - .await?; - - Ok(()) -} - -async fn test_bonk_sniper_trade_with_shreds(trade_info: BonkTradeEvent) -> AnyResult<()> { - println!("Testing Bonk trading..."); - - if !trade_info.is_dev_create_token_trade { - return Ok(()); - } - - let client = test_create_solana_trade_client().await?; - let mint_pubkey = Pubkey::from_str("xxxxxxx")?; - let buy_sol_cost = 100_000; - let slippage_basis_points = Some(100); - let recent_blockhash = client.rpc.get_latest_blockhash().await?; - - // Buy tokens - println!("Buying tokens from letsbonk.fun..."); - client - .buy( - DexType::Bonk, - mint_pubkey, - buy_sol_cost, - slippage_basis_points, - recent_blockhash, - None, - Box::new(BonkParams::from_dev_trade(trade_info.clone())), - None, - true, - ) - .await?; - - // Sell tokens - println!("Selling tokens from letsbonk.fun..."); - let amount_token = 0; - client - .sell( - DexType::Bonk, - mint_pubkey, - amount_token, - slippage_basis_points, - recent_blockhash, - None, - false, - Box::new(BonkParams::from_dev_trade(trade_info)), - None, - true, - ) - .await?; - - Ok(()) -} - -async fn test_bonk() -> Result<(), Box> { - println!("Testing Bonk trading..."); - - let client = test_create_solana_trade_client().await?; - let mint_pubkey = Pubkey::from_str("xxxxxxx")?; - let buy_sol_cost = 100_000; - let slippage_basis_points = Some(100); - let recent_blockhash = client.rpc.get_latest_blockhash().await?; - - // Buy tokens - println!("Buying tokens from letsbonk.fun..."); - client - .buy( - DexType::Bonk, - mint_pubkey, - buy_sol_cost, - slippage_basis_points, - recent_blockhash, - None, - // Through RPC call, adds latency. Can optimize by using from_trade or manually initializing BonkParams - Box::new(BonkParams::from_mint_by_rpc(&client.rpc, &mint_pubkey).await?), - None, - true, - ) - .await?; - - // Sell tokens - println!("Selling tokens from letsbonk.fun..."); - let amount_token = 0; - client - .sell( - DexType::Bonk, - mint_pubkey, - amount_token, - slippage_basis_points, - recent_blockhash, - None, - false, - // Through RPC call, adds latency. Can optimize by using from_trade or manually initializing BonkParams - Box::new(BonkParams::from_mint_by_rpc(&client.rpc, &mint_pubkey).await?), - None, - true, - ) - .await?; - - Ok(()) -} - -async fn test_raydium_cpmm() -> Result<(), Box> { - println!("Testing Raydium Cpmm trading..."); - - let client = test_create_solana_trade_client().await?; - let mint_pubkey = Pubkey::from_str("xxxxxxxx")?; - let buy_sol_cost = 100_000; - let slippage_basis_points = Some(100); - let recent_blockhash = client.rpc.get_latest_blockhash().await?; - let pool_address = Pubkey::from_str("xxxxxxx")?; - // Buy tokens - println!("Buying tokens from Raydium Cpmm..."); - client - .buy( - DexType::RaydiumCpmm, - mint_pubkey, - buy_sol_cost, - slippage_basis_points, - recent_blockhash, - None, - // Through RPC call, adds latency, or manually initialize RaydiumCpmmParams - Box::new( - RaydiumCpmmParams::from_pool_address_by_rpc(&client.rpc, &pool_address).await?, - ), - None, - true, - ) - .await?; - - // Sell tokens - println!("Selling tokens from Raydium Cpmm..."); - let amount_token = 0; - client - .sell( - DexType::RaydiumCpmm, - mint_pubkey, - amount_token, - slippage_basis_points, - recent_blockhash, - None, - false, - // Through RPC call, adds latency, or manually initialize RaydiumCpmmParams - Box::new( - RaydiumCpmmParams::from_pool_address_by_rpc(&client.rpc, &pool_address).await?, - ), - None, - true, - ) - .await?; - - Ok(()) -} - -async fn test_raydium_amm_v4() -> Result<(), Box> { - println!("Testing Raydium Amm V4 trading..."); - - let client = test_create_solana_trade_client().await?; - let mint_pubkey = Pubkey::from_str("xxxxxxx")?; - let buy_sol_cost = 100_000; - let slippage_basis_points = Some(100); - let recent_blockhash = client.rpc.get_latest_blockhash().await?; - let amm_address = Pubkey::from_str("xxxxxx")?; - // Buy tokens - println!("Buying tokens from Raydium Amm V4..."); - client - .buy( - DexType::RaydiumAmmV4, - mint_pubkey, - buy_sol_cost, - slippage_basis_points, - recent_blockhash, - None, - // Through RPC call, adds latency, or from_amm_info_and_reserves or manually initialize RaydiumAmmV4Params - Box::new(RaydiumAmmV4Params::from_amm_address_by_rpc(&client.rpc, amm_address).await?), - None, - true, - ) - .await?; - - // Sell tokens - println!("Selling tokens from Raydium Amm V4..."); - let amount_token = 0; - client - .sell( - DexType::RaydiumAmmV4, - mint_pubkey, - amount_token, - slippage_basis_points, - recent_blockhash, - None, - false, - // Through RPC call, adds latency, or from_amm_info_and_reserves or manually initialize RaydiumAmmV4Params - Box::new(RaydiumAmmV4Params::from_amm_address_by_rpc(&client.rpc, amm_address).await?), - None, - true, - ) - .await?; - - Ok(()) -} - -async fn test_grpc() -> Result<(), Box> { - println!("Subscribing to GRPC events..."); - - let grpc = YellowstoneGrpc::new( - "https://solana-yellowstone-grpc.publicnode.com:443".to_string(), - None, - )?; - - let callback = create_event_callback(); - let protocols = - vec![Protocol::PumpFun, Protocol::PumpSwap, Protocol::Bonk, Protocol::RaydiumCpmm]; - // Filter accounts - let account_include = vec![ - PUMPFUN_PROGRAM_ID.to_string(), // Listen to pumpfun program ID - PUMPSWAP_PROGRAM_ID.to_string(), // Listen to pumpswap program ID - BONK_PROGRAM_ID.to_string(), // Listen to bonk program ID - RAYDIUM_CPMM_PROGRAM_ID.to_string(), // Listen to raydium_cpmm program ID - RAYDIUM_CLMM_PROGRAM_ID.to_string(), // Listen to raydium_clmm program ID - RAYDIUM_AMM_V4_PROGRAM_ID.to_string(), // Listen to raydium_amm_v4 program ID - "xxxxxxxx".to_string(), // Listen to xxxxx account - ]; - let account_exclude = vec![]; - let account_required = vec![]; - - // Listen to transaction data - let transaction_filter = TransactionFilter { - account_include: account_include.clone(), - account_exclude, - account_required, - }; - - // Listen to account data belonging to owner programs -> account event monitoring - let account_filter = AccountFilter { account: vec![], owner: account_include.clone() }; - - println!("Starting to listen for events, press Ctrl+C to stop..."); - grpc.subscribe_events_immediate( - protocols, - None, - transaction_filter, - account_filter, - None, - None, - callback, - ) - .await?; - - Ok(()) -} - -async fn test_shreds() -> Result<(), Box> { - println!("Subscribing to ShredStream events..."); - - let shred_stream = ShredStreamGrpc::new("http://127.0.0.1:10800".to_string()).await?; - let callback = create_event_callback(); - let protocols = vec![Protocol::PumpFun, Protocol::PumpSwap, Protocol::Bonk]; - - println!("Starting to listen for events, press Ctrl+C to stop..."); - shred_stream.shredstream_subscribe(protocols, None, None, callback).await?; - - Ok(()) -} - -fn create_event_callback() -> impl Fn(Box) { - |event: Box| { - match_event!(event, { - BonkPoolCreateEvent => |e: BonkPoolCreateEvent| { - println!("BonkPoolCreateEvent: {:?}", e.base_mint_param.symbol); - }, - BonkTradeEvent => |e: BonkTradeEvent| { - println!("BonkTradeEvent: {:?}", e); - }, - PumpFunTradeEvent => |e: PumpFunTradeEvent| { - println!("PumpFunTradeEvent: {:?}", e); - }, - PumpFunCreateTokenEvent => |e: PumpFunCreateTokenEvent| { - println!("PumpFunCreateTokenEvent: {:?}", e); - }, - PumpSwapBuyEvent => |e: PumpSwapBuyEvent| { - println!("Buy event: {:?}", e); - }, - PumpSwapSellEvent => |e: PumpSwapSellEvent| { - println!("Sell event: {:?}", e); - }, - PumpSwapCreatePoolEvent => |e: PumpSwapCreatePoolEvent| { - println!("CreatePool event: {:?}", e); - }, - PumpSwapDepositEvent => |e: PumpSwapDepositEvent| { - println!("Deposit event: {:?}", e); - }, - PumpSwapWithdrawEvent => |e: PumpSwapWithdrawEvent| { - println!("Withdraw event: {:?}", e); - }, - RaydiumCpmmSwapEvent => |e: RaydiumCpmmSwapEvent| { - println!("RaydiumCpmmSwapEvent: {:?}", e); - }, - // ..... - // For more events and documentation, please refer to https://github.com/0xfnzero/solana-streamer - }); - } -} diff --git a/src/trading/common/wsol_manager.rs b/src/trading/common/wsol_manager.rs index 7e1adea..40f71ab 100644 --- a/src/trading/common/wsol_manager.rs +++ b/src/trading/common/wsol_manager.rs @@ -14,13 +14,13 @@ pub fn handle_wsol(payer: &Pubkey, amount_in: u64) -> SmallVec<[Instruction; 3]> ); let mut insts = SmallVec::<[Instruction; 3]>::new(); + insts.extend(create_associated_token_account_idempotent_fast( + &payer, + &payer, + &crate::constants::WSOL_TOKEN_ACCOUNT, + &crate::constants::TOKEN_PROGRAM, + )); insts.extend([ - create_associated_token_account_idempotent_fast( - &payer, - &payer, - &crate::constants::WSOL_TOKEN_ACCOUNT, - &crate::constants::TOKEN_PROGRAM, - ), transfer(&payer, &wsol_token_account, amount_in), spl_token::instruction::sync_native(&crate::constants::TOKEN_PROGRAM, &wsol_token_account) .unwrap(), @@ -29,33 +29,33 @@ pub fn handle_wsol(payer: &Pubkey, amount_in: u64) -> SmallVec<[Instruction; 3]> insts } -pub fn close_wsol(payer: &Pubkey) -> Instruction { +pub fn close_wsol(payer: &Pubkey) -> Vec { let wsol_token_account = crate::common::fast_fn::get_associated_token_address_with_program_id_fast( &payer, &crate::constants::WSOL_TOKEN_ACCOUNT, &crate::constants::TOKEN_PROGRAM, ); - crate::common::fast_fn::get_cached_instruction( + crate::common::fast_fn::get_cached_instructions( crate::common::fast_fn::InstructionCacheKey::CloseWsolAccount { payer: *payer, wsol_token_account, }, || { - close_account( + vec![close_account( &crate::constants::TOKEN_PROGRAM, &wsol_token_account, &payer, &payer, &[], ) - .unwrap() + .unwrap()] }, ) } #[inline] -pub fn create_wsol_ata(payer: &Pubkey) -> Instruction { +pub fn create_wsol_ata(payer: &Pubkey) -> Vec { create_associated_token_account_idempotent_fast( &payer, &payer, diff --git a/src/trading/core/executor.rs b/src/trading/core/executor.rs index bf759e0..77c4b7a 100755 --- a/src/trading/core/executor.rs +++ b/src/trading/core/executor.rs @@ -1,14 +1,12 @@ use anyhow::Result; use std::{sync::Arc, time::Instant}; +use crate::trading::core::parallel::{buy_parallel_execute, sell_parallel_execute}; + use super::{ - parallel::parallel_execute_with_tips, params::{BuyParams, SellParams}, traits::{InstructionBuilder, TradeExecutor}, }; -use crate::{swqos::SwqosClient, trading::middleware::MiddlewareManager}; - -const MAX_LOADED_ACCOUNTS_DATA_SIZE_LIMIT: u32 = 256 * 1024; /// Generic trade executor implementation pub struct GenericTradeExecutor { @@ -27,22 +25,12 @@ impl GenericTradeExecutor { #[async_trait::async_trait] impl TradeExecutor for GenericTradeExecutor { - async fn buy_with_tip( - &self, - params: BuyParams, - swqos_clients: Vec>, - middleware_manager: Option>, - ) -> Result<()> { - let mut data_size_limit = params.data_size_limit; - if data_size_limit == 0 { - data_size_limit = MAX_LOADED_ACCOUNTS_DATA_SIZE_LIMIT; - } - + async fn buy_with_tip(&self, params: BuyParams) -> Result<()> { let start = Instant::now(); // Build instructions directly from params to avoid unnecessary cloning let instructions = self.instruction_builder.build_buy_instructions(¶ms).await?; - let final_instructions = match &middleware_manager { + let final_instructions = match ¶ms.middleware_manager { Some(middleware_manager) => middleware_manager .apply_middlewares_process_protocol_instructions( instructions, @@ -55,36 +43,17 @@ impl TradeExecutor for GenericTradeExecutor { println!("Building buy transaction instructions time cost: {:?}", start.elapsed()); // Execute transactions in parallel - parallel_execute_with_tips( - swqos_clients, - params.payer, - final_instructions, - Arc::new(params.priority_fee), - params.lookup_table_key, - params.recent_blockhash, - data_size_limit, - middleware_manager, - self.protocol_name, - true, - params.wait_transaction_confirmed, - true, - ) - .await?; + buy_parallel_execute(params, final_instructions, self.protocol_name).await?; Ok(()) } - async fn sell_with_tip( - &self, - params: SellParams, - swqos_clients: Vec>, - middleware_manager: Option>, - ) -> Result<()> { + async fn sell_with_tip(&self, params: SellParams) -> Result<()> { let start = Instant::now(); // Build instructions directly from params to avoid unnecessary cloning let instructions = self.instruction_builder.build_sell_instructions(¶ms).await?; - let final_instructions = match &middleware_manager { + let final_instructions = match ¶ms.middleware_manager { Some(middleware_manager) => middleware_manager .apply_middlewares_process_protocol_instructions( instructions, @@ -97,21 +66,7 @@ impl TradeExecutor for GenericTradeExecutor { println!("Building sell transaction instructions time cost: {:?}", start.elapsed()); // Execute transactions in parallel - parallel_execute_with_tips( - swqos_clients, - params.payer, - final_instructions, - Arc::new(params.priority_fee), - params.lookup_table_key, - params.recent_blockhash, - 0, - middleware_manager, - self.protocol_name, - false, - params.wait_transaction_confirmed, - params.with_tip, - ) - .await?; + sell_parallel_execute(params, final_instructions, self.protocol_name).await?; Ok(()) } diff --git a/src/trading/core/parallel.rs b/src/trading/core/parallel.rs index fae7fd7..c9fa778 100755 --- a/src/trading/core/parallel.rs +++ b/src/trading/core/parallel.rs @@ -8,11 +8,55 @@ use tokio::task::JoinHandle; use crate::{ common::PriorityFee, swqos::{SwqosClient, SwqosType, TradeType}, - trading::{common::build_transaction, MiddlewareManager}, + trading::{common::build_transaction, BuyParams, MiddlewareManager, SellParams}, }; +pub async fn buy_parallel_execute( + params: BuyParams, + instructions: Vec, + protocol_name: &'static str, +) -> Result<()> { + parallel_execute( + params.swqos_clients, + params.payer, + instructions, + params.priority_fee, + params.lookup_table_key, + params.recent_blockhash, + params.data_size_limit, + params.middleware_manager, + protocol_name, + true, + params.wait_transaction_confirmed, + true, + ) + .await +} + +pub async fn sell_parallel_execute( + params: SellParams, + instructions: Vec, + protocol_name: &'static str, +) -> Result<()> { + parallel_execute( + params.swqos_clients, + params.payer, + instructions, + params.priority_fee, + params.lookup_table_key, + params.recent_blockhash, + 0, + params.middleware_manager, + protocol_name, + false, + params.wait_transaction_confirmed, + params.with_tip, + ) + .await +} + /// Generic function for parallel transaction execution -pub async fn parallel_execute_with_tips( +async fn parallel_execute( swqos_clients: Vec>, payer: Arc, instructions: Vec, diff --git a/src/trading/core/params.rs b/src/trading/core/params.rs index 45adbde..d19bb98 100755 --- a/src/trading/core/params.rs +++ b/src/trading/core/params.rs @@ -3,7 +3,9 @@ use crate::common::bonding_curve::BondingCurveAccount; use crate::common::{PriorityFee, SolanaRpcClient}; use crate::solana_streamer_sdk::streaming::event_parser::common::EventType; use crate::solana_streamer_sdk::streaming::event_parser::protocols::bonk::BonkTradeEvent; +use crate::swqos::SwqosClient; use crate::trading::common::get_multi_token_balances; +use crate::trading::MiddlewareManager; use solana_hash::Hash; use solana_sdk::{pubkey::Pubkey, signature::Keypair}; use solana_streamer_sdk::streaming::event_parser::protocols::pumpfun::PumpFunTradeEvent; @@ -21,12 +23,15 @@ pub struct BuyParams { pub mint: Pubkey, pub sol_amount: u64, pub slippage_basis_points: Option, - pub priority_fee: PriorityFee, + pub priority_fee: Arc, pub lookup_table_key: Option, pub recent_blockhash: Hash, pub data_size_limit: u32, pub wait_transaction_confirmed: bool, pub protocol_params: Box, + pub open_seed_optimize: bool, + pub swqos_clients: Vec>, + pub middleware_manager: Option>, } /// Sell parameters @@ -37,12 +42,15 @@ pub struct SellParams { pub mint: Pubkey, pub token_amount: Option, pub slippage_basis_points: Option, - pub priority_fee: PriorityFee, + pub priority_fee: Arc, pub lookup_table_key: Option, pub recent_blockhash: Hash, pub wait_transaction_confirmed: bool, pub with_tip: bool, pub protocol_params: Box, + pub open_seed_optimize: bool, + pub swqos_clients: Vec>, + pub middleware_manager: Option>, } /// PumpFun protocol specific parameters diff --git a/src/trading/core/traits.rs b/src/trading/core/traits.rs index 9801901..3abdec9 100755 --- a/src/trading/core/traits.rs +++ b/src/trading/core/traits.rs @@ -1,28 +1,14 @@ -use std::sync::Arc; - -use crate::{swqos::SwqosClient, trading::MiddlewareManager}; +use super::params::{BuyParams, SellParams}; use anyhow::Result; use solana_sdk::instruction::Instruction; -use super::params::{BuyParams, SellParams}; - /// 交易执行器trait - 定义了所有交易协议都需要实现的核心方法 #[async_trait::async_trait] pub trait TradeExecutor: Send + Sync { /// 使用MEV服务执行买入交易 - async fn buy_with_tip( - &self, - params: BuyParams, - swqos_clients: Vec>, - middleware_manager: Option>, - ) -> Result<()>; + async fn buy_with_tip(&self, params: BuyParams) -> Result<()>; /// 使用MEV服务执行卖出交易 - async fn sell_with_tip( - &self, - params: SellParams, - swqos_clients: Vec>, - middleware_manager: Option>, - ) -> Result<()>; + async fn sell_with_tip(&self, params: SellParams) -> Result<()>; /// 获取协议名称 fn protocol_name(&self) -> &'static str; }