diff --git a/Cargo.toml b/Cargo.toml index 1e743fe..776f174 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sol-trade-sdk" -version = "0.6.1" +version = "0.6.2" edition = "2021" authors = [ "William ", @@ -29,13 +29,15 @@ members = [ "examples/address_lookup", "examples/nonce_cache", "examples/pumpswap_direct_trading", + "examples/wsol_wrapper", + "examples/seed_trading", ] [lib] crate-type = ["cdylib", "rlib"] [dependencies] -solana-streamer-sdk = "0.4.1" +solana-streamer-sdk = "0.4.3" solana-sdk = "2.3.0" solana-client = "2.3.6" solana-program = "2.3.0" diff --git a/README.md b/README.md index 895e8a8..2bac0ae 100644 --- a/README.md +++ b/README.md @@ -33,29 +33,46 @@ Add the dependency to your `Cargo.toml`: ```toml # Add to your Cargo.toml -sol-trade-sdk = { path = "./sol-trade-sdk", version = "0.6.1" } +sol-trade-sdk = { path = "./sol-trade-sdk", version = "0.6.2" } ``` ### Use crates.io ```toml # Add to your Cargo.toml -sol-trade-sdk = "0.6.1" +sol-trade-sdk = "0.6.2" ``` ## Usage Examples ### Important Parameter Description +#### open_seed_optimize Parameter -#### auto_handle_wsol Parameter +`open_seed_optimize` is used to specify whether to use seed optimization to reduce transaction CU consumption. -In PumpSwap, Bonk, and Raydium CPMM trading, the `auto_handle_wsol` parameter is used to automatically handle wSOL (Wrapped SOL): +- **Purpose**: When `open_seed_optimize: true`, the SDK uses createAccountWithSeed optimization to create token ata accounts during transactions. +- **Note**: Transactions created with `open_seed_optimize` enabled must be sold through this SDK. Using official methods to sell may fail. +- **Note**: After enabling `open_seed_optimize`, you need to use the `get_associated_token_address_with_program_id_fast_use_seed` method to get the token ata address. -- **Mechanism**: - - When `auto_handle_wsol: true`, the SDK automatically handles the conversion between SOL and wSOL +#### create_wsol_ata and close_wsol_ata、 create_mint_ata Parameters + +In PumpSwap, Bonk, and Raydium trading, the `create_wsol_ata` and `close_wsol_ata`、 `create_mint_ata` parameters provide fine-grained control over wSOL (Wrapped SOL) account management: + +- **create_wsol_ata**: + - When `create_wsol_ata: true`, the SDK automatically creates and wraps SOL to wSOL before trading - When buying: automatically wraps SOL to wSOL for trading - - When selling: automatically unwraps the received wSOL to SOL - - Default value is `true` + +- **close_wsol_ata**: + - When `close_wsol_ata: true`, the SDK automatically closes the wSOL account and unwraps to SOL after trading + - When selling: automatically unwraps the received wSOL to SOL and reclaims rent + +- **create_mint_ata**: + - When `create_mint_ata: true`, the SDK automatically creates the token ata account before trading + +- **Benefits of Separate Parameters**: + - Allows independent control of wSOL account creation and closure + - Useful for batch operations where you want to create once and close after multiple transactions + - Provides flexibility for advanced trading strategies #### lookup_table_key Parameter @@ -106,6 +123,8 @@ 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) | +| Seed Trading | `seed_trading` | Seed trading example | `cargo run --package seed_trading` | [examples/seed_trading](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/seed_trading/src/main.rs) | ### SWQOS Service Configuration @@ -181,7 +200,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..5ec6c07 100755 --- a/README_CN.md +++ b/README_CN.md @@ -33,29 +33,47 @@ git clone https://github.com/0xfnzero/sol-trade-sdk ```toml # 添加到您的 Cargo.toml -sol-trade-sdk = { path = "./sol-trade-sdk", version = "0.6.1" } +sol-trade-sdk = { path = "./sol-trade-sdk", version = "0.6.2" } ``` ### 使用 crates.io ```toml # 添加到您的 Cargo.toml -sol-trade-sdk = "0.6.1" +sol-trade-sdk = "0.6.2" ``` ## 使用示例 ### 重要说明 -#### auto_handle_wsol 参数 +#### open_seed_optimize 参数 -在 PumpSwap、Bonk、Raydium CPMM 交易中,`auto_handle_wsol` 参数用于自动处理 wSOL(Wrapped SOL): +`open_seed_optimize` ,用于指定是否使用 seed 优化交易 CU 消耗。 -- **作用机制**: - - 当 `auto_handle_wsol: true` 时,SDK 会自动处理 SOL 与 wSOL 之间的转换 +- **用途**:当 `open_seed_optimize: true` 时,SDK 会在交易时使用 createAccountWithSeed 优化来创建代币 ata 账户。 +- **注意**:开启 `open_seed_optimize` 后创建的交易,需要通过该 SDK 卖出,使用官网提供的方法卖出可能会失败。 +- **注意**:开启 `open_seed_optimize` 后,获取代币 ata 地址需要通过 `get_associated_token_address_with_program_id_fast_use_seed` 方法获取。 + +#### create_wsol_ata 和 close_wsol_ata、 create_mint_ata 参数 + +在 PumpSwap、Bonk、Raydium 交易中,`create_wsol_ata` 和 `close_wsol_ata`、 `create_mint_ata` 参数提供对 wSOL(Wrapped SOL)账户管理的精细控制: + +- **create_wsol_ata**: + - 当 `create_wsol_ata: true` 时,SDK 会在交易前自动创建并将 SOL 包装为 wSOL - 买入时:自动将 SOL 包装为 wSOL 进行交易 - - 卖出时:自动将获得的 wSOL 解包装为 SOL - - 默认值为 `true` + +- **close_wsol_ata**: + - 当 `close_wsol_ata: true` 时,SDK 会在交易后自动关闭 wSOL 账户并解包装为 SOL + - 卖出时:自动将获得的 wSOL 解包装为 SOL 并回收租金 + +- **create_mint_ata**: + - 当 `create_mint_ata: true` 时,SDK 会在交易时创建代币ata账户 + +- **分离参数的优势**: + - 允许独立控制 wSOL 账户的创建和关闭 + - 适用于批量操作,可以创建一次,在多次交易后再关闭 + - 为高级交易策略提供灵活性 #### lookup_table_key 参数 @@ -106,6 +124,8 @@ 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) | +| Seed 优化 | `seed_trading` | Seed 优化交易示例 | `cargo run --package seed_trading` | [examples/seed_trading](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/seed_trading/src/main.rs) | ### SWQOS 服务配置说明 @@ -181,7 +201,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..cf23e81 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,12 @@ 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, + false, + true, + false, ) .await?; diff --git a/examples/bonk_copy_trading/src/main.rs b/examples/bonk_copy_trading/src/main.rs index 210b999..c887350 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,10 @@ async fn bonk_copy_trade_with_grpc(trade_info: BonkTradeEvent) -> AnyResult<()> Box::new(BonkParams::from_trade(trade_info.clone())), None, true, + true, + true, + true, + false, ) .await?; @@ -178,6 +181,9 @@ async fn bonk_copy_trade_with_grpc(trade_info: BonkTradeEvent) -> AnyResult<()> Box::new(BonkParams::from_trade(trade_info.clone())), None, true, + true, + true, + false, ) .await?; diff --git a/examples/bonk_sniper_trading/src/main.rs b/examples/bonk_sniper_trading/src/main.rs index d31328f..1d4cc42 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,10 @@ async fn bonk_sniper_trade_with_shreds(trade_info: BonkTradeEvent) -> AnyResult< Box::new(BonkParams::from_dev_trade(trade_info.clone())), None, true, + true, + true, + true, + false, ) .await?; @@ -152,6 +155,9 @@ async fn bonk_sniper_trade_with_shreds(trade_info: BonkTradeEvent) -> AnyResult< )), None, true, + true, + true, + false, ) .await?; diff --git a/examples/middleware_system/src/main.rs b/examples/middleware_system/src/main.rs index 3bc3914..424d5dc 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,10 @@ async fn test_middleware() -> AnyResult<()> { Box::new(PumpSwapParams::from_pool_address_by_rpc(&client.rpc, &pool_address).await?), None, true, + true, + true, + 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..f60dcd2 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,10 @@ async fn pumpfun_copy_trade_with_grpc(trade_info: PumpFunTradeEvent) -> AnyResul Box::new(PumpFunParams::from_trade(&trade_info, None)), None, true, + false, + false, + true, + false, ) .await?; diff --git a/examples/pumpfun_copy_trading/src/main.rs b/examples/pumpfun_copy_trading/src/main.rs index 60aed62..55c97b8 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,10 @@ async fn pumpfun_copy_trade_with_grpc(trade_info: PumpFunTradeEvent) -> AnyResul Box::new(PumpFunParams::from_trade(&trade_info, None)), None, true, + false, + false, + true, + false, ) .await?; @@ -172,6 +175,9 @@ async fn pumpfun_copy_trade_with_grpc(trade_info: PumpFunTradeEvent) -> AnyResul Box::new(PumpFunParams::from_trade(&trade_info, Some(true))), None, true, + false, + false, + false, ) .await?; diff --git a/examples/pumpfun_sniper_trading/src/main.rs b/examples/pumpfun_sniper_trading/src/main.rs index c64d9c8..4fb15df 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,10 @@ async fn pumpfun_sniper_trade_with_shreds(trade_info: PumpFunTradeEvent) -> AnyR Box::new(PumpFunParams::from_dev_trade(&trade_info, None)), None, true, + true, + true, + true, + false, ) .await?; @@ -140,6 +143,9 @@ async fn pumpfun_sniper_trade_with_shreds(trade_info: PumpFunTradeEvent) -> AnyR Box::new(PumpFunParams::immediate_sell(trade_info.creator_vault, true)), None, true, + true, + true, + false, ) .await?; diff --git a/examples/pumpswap_direct_trading/src/main.rs b/examples/pumpswap_direct_trading/src/main.rs index 98bea2b..2fd3141 100644 --- a/examples/pumpswap_direct_trading/src/main.rs +++ b/examples/pumpswap_direct_trading/src/main.rs @@ -33,6 +33,10 @@ async fn main() -> Result<(), Box> { Box::new(PumpSwapParams::from_pool_address_by_rpc(&client.rpc, &pool).await?), None, true, + true, + true, + true, + false, ) .await?; @@ -57,6 +61,9 @@ async fn main() -> Result<(), Box> { Box::new(PumpSwapParams::from_pool_address_by_rpc(&client.rpc, &pool).await?), None, true, + true, + true, + false, ) .await?; @@ -84,7 +91,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..491473e 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,10 @@ async fn pumpswap_trade_with_grpc(mint_pubkey: Pubkey, params: PumpSwapParams) - Box::new(params.clone()), None, true, + true, + true, + true, + false, ) .await?; @@ -216,6 +219,9 @@ async fn pumpswap_trade_with_grpc(mint_pubkey: Pubkey, params: PumpSwapParams) - Box::new(params.clone()), None, true, + true, + 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..71e38bc 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,10 @@ async fn raydium_amm_v4_copy_trade_with_grpc(trade_info: RaydiumAmmV4SwapEvent) Box::new(params), None, true, + true, + true, + true, + false, ) .await?; @@ -186,6 +189,9 @@ async fn raydium_amm_v4_copy_trade_with_grpc(trade_info: RaydiumAmmV4SwapEvent) Box::new(params), None, true, + true, + true, + false, ) .await?; diff --git a/examples/raydium_cpmm_trading/src/main.rs b/examples/raydium_cpmm_trading/src/main.rs index 9bd7440..b2b273d 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,10 @@ async fn raydium_cpmm_copy_trade_with_grpc(trade_info: RaydiumCpmmSwapEvent) -> Box::new(buy_params), None, true, + true, + true, + true, + false, ) .await?; @@ -192,6 +195,9 @@ async fn raydium_cpmm_copy_trade_with_grpc(trade_info: RaydiumCpmmSwapEvent) -> Box::new(sell_params), None, true, + true, + true, + false, ) .await?; diff --git a/examples/seed_trading/Cargo.toml b/examples/seed_trading/Cargo.toml new file mode 100644 index 0000000..1fe9a6e --- /dev/null +++ b/examples/seed_trading/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "seed_trading" +version = "0.1.0" +edition = "2021" + +[dependencies] +sol-trade-sdk = { path = "../.." } +solana-sdk = "2.3.0" +spl-associated-token-account = "7.0.0" +tokio = { version = "1", features = ["full"] } +spl-token= "8.0.0" +spl-token-2022 = { version = "8.0.0", features = ["no-entrypoint"] } \ No newline at end of file diff --git a/examples/seed_trading/src/main.rs b/examples/seed_trading/src/main.rs new file mode 100644 index 0000000..e8ac4b2 --- /dev/null +++ b/examples/seed_trading/src/main.rs @@ -0,0 +1,110 @@ +use sol_trade_sdk::{ + common::{ + fast_fn::get_associated_token_address_with_program_id_fast_use_seed, AnyResult, + PriorityFee, TradeConfig, + }, + swqos::SwqosConfig, + trading::{core::params::PumpSwapParams, factory::DexType}, + SolanaTrade, +}; +use solana_sdk::{commitment_config::CommitmentConfig, signature::Keypair}; +use solana_sdk::{pubkey::Pubkey, signer::Signer}; +use std::{str::FromStr, sync::Arc}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + println!("Testing PumpSwap trading..."); + + let client = create_solana_trade_client().await?; + let slippage_basis_points = Some(100); + let recent_blockhash = client.rpc.get_latest_blockhash().await?; + let pool = Pubkey::from_str("9qKxzRejsV6Bp2zkefXWCbGvg61c3hHei7ShXJ4FythA").unwrap(); + let mint_pubkey = Pubkey::from_str("2zMMhcVQEXDtdE6vsFS7S7D5oUodfJHE8vd1gnBouauv").unwrap(); + + // Buy tokens + println!("Buying tokens from PumpSwap..."); + let buy_sol_amount = 100_000; + client + .buy( + DexType::PumpSwap, + mint_pubkey, + buy_sol_amount, + slippage_basis_points, + recent_blockhash, + None, + Box::new(PumpSwapParams::from_pool_address_by_rpc(&client.rpc, &pool).await?), + None, + true, + true, + true, + true, + true, // ❗️❗️❗️❗️ open seed optimize + ) + .await?; + + tokio::time::sleep(std::time::Duration::from_secs(4)).await; + + // Sell tokens + println!("Selling tokens from PumpSwap..."); + + let rpc = client.rpc.clone(); + let payer = client.payer.pubkey(); + let program_id = spl_token::ID; + // ❗️❗️❗️❗️ Must use the 'use seed' method to get the ATA account, otherwise the transaction will fail + let account = get_associated_token_address_with_program_id_fast_use_seed( + &payer, + &mint_pubkey, + &program_id, + true, + ); + let balance = rpc.get_token_account_balance(&account).await?; + let amount_token = balance.amount.parse::().unwrap(); + client + .sell( + DexType::PumpSwap, + mint_pubkey, + amount_token, + slippage_basis_points, + recent_blockhash, + None, + false, + Box::new(PumpSwapParams::from_pool_address_by_rpc(&client.rpc, &pool).await?), + None, + true, + true, + true, + true, // ❗️❗️❗️❗️ open seed optimize + ) + .await?; + + tokio::signal::ctrl_c().await?; + Ok(()) +} + +/// Create SolanaTrade client +/// Initializes a new SolanaTrade client with configuration +async fn create_solana_trade_client() -> AnyResult { + println!("Creating SolanaTrade client..."); + + let payer = Keypair::from_base58_string("use_your_own_keypair"); + let rpc_url = "https://api.mainnet-beta.solana.com".to_string(); + + let swqos_configs = vec![SwqosConfig::Default(rpc_url.clone())]; + + let mut priority_fee = PriorityFee::default(); + priority_fee.buy_tip_fees = vec![0.001]; + // Configure according to your needs + priority_fee.rpc_unit_limit = 150000; + + let trade_config = TradeConfig { + rpc_url, + commitment: CommitmentConfig::confirmed(), + priority_fee: priority_fee, + swqos_configs, + }; + + let solana_trade_client = SolanaTrade::new(Arc::new(payer), trade_config).await; + println!("SolanaTrade client created successfully!"); + + Ok(solana_trade_client) +} 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..d0718c1 100755 --- a/src/instruction/bonk.rs +++ b/src/instruction/bonk.rs @@ -61,16 +61,18 @@ impl InstructionBuilder for BonkInstructionBuilder { ); 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(), ¶ms.mint, &protocol_params.mint_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(), &crate::constants::WSOL_TOKEN_ACCOUNT, &crate::constants::TOKEN_PROGRAM, + params.open_seed_optimize, ); let base_vault_account = if protocol_params.base_vault == Pubkey::default() { @@ -89,17 +91,22 @@ impl InstructionBuilder for BonkInstructionBuilder { // ======================================== let mut instructions = Vec::with_capacity(6); - if protocol_params.auto_handle_wsol { + if params.create_wsol_ata { instructions .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, - )); + if params.create_mint_ata { + instructions.extend( + crate::common::fast_fn::create_associated_token_account_idempotent_fast_use_seed( + ¶ms.payer.pubkey(), + ¶ms.payer.pubkey(), + ¶ms.mint, + &protocol_params.mint_token_program, + params.open_seed_optimize, + ), + ); + } let mut data = [0u8; 32]; data[..8].copy_from_slice(&BUY_EXECT_IN_DISCRIMINATOR); @@ -130,8 +137,8 @@ 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())); + if params.close_wsol_ata { + instructions.extend(crate::trading::common::close_wsol(¶ms.payer.pubkey())); } Ok(instructions) @@ -185,16 +192,18 @@ impl InstructionBuilder for BonkInstructionBuilder { ); 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(), ¶ms.mint, &protocol_params.mint_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(), &crate::constants::WSOL_TOKEN_ACCOUNT, &crate::constants::TOKEN_PROGRAM, + params.open_seed_optimize, ); let base_vault_account = if protocol_params.base_vault == Pubkey::default() { @@ -213,7 +222,9 @@ impl InstructionBuilder for BonkInstructionBuilder { // ======================================== let mut instructions = Vec::with_capacity(3); - instructions.push(crate::trading::common::create_wsol_ata(¶ms.payer.pubkey())); + if params.create_wsol_ata { + 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); @@ -244,8 +255,8 @@ 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())); + if params.close_wsol_ata { + 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..056a07c 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,17 @@ 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, - )); + if params.create_mint_ata { + 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 +193,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..7cd37c0 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}, }; @@ -43,7 +46,8 @@ impl InstructionBuilder for PumpSwapInstructionBuilder { let pool_quote_token_reserves = protocol_params.pool_quote_token_reserves; let params_coin_creator_vault_ata = protocol_params.coin_creator_vault_ata; let params_coin_creator_vault_authority = protocol_params.coin_creator_vault_authority; - let auto_handle_wsol = protocol_params.auto_handle_wsol; + let create_wsol_ata = params.create_wsol_ata; + let close_wsol_ata = params.close_wsol_ata; let base_token_program = protocol_params.base_token_program; let quote_token_program = protocol_params.quote_token_program; let pool_base_token_account = protocol_params.pool_base_token_account; @@ -95,16 +99,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); @@ -113,17 +119,22 @@ impl InstructionBuilder for PumpSwapInstructionBuilder { // ======================================== let mut instructions = Vec::with_capacity(6); - if auto_handle_wsol { + if create_wsol_ata { instructions .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 }, - )); + if params.create_mint_ata { + 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); @@ -179,9 +190,9 @@ impl InstructionBuilder for PumpSwapInstructionBuilder { accounts, data: data.to_vec(), }); - if auto_handle_wsol { + if close_wsol_ata { // 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) } @@ -205,7 +216,8 @@ impl InstructionBuilder for PumpSwapInstructionBuilder { let pool_quote_token_account = protocol_params.pool_quote_token_account; let params_coin_creator_vault_ata = protocol_params.coin_creator_vault_ata; let params_coin_creator_vault_authority = protocol_params.coin_creator_vault_authority; - let auto_handle_wsol = protocol_params.auto_handle_wsol; + let create_wsol_ata = params.create_wsol_ata; + let close_wsol_ata = params.close_wsol_ata; let base_token_program = protocol_params.base_token_program; let quote_token_program = protocol_params.quote_token_program; @@ -259,16 +271,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 +290,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 create_wsol_ata { + instructions.extend(wsol_manager::create_wsol_ata(¶ms.payer.pubkey())); + } // Create sell instruction let mut accounts = Vec::with_capacity(23); @@ -339,8 +350,8 @@ impl InstructionBuilder for PumpSwapInstructionBuilder { data: data.to_vec(), }); - if auto_handle_wsol { - instructions.push(crate::trading::common::close_wsol(¶ms.payer.pubkey())); + if close_wsol_ata { + 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..6bc8af2 100755 --- a/src/instruction/raydium_amm_v4.rs +++ b/src/instruction/raydium_amm_v4.rs @@ -46,16 +46,18 @@ impl InstructionBuilder for RaydiumAmmV4InstructionBuilder { let minimum_amount_out = swap_result.min_amount_out; let user_source_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(), &crate::constants::WSOL_TOKEN_ACCOUNT, &crate::constants::TOKEN_PROGRAM, + params.open_seed_optimize, ); let user_destination_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(), ¶ms.mint, &crate::constants::TOKEN_PROGRAM, + params.open_seed_optimize, ); // ======================================== @@ -63,17 +65,22 @@ impl InstructionBuilder for RaydiumAmmV4InstructionBuilder { // ======================================== let mut instructions = Vec::with_capacity(6); - if protocol_params.auto_handle_wsol { + if params.create_wsol_ata { instructions .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, - )); + if params.create_mint_ata { + 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, + ), + ); + } // Create buy instruction let accounts: [AccountMeta; 17] = [ @@ -107,9 +114,9 @@ impl InstructionBuilder for RaydiumAmmV4InstructionBuilder { accounts.to_vec(), )); - if protocol_params.auto_handle_wsol { + if params.close_wsol_ata { // 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) @@ -143,16 +150,18 @@ impl InstructionBuilder for RaydiumAmmV4InstructionBuilder { let minimum_amount_out = swap_result.min_amount_out; let user_source_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(), ¶ms.mint, &crate::constants::TOKEN_PROGRAM, + params.open_seed_optimize, ); let user_destination_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(), &crate::constants::WSOL_TOKEN_ACCOUNT, &crate::constants::TOKEN_PROGRAM, + params.open_seed_optimize, ); // ======================================== @@ -160,12 +169,9 @@ 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, - )); + if params.create_wsol_ata { + instructions.extend(crate::trading::common::create_wsol_ata(¶ms.payer.pubkey())); + } // Create buy instruction let accounts: [AccountMeta; 17] = [ @@ -199,8 +205,8 @@ impl InstructionBuilder for RaydiumAmmV4InstructionBuilder { accounts.to_vec(), )); - if protocol_params.auto_handle_wsol { - instructions.push(crate::trading::common::close_wsol(¶ms.payer.pubkey())); + if params.close_wsol_ata { + 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..0bee5e8 100755 --- a/src/instruction/raydium_cpmm.rs +++ b/src/instruction/raydium_cpmm.rs @@ -1,5 +1,5 @@ use crate::{ - common::fast_fn::get_associated_token_address_with_program_id_fast, + common::fast_fn::get_associated_token_address_with_program_id_fast_use_seed, constants::trade::trade::DEFAULT_SLIPPAGE, instruction::utils::raydium_cpmm::{ accounts, get_observation_state_pda, get_pool_pda, get_vault_account, @@ -38,7 +38,7 @@ impl InstructionBuilder for RaydiumCpmmInstructionBuilder { let pool_state = if protocol_params.pool_state == Pubkey::default() { get_pool_pda( - &accounts::AMM_CONFIG, + &protocol_params.amm_config, &protocol_params.base_mint, &protocol_params.quote_mint, ) @@ -67,15 +67,17 @@ impl InstructionBuilder for RaydiumCpmmInstructionBuilder { ); let minimum_amount_out = result.min_amount_out; - let wsol_token_account = get_associated_token_address_with_program_id_fast( + let wsol_token_account = get_associated_token_address_with_program_id_fast_use_seed( ¶ms.payer.pubkey(), &crate::constants::WSOL_TOKEN_ACCOUNT, &crate::constants::TOKEN_PROGRAM, + params.open_seed_optimize, ); - let mint_token_account = get_associated_token_address_with_program_id_fast( + let mint_token_account = get_associated_token_address_with_program_id_fast_use_seed( ¶ms.payer.pubkey(), ¶ms.mint, &mint_token_program, + params.open_seed_optimize, ); let wsol_vault_account = get_vault_account( @@ -98,23 +100,28 @@ impl InstructionBuilder for RaydiumCpmmInstructionBuilder { // ======================================== let mut instructions = Vec::with_capacity(6); - if protocol_params.auto_handle_wsol { + if params.create_wsol_ata { instructions .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, - )); + if params.create_mint_ata { + instructions.extend( + crate::common::fast_fn::create_associated_token_account_idempotent_fast_use_seed( + ¶ms.payer.pubkey(), + ¶ms.payer.pubkey(), + ¶ms.mint, + &mint_token_program, + params.open_seed_optimize, + ), + ); + } // Create buy instruction let accounts: [AccountMeta; 13] = [ AccountMeta::new(params.payer.pubkey(), true), // Payer (signer) accounts::AUTHORITY_META, // Authority (readonly) - accounts::AMM_CONFIG_META, // Amm Config (readonly) + AccountMeta::new(protocol_params.amm_config, false), // Amm Config (readonly) AccountMeta::new(pool_state, false), // Pool State AccountMeta::new(wsol_token_account, false), // Input Token Account AccountMeta::new(mint_token_account, false), // Output Token Account @@ -138,9 +145,9 @@ impl InstructionBuilder for RaydiumCpmmInstructionBuilder { accounts.to_vec(), )); - if protocol_params.auto_handle_wsol { + if params.close_wsol_ata { // 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) @@ -162,7 +169,7 @@ impl InstructionBuilder for RaydiumCpmmInstructionBuilder { let pool_state = if protocol_params.pool_state == Pubkey::default() { get_pool_pda( - &accounts::AMM_CONFIG, + &protocol_params.amm_config, &protocol_params.base_mint, &protocol_params.quote_mint, ) @@ -190,15 +197,17 @@ impl InstructionBuilder for RaydiumCpmmInstructionBuilder { ) .min_amount_out; - let wsol_token_account = get_associated_token_address_with_program_id_fast( + let wsol_token_account = get_associated_token_address_with_program_id_fast_use_seed( ¶ms.payer.pubkey(), &crate::constants::WSOL_TOKEN_ACCOUNT, &crate::constants::TOKEN_PROGRAM, + params.open_seed_optimize, ); - let mint_token_account = get_associated_token_address_with_program_id_fast( + let mint_token_account = get_associated_token_address_with_program_id_fast_use_seed( ¶ms.payer.pubkey(), ¶ms.mint, &mint_token_program, + params.open_seed_optimize, ); let wsol_vault_account = get_vault_account( @@ -221,18 +230,15 @@ 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, - )); + if params.create_wsol_ata { + instructions.extend(crate::trading::common::create_wsol_ata(¶ms.payer.pubkey())); + } // Create sell instruction let accounts: [AccountMeta; 13] = [ AccountMeta::new(params.payer.pubkey(), true), // Payer (signer) accounts::AUTHORITY_META, // Authority (readonly) - accounts::AMM_CONFIG_META, // Amm Config (readonly) + AccountMeta::new(protocol_params.amm_config, false), // Amm Config (readonly) AccountMeta::new(pool_state, false), // Pool State AccountMeta::new(mint_token_account, false), // Input Token Account AccountMeta::new(wsol_token_account, false), // Output Token Account @@ -256,9 +262,9 @@ impl InstructionBuilder for RaydiumCpmmInstructionBuilder { accounts.to_vec(), )); - if protocol_params.auto_handle_wsol { + if params.close_wsol_ata { // 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/instruction/utils/raydium_cpmm.rs b/src/instruction/utils/raydium_cpmm.rs index 0e2e658..05db8d4 100644 --- a/src/instruction/utils/raydium_cpmm.rs +++ b/src/instruction/utils/raydium_cpmm.rs @@ -16,7 +16,6 @@ pub mod seeds { pub mod accounts { use solana_sdk::{pubkey, pubkey::Pubkey}; pub const AUTHORITY: Pubkey = pubkey!("GpMZbSM2GgvTKHJirzeGfMFoaZ8UR2X7F4v8vHTvxFbL"); - pub const AMM_CONFIG: Pubkey = pubkey!("D4FPEruKEHrG5TenZ2mpDGEfu1iUvTiqBxvpU8HLBvC2"); pub const RAYDIUM_CPMM: Pubkey = pubkey!("CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C"); pub const FEE_RATE_DENOMINATOR_VALUE: u128 = 1_000_000; pub const TRADE_FEE_RATE: u64 = 2500; @@ -30,13 +29,6 @@ pub mod accounts { is_signer: false, is_writable: false, }; - - pub const AMM_CONFIG_META: solana_sdk::instruction::AccountMeta = - solana_sdk::instruction::AccountMeta { - pubkey: AMM_CONFIG, - is_signer: false, - is_writable: false, - }; } pub const SWAP_BASE_IN_DISCRIMINATOR: &[u8] = &[143, 190, 90, 218, 196, 30, 51, 222]; @@ -93,11 +85,15 @@ pub async fn get_pool_token_balances( let token1_balance = rpc.get_token_account_balance(&token1_vault).await?; // Parse balance string to u64 - let token0_amount = - token0_balance.amount.parse::().map_err(|e| anyhow!("Failed to parse token0 balance: {}", e))?; + let token0_amount = token0_balance + .amount + .parse::() + .map_err(|e| anyhow!("Failed to parse token0 balance: {}", e))?; - let token1_amount = - token1_balance.amount.parse::().map_err(|e| anyhow!("Failed to parse token1 balance: {}", e))?; + let token1_amount = token1_balance + .amount + .parse::() + .map_err(|e| anyhow!("Failed to parse token1 balance: {}", e))?; Ok((token0_amount, token1_amount)) } diff --git a/src/lib.rs b/src/lib.rs index d082b73..4a8a57b 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 solana_sdk::{pubkey::Pubkey, signature::Keypair, signature::Signature}; 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, }; @@ -134,6 +133,9 @@ impl SolanaTrade { /// * `extension_params` - Optional protocol-specific parameters (uses defaults if None) /// * `lookup_table_key` - Optional address lookup table key for transaction optimization /// * `wait_transaction_confirmed` - Whether to wait for the transaction to be confirmed + /// * `create_wsol_ata` - Whether to create wSOL ATA account + /// * `close_wsol_ata` - Whether to close wSOL ATA account + /// * `open_seed_optimize` - Whether to open seed optimize /// /// # Returns /// @@ -157,7 +159,11 @@ impl SolanaTrade { extension_params: Box, lookup_table_key: Option, wait_transaction_confirmed: bool, - ) -> Result<(), anyhow::Error> { + create_wsol_ata: bool, + close_wsol_ata: bool, + create_mint_ata: bool, + open_seed_optimize: bool, + ) -> Result { if slippage_basis_points.is_none() { println!( "slippage_basis_points is none, use default slippage basis points: {}", @@ -167,23 +173,27 @@ 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, + create_wsol_ata, + close_wsol_ata, + create_mint_ata, + 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 +215,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 @@ -224,6 +232,9 @@ impl SolanaTrade { /// * `extension_params` - Optional protocol-specific parameters (uses defaults if None) /// * `lookup_table_key` - Optional address lookup table key for transaction optimization /// * `wait_transaction_confirmed` - Whether to wait for the transaction to be confirmed + /// * `create_wsol_ata` - Whether to create wSOL ATA account + /// * `close_wsol_ata` - Whether to close wSOL ATA account + /// * `open_seed_optimize` - Whether to open seed optimize /// /// # Returns /// @@ -249,7 +260,10 @@ impl SolanaTrade { extension_params: Box, lookup_table_key: Option, wait_transaction_confirmed: bool, - ) -> Result<(), anyhow::Error> { + create_wsol_ata: bool, + close_wsol_ata: bool, + open_seed_optimize: bool, + ) -> Result { if slippage_basis_points.is_none() { println!( "slippage_basis_points is none, use default slippage basis points: {}", @@ -259,23 +273,30 @@ 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(), + create_wsol_ata, + close_wsol_ata, }; 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 @@ -297,11 +318,8 @@ impl SolanaTrade { return Err(anyhow::anyhow!("Invalid protocol params for Trade")); } - let _swqos_clients = - 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,7 +367,10 @@ impl SolanaTrade { extension_params: Box, lookup_table_key: Option, wait_transaction_confirmed: bool, - ) -> Result<(), anyhow::Error> { + create_wsol_ata: bool, + close_wsol_ata: bool, + open_seed_optimize: bool, + ) -> Result { if percent == 0 || percent > 100 { return Err(anyhow::anyhow!("Percentage must be between 1 and 100")); } @@ -365,7 +386,54 @@ impl SolanaTrade { extension_params, lookup_table_key, wait_transaction_confirmed, + create_wsol_ata, + close_wsol_ata, + 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..2a94407 100755 --- a/src/trading/core/executor.rs +++ b/src/trading/core/executor.rs @@ -1,14 +1,13 @@ use anyhow::Result; +use solana_sdk::signature::Signature; 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 +26,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 +44,15 @@ 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?; - - Ok(()) + buy_parallel_execute(params, final_instructions, self.protocol_name).await } - 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,23 +65,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?; - - Ok(()) + sell_parallel_execute(params, final_instructions, self.protocol_name).await } fn protocol_name(&self) -> &'static str { diff --git a/src/trading/core/parallel.rs b/src/trading/core/parallel.rs index fae7fd7..07b10b0 100755 --- a/src/trading/core/parallel.rs +++ b/src/trading/core/parallel.rs @@ -1,6 +1,8 @@ use anyhow::{anyhow, Result}; use solana_hash::Hash; -use solana_sdk::{instruction::Instruction, pubkey::Pubkey, signature::Keypair}; +use solana_sdk::{ + instruction::Instruction, pubkey::Pubkey, signature::Keypair, signature::Signature, +}; use std::{str::FromStr, sync::Arc, time::Instant}; use tokio::sync::mpsc; use tokio::task::JoinHandle; @@ -8,11 +10,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, @@ -25,9 +71,9 @@ pub async fn parallel_execute_with_tips( is_buy: bool, wait_transaction_confirmed: bool, with_tip: bool, -) -> Result<()> { +) -> Result { let cores = core_affinity::get_core_ids().unwrap(); - let mut handles: Vec>> = Vec::with_capacity(swqos_clients.len()); + let mut handles: Vec>> = Vec::with_capacity(swqos_clients.len()); if is_buy && (swqos_clients.len() > priority_fee.buy_tip_fees.len() || priority_fee.buy_tip_fees.is_empty()) @@ -103,7 +149,11 @@ pub async fn parallel_execute_with_tips( start.elapsed() ); - Ok::<(), anyhow::Error>(()) + transaction + .signatures + .first() + .ok_or_else(|| anyhow!("Transaction has no signatures")) + .cloned() }); handles.push(handle); @@ -125,13 +175,20 @@ pub async fn parallel_execute_with_tips( let mut errors = Vec::new(); if !wait_transaction_confirmed { - return Ok(()); + if let Some(result) = rx.recv().await { + match result { + Ok(Ok(sig)) => return Ok(sig), + Ok(Err(e)) => errors.push(format!("Task error: {}", e)), + Err(e) => errors.push(format!("Join error: {}", e)), + } + } + return Err(anyhow!("No transaction signature available")); } while let Some(result) = rx.recv().await { match result { - Ok(Ok(_)) => { - return Ok(()); + Ok(Ok(sig)) => { + return Ok(sig); } Ok(Err(e)) => errors.push(format!("Task error: {}", e)), Err(e) => errors.push(format!("Join error: {}", e)), diff --git a/src/trading/core/params.rs b/src/trading/core/params.rs index 45adbde..99a85e0 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,18 @@ 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>, + pub create_wsol_ata: bool, + pub close_wsol_ata: bool, + pub create_mint_ata: bool, } /// Sell parameters @@ -37,12 +45,17 @@ 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>, + pub create_wsol_ata: bool, + pub close_wsol_ata: bool, } /// PumpFun protocol specific parameters @@ -142,9 +155,6 @@ pub struct PumpSwapParams { pub base_token_program: Pubkey, /// Quote token program ID pub quote_token_program: Pubkey, - /// Automatically handle WSOL wrapping - /// When true, automatically handles wrapping and unwrapping operations between SOL and WSOL - pub auto_handle_wsol: bool, } impl PumpSwapParams { @@ -161,7 +171,6 @@ impl PumpSwapParams { coin_creator_vault_authority: event.coin_creator_vault_authority, base_token_program: event.base_token_program, quote_token_program: event.quote_token_program, - auto_handle_wsol: true, } } @@ -178,7 +187,6 @@ impl PumpSwapParams { coin_creator_vault_authority: event.coin_creator_vault_authority, base_token_program: event.base_token_program, quote_token_program: event.quote_token_program, - auto_handle_wsol: true, } } @@ -230,7 +238,6 @@ impl PumpSwapParams { } else { crate::constants::TOKEN_PROGRAM_2022 }, - auto_handle_wsol: true, }) } } @@ -262,7 +269,6 @@ pub struct BonkParams { pub platform_config: Pubkey, pub platform_associated_account: Pubkey, pub creator_associated_account: Pubkey, - pub auto_handle_wsol: bool, } impl BonkParams { @@ -273,7 +279,6 @@ impl BonkParams { creator_associated_account: Pubkey, ) -> Self { Self { - auto_handle_wsol: true, mint_token_program, platform_config, platform_associated_account, @@ -294,7 +299,6 @@ impl BonkParams { platform_config: trade_info.platform_config, platform_associated_account: trade_info.platform_associated_account, creator_associated_account: trade_info.creator_associated_account, - auto_handle_wsol: true, } } @@ -350,7 +354,6 @@ impl BonkParams { platform_config: trade_info.platform_config, platform_associated_account: trade_info.platform_associated_account, creator_associated_account: trade_info.creator_associated_account, - auto_handle_wsol: true, } } @@ -386,7 +389,6 @@ impl BonkParams { platform_config: pool_data.platform_config, platform_associated_account, creator_associated_account, - auto_handle_wsol: true, }) } } @@ -407,6 +409,8 @@ impl ProtocolParams for BonkParams { pub struct RaydiumCpmmParams { /// Pool address pub pool_state: Pubkey, + /// Amm config address + pub amm_config: Pubkey, /// Base token mint address pub base_mint: Pubkey, /// Quote token mint address @@ -425,8 +429,6 @@ pub struct RaydiumCpmmParams { pub quote_token_program: Pubkey, /// Observation state account pub observation_state: Pubkey, - /// Whether to automatically handle wSOL wrapping and unwrapping - pub auto_handle_wsol: bool, } impl RaydiumCpmmParams { @@ -437,6 +439,7 @@ impl RaydiumCpmmParams { ) -> Self { Self { pool_state: trade_info.pool_state, + amm_config: trade_info.amm_config, base_mint: trade_info.input_token_mint, quote_mint: trade_info.output_token_mint, base_reserve: base_reserve, @@ -446,7 +449,6 @@ impl RaydiumCpmmParams { base_token_program: trade_info.input_token_program, quote_token_program: trade_info.output_token_program, observation_state: trade_info.observation_state, - auto_handle_wsol: true, } } @@ -466,6 +468,7 @@ impl RaydiumCpmmParams { .await?; Ok(Self { pool_state: pool_address.clone(), + amm_config: pool.amm_config, base_mint: pool.token0_mint, quote_mint: pool.token1_mint, base_reserve: token0_balance, @@ -475,7 +478,6 @@ impl RaydiumCpmmParams { base_token_program: pool.token0_program, quote_token_program: pool.token1_program, observation_state: pool.observation_key, - auto_handle_wsol: true, }) } } @@ -508,8 +510,6 @@ pub struct RaydiumAmmV4Params { pub coin_reserve: u64, /// Current pc reserve amount in the pool pub pc_reserve: u64, - /// Whether to automatically handle wSOL wrapping and unwrapping - pub auto_handle_wsol: bool, } impl RaydiumAmmV4Params { @@ -527,7 +527,6 @@ impl RaydiumAmmV4Params { token_pc: amm_info.token_pc, coin_reserve, pc_reserve, - auto_handle_wsol: true, } } pub async fn from_amm_address_by_rpc( @@ -545,7 +544,6 @@ impl RaydiumAmmV4Params { token_pc: amm_info.token_pc, coin_reserve, pc_reserve, - auto_handle_wsol: true, }) } } diff --git a/src/trading/core/traits.rs b/src/trading/core/traits.rs index 9801901..273b2f2 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 anyhow::Result; -use solana_sdk::instruction::Instruction; - use super::params::{BuyParams, SellParams}; +use anyhow::Result; +use solana_sdk::{instruction::Instruction, signature::Signature}; /// 交易执行器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; }