diff --git a/Cargo.toml b/Cargo.toml index 0bfff3c..799605e 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sol-trade-sdk" -version = "0.6.15" +version = "1.0.1" edition = "2021" authors = [ "William ", @@ -32,6 +32,7 @@ members = [ "examples/wsol_wrapper", "examples/seed_trading", "examples/cli_trading", + "examples/gas_fee_strategy", ] [lib] @@ -97,4 +98,5 @@ fnv = "1.0.7" dashmap = "6.1.0" clru = "0.6" smallvec = "1.15.1" -parking_lot = "0.12" \ No newline at end of file +parking_lot = "0.12" +arc-swap = "1.7" \ No newline at end of file diff --git a/README.md b/README.md index 0b4aed1..bd81b28 100644 --- a/README.md +++ b/README.md @@ -44,15 +44,14 @@ - [✨ Features](#-features) - [📦 Installation](#-installation) - [🛠️ Usage Examples](#️-usage-examples) - - [📋 Important Parameter Description](#-important-parameter-description) + - [📋 Example Usage](#-example-usage) + - [⚡ Trading Parameters](#-trading-parameters) - [📊 Usage Examples Summary Table](#-usage-examples-summary-table) - [⚙️ SWQOS Service Configuration](#️-swqos-service-configuration) - [🔧 Middleware System](#-middleware-system) - - [⚡ Custom Priority Fee Configuration](#-custom-priority-fee-configuration) -- [🏪 Supported Trading Platforms](#-supported-trading-platforms) + - [🔍 Address Lookup Tables](#-address-lookup-tables) + - [🔍 Nonce Cache](#-nonce-cache) - [🛡️ MEV Protection Services](#️-mev-protection-services) -- [💰 Price Calculation Utilities](#-price-calculation-utilities) -- [🧮 Amount Calculation Utilities](#-amount-calculation-utilities) - [📁 Project Structure](#-project-structure) - [📄 License](#-license) - [💬 Contact](#-contact) @@ -67,13 +66,11 @@ 3. **Bonk Trading**: Support for Bonk trading operations 4. **Raydium CPMM Trading**: Support for Raydium CPMM (Concentrated Pool Market Maker) trading operations 5. **Raydium AMM V4 Trading**: Support for Raydium AMM V4 (Automated Market Maker) trading operations -6. **Event Subscription**: Subscribe to PumpFun, PumpSwap, Bonk, Raydium CPMM, and Raydium AMM V4 program trading events -7. **Yellowstone gRPC**: Subscribe to program events using Yellowstone gRPC -8. **ShredStream Support**: Subscribe to program events using ShredStream -9. **Multiple MEV Protection**: Support for Jito, Nextblock, ZeroSlot, Temporal, Bloxroute, FlashBlock, BlockRazor, Node1, Astralane and other services -10. **Concurrent Trading**: Send transactions using multiple MEV services simultaneously; the fastest succeeds while others fail -11. **Unified Trading Interface**: Use unified trading protocol enums for trading operations -12. **Middleware System**: Support for custom instruction middleware to modify, add, or remove instructions before transaction execution +6. **Event Subscription**: SDK integrates solana-streamer SDK, supports subscribing to PumpFun, PumpSwap, Bonk, Raydium CPMM, and Raydium AMM V4 program trading events, the description of this SDK can be found in [solana-streamer SDK](https://github.com/0xfnzero/solana-streamer). +7. **Multiple MEV Protection**: Support for Jito, Nextblock, ZeroSlot, Temporal, Bloxroute, FlashBlock, BlockRazor, Node1, Astralane and other services +8. **Concurrent Trading**: Send transactions using multiple MEV services simultaneously; the fastest succeeds while others fail +9. **Unified Trading Interface**: Use unified trading protocol enums for trading operations +10. **Middleware System**: Support for custom instruction middleware to modify, add, or remove instructions before transaction execution ## 📦 Installation @@ -90,74 +87,86 @@ Add the dependency to your `Cargo.toml`: ```toml # Add to your Cargo.toml -sol-trade-sdk = { path = "./sol-trade-sdk", version = "0.6.15" } +sol-trade-sdk = { path = "./sol-trade-sdk", version = "1.0.1" } ``` ### Use crates.io ```toml # Add to your Cargo.toml -sol-trade-sdk = "0.6.15" +sol-trade-sdk = "1.0.1" ``` ## 🛠️ Usage Examples -### 📋 Important Parameter Description +### 📋 Example Usage -#### 🌱 open_seed_optimize Parameter +#### 1. Create SolanaTrade Instance -`open_seed_optimize` is used to specify whether to use seed optimization to reduce transaction CU consumption. +You can refer to [Example: Create SolanaTrade Instance](examples/trading_client/src/main.rs). -- **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. +```rust +// Wallet +let payer = Keypair::from_base58_string("use_your_payer_keypair_here"); +// RPC URL +let rpc_url = "https://mainnet.helius-rpc.com/?api-key=xxxxxx".to_string(); +let commitment = CommitmentConfig::processed(); +// Multiple SWQOS services can be configured +let swqos_configs: Vec = vec![ + SwqosConfig::Default(rpc_url.clone()), + SwqosConfig::Jito("your uuid".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::FlashBlock("your api_token".to_string(), SwqosRegion::Frankfurt, None), + SwqosConfig::Node1("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), +]; +// Create TradeConfig instance +let trade_config = TradeConfig::new(rpc_url, swqos_configs, commitment); +// Create SolanaTrade client +let client = SolanaTrade::new(Arc::new(payer), trade_config).await; +``` -#### 💰 create_wsol_ata and close_wsol_ata、 create_mint_ata Parameters +#### 2. Configure Gas Fee Strategy -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: +For detailed information about Gas Fee Strategy, see the [Gas Fee Strategy Reference](docs/GAS_FEE_STRATEGY.md). +```rust +GasFeeStrategy::set_global_fee_strategy(150000, 500000, 0.001, 0.001); +``` -- **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 +#### 3. Build Trading Parameters -- **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 +For detailed information about all trading parameters, see the [Trading Parameters Reference](docs/TRADING_PARAMETERS.md). -- **create_mint_ata**: - - When `create_mint_ata: true`, the SDK automatically creates the token ata account before trading +```rust +let buy_params = sol_trade_sdk::TradeBuyParams { + dex_type: DexType::PumpSwap, + mint: mint_pubkey, + sol_amount: buy_sol_amount, + slippage_basis_points: slippage_basis_points, + recent_blockhash: recent_blockhash, + extension_params: Box::new(params.clone()), + lookup_table_key: None, + wait_transaction_confirmed: true, + create_wsol_ata: true, + close_wsol_ata: true, + create_mint_ata: true, + open_seed_optimize: false, +}; +``` -- **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 +#### 4. Execute Trading -#### 🔍 lookup_table_key Parameter +```rust +client.buy(buy_params).await?; +``` -The `lookup_table_key` parameter is an optional `Pubkey` that specifies an address lookup table for transaction optimization. You need to use `AddressLookupTableCache` to manage the cached address lookup table before using it. +### ⚡ Trading Parameters -- **Purpose**: Address lookup tables can reduce transaction size and improve execution speed by storing frequently used addresses -- **Usage**: - - Can be overridden per transaction in `buy()` and `sell()` methods - - If not provided, defaults to `None` -- **Benefits**: - - Reduces transaction size by referencing addresses from lookup tables - - Improves transaction success rate and speed - - Particularly useful for complex transactions with many account references - -#### ⚡ priority_fee Parameter - -The `priority_fee` parameter is an optional `PriorityFee` that allows you to override the default priority fee settings for individual transactions: - -- **Purpose**: Provides fine-grained control over transaction priority fees on a per-transaction basis -- **Usage**: - - Can be passed to `buy()` and `sell()` methods to override the global priority fee settings - - If not provided, defaults to `None` and uses the priority fee settings from `TradeConfig` - - When provided, the `buy_tip_fees` array will be automatically padded to match the number of SWQOS clients -- **Benefits**: - - Allows dynamic adjustment of priority fees based on market conditions - - Enables different fee strategies for different types of transactions - - Provides flexibility for high-frequency trading scenarios +For comprehensive information about all trading parameters including `TradeBuyParams` and `TradeSellParams`, see the dedicated [Trading Parameters Reference](docs/TRADING_PARAMETERS.md). #### About ShredStream @@ -166,22 +175,23 @@ Please ensure that the parameters your trading logic depends on are available in ### 📊 Usage Examples Summary Table -| Feature Type | Package Name | Description | Run Command | Source Code | -|-------------|--------------|-------------|-------------|-------------| -| Event Subscription | `event_subscription` | Monitor token trading events | `cargo run --package event_subscription` | [examples/event_subscription](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/event_subscription/src/main.rs) | -| Trading Client | `trading_client` | Create and configure SolanaTrade instance | `cargo run --package trading_client` | [examples/trading_client](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/trading_client/src/main.rs) | -| PumpFun Sniping | `pumpfun_sniper_trading` | PumpFun token sniping trading | `cargo run --package pumpfun_sniper_trading` | [examples/pumpfun_sniper_trading](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/pumpfun_sniper_trading/src/main.rs) | -| PumpFun Copy Trading | `pumpfun_copy_trading` | PumpFun token copy trading | `cargo run --package pumpfun_copy_trading` | [examples/pumpfun_copy_trading](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/pumpfun_copy_trading/src/main.rs) | -| PumpSwap | `pumpswap_trading` | PumpSwap trading operations | `cargo run --package pumpswap_trading` | [examples/pumpswap_trading](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/pumpswap_trading/src/main.rs) | -| Raydium CPMM | `raydium_cpmm_trading` | Raydium CPMM trading operations | `cargo run --package raydium_cpmm_trading` | [examples/raydium_cpmm_trading](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/raydium_cpmm_trading/src/main.rs) | -| Raydium AMM V4 | `raydium_amm_v4_trading` | Raydium AMM V4 trading operations | `cargo run --package raydium_amm_v4_trading` | [examples/raydium_amm_v4_trading](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/raydium_amm_v4_trading/src/main.rs) | -| Bonk Sniping | `bonk_sniper_trading` | Bonk token sniping trading | `cargo run --package bonk_sniper_trading` | [examples/bonk_sniper_trading](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/bonk_sniper_trading/src/main.rs) | -| Bonk Copy Trading | `bonk_copy_trading` | Bonk token copy trading | `cargo run --package bonk_copy_trading` | [examples/bonk_copy_trading](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/bonk_copy_trading/src/main.rs) | -| 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) | +| Description | Run Command | Source Code | +|-------------|-------------|-------------| +| Monitor token trading events | `cargo run --package event_subscription` | [examples/event_subscription](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/event_subscription/src/main.rs) | +| Create and configure SolanaTrade instance | `cargo run --package trading_client` | [examples/trading_client](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/trading_client/src/main.rs) | +| PumpFun token sniping trading | `cargo run --package pumpfun_sniper_trading` | [examples/pumpfun_sniper_trading](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/pumpfun_sniper_trading/src/main.rs) | +| PumpFun token copy trading | `cargo run --package pumpfun_copy_trading` | [examples/pumpfun_copy_trading](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/pumpfun_copy_trading/src/main.rs) | +| PumpSwap trading operations | `cargo run --package pumpswap_trading` | [examples/pumpswap_trading](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/pumpswap_trading/src/main.rs) | +| Raydium CPMM trading operations | `cargo run --package raydium_cpmm_trading` | [examples/raydium_cpmm_trading](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/raydium_cpmm_trading/src/main.rs) | +| Raydium AMM V4 trading operations | `cargo run --package raydium_amm_v4_trading` | [examples/raydium_amm_v4_trading](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/raydium_amm_v4_trading/src/main.rs) | +| Bonk token sniping trading | `cargo run --package bonk_sniper_trading` | [examples/bonk_sniper_trading](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/bonk_sniper_trading/src/main.rs) | +| Bonk token copy trading | `cargo run --package bonk_copy_trading` | [examples/bonk_copy_trading](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/bonk_copy_trading/src/main.rs) | +| 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 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 example | `cargo run --package nonce_cache` | [examples/nonce_cache](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/nonce_cache/src/main.rs) | +| 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 example | `cargo run --package seed_trading` | [examples/seed_trading](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/seed_trading/src/main.rs) | +| Gas fee strategy example | `cargo run --package gas_fee_strategy` | [examples/gas_fee_strategy](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/gas_fee_strategy/src/main.rs) | ### ⚙️ SWQOS Service Configuration @@ -215,7 +225,7 @@ let nextblock_config = SwqosConfig::NextBlock( - If no custom URL is provided (`None`), the system will use the default endpoint for the specified `SwqosRegion` - This allows for maximum flexibility while maintaining backward compatibility -When using multiple MEV services, you need to use `Durable Nonce`. You need to initialize a `NonceCache` class (or write your own nonce management class), get the latest `nonce` value, and use it as the `blockhash` when trading. +When using multiple MEV services, you need to use `Durable Nonce`. You need to initialize a `NonceCache` class (or write your own nonce management class), get the latest `nonce` value, and use it as the `nonce_account` and `current_nonce` when trading. --- @@ -230,41 +240,18 @@ let middleware_manager = MiddlewareManager::new() .add_middleware(Box::new(ThirdMiddleware)); // Executes last ``` -### ⚡ Custom Priority Fee Configuration +### 🔍 Address Lookup Tables -```rust -use sol_trade_sdk::common::PriorityFee; +Address Lookup Tables (ALT) allow you to optimize transaction size and reduce fees by storing frequently used addresses in a compact table format. For detailed information, see the [Address Lookup Tables Guide](docs/ADDRESS_LOOKUP_TABLE.md). -// Custom priority fee configuration -let priority_fee = PriorityFee { - tip_unit_limit: 190000, - tip_unit_price: 1000000, - rpc_unit_limit: 500000, - rpc_unit_price: 500000, - buy_tip_fee: 0.001, - buy_tip_fees: vec![0.001, 0.002], - sell_tip_fee: 0.0001, -}; +### 🔍 Nonce Cache -// Use custom priority fee in TradeConfig -let trade_config = TradeConfig { - rpc_url: rpc_url.clone(), - commitment: CommitmentConfig::confirmed(), - priority_fee, // Use custom priority fee - swqos_configs, -}; -``` - -## 🏪 Supported Trading Platforms - -- **PumpFun**: Primary meme coin trading platform -- **PumpSwap**: PumpFun's swap protocol -- **Bonk**: Token launch platform (letsbonk.fun) -- **Raydium CPMM**: Raydium's Concentrated Pool Market Maker protocol -- **Raydium AMM V4**: Raydium's Automated Market Maker V4 protocol +Use Nonce Cache to implement transaction replay protection and optimize transaction processing. For detailed information, see the [Nonce Cache Guide](docs/NONCE_CACHE.md). ## 🛡️ MEV Protection Services +You can apply for a key through the official website: [Community Website](https://fnzero.dev/swqos) + - **Jito**: High-performance block space - **NextBlock**: Fast transaction execution - **ZeroSlot**: Zero-latency transactions @@ -275,28 +262,6 @@ let trade_config = TradeConfig { - **Node1**: High-speed transaction execution with API key authentication - [Official Documentation](https://node1.me/docs.html) - **Astralane**: Blockchain network acceleration -## 💰 Price Calculation Utilities - -The SDK includes price calculation utilities for all supported protocols in `src/utils/price/`. - -## 🧮 Amount Calculation Utilities - -The SDK provides trading amount calculation functionality for various protocols, located in `src/utils/calc/`: - -- **Common Calculation Functions**: Provides general fee calculation and division utilities -- **Protocol-Specific Calculations**: Specialized calculation logic for each protocol - - **PumpFun**: Token buy/sell amount calculations based on bonding curves - - **PumpSwap**: Amount calculations for multiple trading pairs - - **Raydium AMM V4**: Amount and fee calculations for automated market maker pools - - **Raydium CPMM**: Amount calculations for constant product market makers - - **Bonk**: Specialized calculation logic for Bonk tokens - -Key features include: -- Calculate output amounts based on input amounts -- Fee calculation and distribution -- Slippage protection calculations -- Liquidity pool state calculations - ## 📁 Project Structure ``` @@ -304,38 +269,18 @@ src/ ├── common/ # Common functionality and tools ├── constants/ # Constant definitions ├── instruction/ # Instruction building +│ └── utils/ # Instruction utilities +├── protos/ # gRPC protocol definitions ├── swqos/ # MEV service clients ├── trading/ # Unified trading engine │ ├── common/ # Common trading tools │ ├── core/ # Core trading engine │ ├── middleware/ # Middleware system -│ │ ├── builtin.rs # Built-in middleware implementations -│ │ ├── traits.rs # Middleware trait definitions -│ │ └── mod.rs # Middleware module -│ ├── bonk/ # Bonk trading implementation -│ ├── pumpfun/ # PumpFun trading implementation -│ ├── pumpswap/ # PumpSwap trading implementation -│ ├── raydium_cpmm/ # Raydium CPMM trading implementation -│ ├── raydium_amm_v4/ # Raydium AMM V4 trading implementation │ └── factory.rs # Trading factory ├── utils/ # Utility functions -│ ├── price/ # Price calculation utilities -│ │ ├── common.rs # Common price functions -│ │ ├── bonk.rs # Bonk price calculations -│ │ ├── pumpfun.rs # PumpFun price calculations -│ │ ├── pumpswap.rs # PumpSwap price calculations -│ │ ├── raydium_cpmm.rs # Raydium CPMM price calculations -│ │ ├── raydium_clmm.rs # Raydium CLMM price calculations -│ │ └── raydium_amm_v4.rs # Raydium AMM V4 price calculations -│ └── calc/ # Amount calculation utilities -│ ├── common.rs # Common calculation functions -│ ├── bonk.rs # Bonk amount calculations -│ ├── pumpfun.rs # PumpFun amount calculations -│ ├── pumpswap.rs # PumpSwap amount calculations -│ ├── raydium_cpmm.rs # Raydium CPMM amount calculations -│ └── raydium_amm_v4.rs # Raydium AMM V4 amount calculations -├── lib.rs # Main library file -└── main.rs # Example program +│ ├── calc/ # Amount calculation utilities +│ └── price/ # Price calculation utilities +└── lib.rs # Main library file ``` ## 📄 License diff --git a/README_CN.md b/README_CN.md index 0ca93c2..5bea67d 100755 --- a/README_CN.md +++ b/README_CN.md @@ -35,7 +35,8 @@ 中文 | English | Website | - Telegram + Telegram | + Discord

## 📋 目录 @@ -43,15 +44,14 @@ - [✨ 项目特性](#-项目特性) - [📦 安装](#-安装) - [🛠️ 使用示例](#️-使用示例) - - [📋 重要说明](#-重要说明) + - [📋 使用示例](#-使用示例) + - [⚡ 交易参数](#-交易参数) - [📊 使用示例汇总表格](#-使用示例汇总表格) - [⚙️ SWQOS 服务配置说明](#️-swqos-服务配置说明) - [🔧 中间件系统说明](#-中间件系统说明) - - [⚡ 自定义优先费用配置](#-自定义优先费用配置) -- [🏪 支持的交易平台](#-支持的交易平台) + - [🔍 地址查找表](#-地址查找表) + - [🔍 Nonce 缓存](#-nonce-缓存) - [🛡️ MEV 保护服务](#️-mev-保护服务) -- [💰 价格计算工具](#-价格计算工具) -- [🧮 数量计算工具](#-数量计算工具) - [📁 项目结构](#-项目结构) - [📄 许可证](#-许可证) - [💬 联系方式](#-联系方式) @@ -66,13 +66,11 @@ 3. **Bonk 交易**: 支持 Bonk 的交易操作 4. **Raydium CPMM 交易**: 支持 Raydium CPMM (Concentrated Pool Market Maker) 的交易操作 5. **Raydium AMM V4 交易**: 支持 Raydium AMM V4 (Automated Market Maker) 的交易操作 -6. **事件订阅**: 订阅 PumpFun、PumpSwap、Bonk、Raydium CPMM 和 Raydium AMM V4 程序的交易事件 -7. **Yellowstone gRPC**: 使用 Yellowstone gRPC 订阅程序事件 -8. **ShredStream 支持**: 使用 ShredStream 订阅程序事件 -9. **多种 MEV 保护**: 支持 Jito、Nextblock、ZeroSlot、Temporal、Bloxroute、FlashBlock、BlockRazor、Node1、Astralane 等服务 -10. **并发交易**: 同时使用多个 MEV 服务发送交易,最快的成功,其他失败 -11. **统一交易接口**: 使用统一的交易协议枚举进行交易操作 -12. **中间件系统**: 支持自定义指令中间件,可在交易执行前对指令进行修改、添加或移除 +6. **事件订阅**: SDK 集成了 solana-streamer SDK,支持引用该 SDK 订阅 PumpFun、PumpSwap、Bonk、Raydium CPMM 和 Raydium AMM V4 程序的交易事件,该 SDK 的说明可以查阅:[solana-streamer SDK](https://github.com/0xfnzero/solana-streamer)。 +7. **多种 MEV 保护**: 支持 Jito、Nextblock、ZeroSlot、Temporal、Bloxroute、FlashBlock、BlockRazor、Node1、Astralane 等服务 +8. **并发交易**: 同时使用多个 MEV 服务发送交易,最快的成功,其他失败 +9. **统一交易接口**: 使用统一的交易协议枚举进行交易操作 +10. **中间件系统**: 支持自定义指令中间件,可在交易执行前对指令进行修改、添加或移除 ## 📦 安装 @@ -89,74 +87,87 @@ git clone https://github.com/0xfnzero/sol-trade-sdk ```toml # 添加到您的 Cargo.toml -sol-trade-sdk = { path = "./sol-trade-sdk", version = "0.6.15" } +sol-trade-sdk = { path = "./sol-trade-sdk", version = "1.0.1" } ``` ### 使用 crates.io ```toml # 添加到您的 Cargo.toml -sol-trade-sdk = "0.6.15" +sol-trade-sdk = "1.0.1" ``` ## 🛠️ 使用示例 -### 📋 重要说明 +### 📋 使用示例 -#### 🌱 open_seed_optimize 参数 +#### 1. 创建 SolanaTrade 实例 -`open_seed_optimize` ,用于指定是否使用 seed 优化交易 CU 消耗。 +可以参考 [示例:创建 SolanaTrade 实例](examples/trading_client/src/main.rs)。 -- **用途**:当 `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` 方法获取。 +```rust +// 钱包 +let payer = Keypair::from_base58_string("use_your_payer_keypair_here"); +// RPC 地址 +let rpc_url = "https://mainnet.helius-rpc.com/?api-key=xxxxxx".to_string(); +let commitment = CommitmentConfig::processed(); +// 可以配置多个SWQOS服务 +let swqos_configs: Vec = vec![ + SwqosConfig::Default(rpc_url.clone()), + SwqosConfig::Jito("your uuid".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::FlashBlock("your api_token".to_string(), SwqosRegion::Frankfurt, None), + SwqosConfig::Node1("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), +]; +// 创建 TradeConfig 实例 +let trade_config = TradeConfig::new(rpc_url, swqos_configs, commitment); +// 创建 SolanaTrade 客户端 +let client = SolanaTrade::new(Arc::new(payer), trade_config).await; +``` -#### 💰 create_wsol_ata 和 close_wsol_ata、 create_mint_ata 参数 +#### 2. 配置 Gas Fee 策略 -在 PumpSwap、Bonk、Raydium 交易中,`create_wsol_ata` 和 `close_wsol_ata`、 `create_mint_ata` 参数提供对 wSOL(Wrapped SOL)账户管理的精细控制: +有关 Gas Fee 策略的详细信息,请参阅 [Gas Fee 策略参考手册](docs/GAS_FEE_STRATEGY_CN.md)。 +```rust +// 设置全局策略 +GasFeeStrategy::set_global_fee_strategy(150000, 500000, 0.001, 0.001); +``` -- **create_wsol_ata**: - - 当 `create_wsol_ata: true` 时,SDK 会在交易前自动创建并将 SOL 包装为 wSOL - - 买入时:自动将 SOL 包装为 wSOL 进行交易 +#### 3. 构建交易参数 -- **close_wsol_ata**: - - 当 `close_wsol_ata: true` 时,SDK 会在交易后自动关闭 wSOL 账户并解包装为 SOL - - 卖出时:自动将获得的 wSOL 解包装为 SOL 并回收租金 +有关所有交易参数的详细信息,请参阅 [交易参数参考手册](docs/TRADING_PARAMETERS_CN.md)。 -- **create_mint_ata**: - - 当 `create_mint_ata: true` 时,SDK 会在交易时创建代币ata账户 +```rust +let buy_params = sol_trade_sdk::TradeBuyParams { + dex_type: DexType::PumpSwap, + mint: mint_pubkey, + sol_amount: buy_sol_amount, + slippage_basis_points: slippage_basis_points, + recent_blockhash: recent_blockhash, + extension_params: Box::new(params.clone()), + lookup_table_key: None, + wait_transaction_confirmed: true, + create_wsol_ata: true, + close_wsol_ata: true, + create_mint_ata: true, + open_seed_optimize: false, +}; +``` -- **分离参数的优势**: - - 允许独立控制 wSOL 账户的创建和关闭 - - 适用于批量操作,可以创建一次,在多次交易后再关闭 - - 为高级交易策略提供灵活性 +#### 4. 执行交易 -#### 🔍 lookup_table_key 参数 +```rust +client.buy(buy_params).await?; +``` -`lookup_table_key` 参数是一个可选的 `Pubkey`,用于指定地址查找表以优化交易。在使用前你需要通过`AddressLookupTableCache`来管理缓存地址查找表。 +### ⚡ 交易参数 -- **用途**:地址查找表可以通过存储常用地址来减少交易大小并提高执行速度 -- **使用方法**: - - 可以在 `buy()` 和 `sell()` 方法中按交易覆盖 - - 如果不提供,默认为 `None` -- **优势**: - - 通过从查找表引用地址来减少交易大小 - - 提高交易成功率和速度 - - 特别适用于具有许多账户引用的复杂交易 - -#### ⚡ priority_fee 参数 - -`priority_fee` 参数是一个可选的 `PriorityFee`,允许您为单个交易覆盖默认的优先级费用设置: - -- **用途**:为每个交易提供对交易优先级费用的细粒度控制 -- **使用方法**: - - 可以传递给 `buy()` 和 `sell()` 方法来覆盖全局优先级费用设置 - - 如果不提供,默认为 `None` 并使用 `TradeConfig` 中的优先级费用设置 - - 当提供时,`buy_tip_fees` 数组将自动填充以匹配 SWQOS 客户端的数量 -- **优势**: - - 允许根据市场条件动态调整优先级费用 - - 为不同类型的交易启用不同的费用策略 - - 为高频交易场景提供灵活性 +有关所有交易参数(包括 `TradeBuyParams` 和 `TradeSellParams`)的详细信息,请参阅专门的 [交易参数参考手册](docs/TRADING_PARAMETERS_CN.md)。 #### 关于shredstream @@ -165,22 +176,23 @@ sol-trade-sdk = "0.6.15" ### 📊 使用示例汇总表格 -| 功能类型 | 示例包名 | 描述 | 运行命令 | 源码路径 | -|---------|---------|------|---------|----------| -| 事件订阅 | `event_subscription` | 监听代币交易事件 | `cargo run --package event_subscription` | [examples/event_subscription](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/event_subscription/src/main.rs) | -| 交易客户端 | `trading_client` | 创建和配置 SolanaTrade 实例 | `cargo run --package trading_client` | [examples/trading_client](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/trading_client/src/main.rs) | -| PumpFun 狙击 | `pumpfun_sniper_trading` | PumpFun 代币狙击交易 | `cargo run --package pumpfun_sniper_trading` | [examples/pumpfun_sniper_trading](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/pumpfun_sniper_trading/src/main.rs) | -| PumpFun 跟单 | `pumpfun_copy_trading` | PumpFun 代币跟单交易 | `cargo run --package pumpfun_copy_trading` | [examples/pumpfun_copy_trading](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/pumpfun_copy_trading/src/main.rs) | -| PumpSwap | `pumpswap_trading` | PumpSwap 交易操作 | `cargo run --package pumpswap_trading` | [examples/pumpswap_trading](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/pumpswap_trading/src/main.rs) | -| Raydium CPMM | `raydium_cpmm_trading` | Raydium CPMM 交易操作 | `cargo run --package raydium_cpmm_trading` | [examples/raydium_cpmm_trading](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/raydium_cpmm_trading/src/main.rs) | -| Raydium AMM V4 | `raydium_amm_v4_trading` | Raydium AMM V4 交易操作 | `cargo run --package raydium_amm_v4_trading` | [examples/raydium_amm_v4_trading](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/raydium_amm_v4_trading/src/main.rs) | -| Bonk 狙击 | `bonk_sniper_trading` | Bonk 代币狙击交易 | `cargo run --package bonk_sniper_trading` | [examples/bonk_sniper_trading](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/bonk_sniper_trading/src/main.rs) | -| Bonk 跟单 | `bonk_copy_trading` | Bonk 代币跟单交易 | `cargo run --package bonk_copy_trading` | [examples/bonk_copy_trading](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/bonk_copy_trading/src/main.rs) | -| 中间件系统 | `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) | +| 描述 | 运行命令 | 源码路径 | +|------|---------|----------| +| 监听代币交易事件 | `cargo run --package event_subscription` | [examples/event_subscription](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/event_subscription/src/main.rs) | +| 创建和配置 SolanaTrade 实例 | `cargo run --package trading_client` | [examples/trading_client](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/trading_client/src/main.rs) | +| PumpFun 代币狙击交易 | `cargo run --package pumpfun_sniper_trading` | [examples/pumpfun_sniper_trading](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/pumpfun_sniper_trading/src/main.rs) | +| PumpFun 代币跟单交易 | `cargo run --package pumpfun_copy_trading` | [examples/pumpfun_copy_trading](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/pumpfun_copy_trading/src/main.rs) | +| PumpSwap 交易操作 | `cargo run --package pumpswap_trading` | [examples/pumpswap_trading](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/pumpswap_trading/src/main.rs) | +| Raydium CPMM 交易操作 | `cargo run --package raydium_cpmm_trading` | [examples/raydium_cpmm_trading](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/raydium_cpmm_trading/src/main.rs) | +| Raydium AMM V4 交易操作 | `cargo run --package raydium_amm_v4_trading` | [examples/raydium_amm_v4_trading](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/raydium_amm_v4_trading/src/main.rs) | +| Bonk 代币狙击交易 | `cargo run --package bonk_sniper_trading` | [examples/bonk_sniper_trading](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/bonk_sniper_trading/src/main.rs) | +| Bonk 代币跟单交易 | `cargo run --package bonk_copy_trading` | [examples/bonk_copy_trading](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/bonk_copy_trading/src/main.rs) | +| 自定义指令中间件示例 | `cargo run --package middleware_system` | [examples/middleware_system](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/middleware_system/src/main.rs) | +| 地址查找表示例 | `cargo run --package address_lookup` | [examples/address_lookup](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/address_lookup/src/main.rs) | +| Nonce示例 | `cargo run --package nonce_cache` | [examples/nonce_cache](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/nonce_cache/src/main.rs) | +| 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 优化交易示例 | `cargo run --package seed_trading` | [examples/seed_trading](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/seed_trading/src/main.rs) | +| Gas费用策略示例 | `cargo run --package gas_fee_strategy` | [examples/gas_fee_strategy](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/gas_fee_strategy/src/main.rs) | ### ⚙️ SWQOS 服务配置说明 @@ -214,7 +226,7 @@ let nextblock_config = SwqosConfig::NextBlock( - 如果没有提供自定义 URL(`None`),系统将使用指定 `SwqosRegion` 的默认端点 - 这提供了最大的灵活性,同时保持向后兼容性 -当使用多个MEV服务时,需要使用`Durable Nonce`。你需要初始化`NonceCache`类(或者自行写一个管理nonce的类),获取最新的`nonce`值,并在交易的时候作为`blockhash`使用。 +当使用多个MEV服务时,需要使用`Durable Nonce`。你需要初始化`NonceCache`类(或者自行写一个管理nonce的类),获取最新的`nonce`值,并在交易的时候将`nonce_account`和`current_nonce`填入交易参数。 --- @@ -229,41 +241,18 @@ let middleware_manager = MiddlewareManager::new() .add_middleware(Box::new(ThirdMiddleware)); // 最后执行 ``` -### ⚡ 自定义优先费用配置 +### 🔍 地址查找表 -```rust -use sol_trade_sdk::common::PriorityFee; +地址查找表 (ALT) 允许您通过将经常使用的地址存储在紧凑的表格格式中来优化交易大小并降低费用。详细信息请参阅 [地址查找表指南](docs/ADDRESS_LOOKUP_TABLE_CN.md)。 -// 自定义优先费用配置 -let priority_fee = PriorityFee { - tip_unit_limit: 190000, - tip_unit_price: 1000000, - rpc_unit_limit: 500000, - rpc_unit_price: 500000, - buy_tip_fee: 0.001, - buy_tip_fees: vec![0.001, 0.002], - sell_tip_fee: 0.0001, -}; +### 🔍 Nonce 缓存 -// 在TradeConfig中使用自定义优先费用 -let trade_config = TradeConfig { - rpc_url: rpc_url.clone(), - commitment: CommitmentConfig::confirmed(), - priority_fee, // 使用自定义优先费用 - swqos_configs, -}; -``` - -## 🏪 支持的交易平台 - -- **PumpFun**: 主要的 meme 币交易平台 -- **PumpSwap**: PumpFun 的交换协议 -- **Bonk**: 代币发行平台(letsbonk.fun) -- **Raydium CPMM**: Raydium 的集中流动性做市商协议 -- **Raydium AMM V4**: Raydium 的自动做市商 V4 协议 +使用 Nonce 缓存来实现交易重放保护和优化交易处理。详细信息请参阅 [Nonce 缓存指南](docs/NONCE_CACHE_CN.md)。 ## 🛡️ MEV 保护服务 +可以通过官网申请密钥:[社区官网](https://fnzero.dev/swqos) + - **Jito**: 高性能区块空间 - **NextBlock**: 快速交易执行 - **ZeroSlot**: 零延迟交易 @@ -274,29 +263,6 @@ let trade_config = TradeConfig { - **Node1**: 高速交易执行,支持 API 密钥认证 - [官方文档](https://node1.me/docs.html) - **Astralane**: 高速交易执行,支持 API 密钥认证 - -## 💰 价格计算工具 - -SDK 包含所有支持协议的价格计算工具,位于 `src/utils/price/` 目录。 - -## 🧮 数量计算工具 - -SDK 提供各种协议的交易数量计算功能,位于 `src/utils/calc/` 目录: - -- **通用计算函数**: 提供通用的手续费计算和除法运算工具 -- **协议特定计算**: 针对每个协议的特定计算逻辑 - - **PumpFun**: 基于联合曲线的代币购买/销售数量计算 - - **PumpSwap**: 支持多种交易对的数量计算 - - **Raydium AMM V4**: 自动做市商池的数量和手续费计算 - - **Raydium CPMM**: 恒定乘积做市商的数量计算 - - **Bonk**: 专门的 Bonk 代币计算逻辑 - -主要功能包括: -- 根据输入金额计算输出数量 -- 手续费计算和分配 -- 滑点保护计算 -- 流动性池状态计算 - ## 📁 项目结构 ``` @@ -304,38 +270,18 @@ src/ ├── common/ # 通用功能和工具 ├── constants/ # 常量定义 ├── instruction/ # 指令构建 -├── swqos/ # MEV服务客户端 +│ └── utils/ # 指令工具函数 +├── protos/ # gRPC 协议定义 +├── swqos/ # MEV 服务客户端 ├── trading/ # 统一交易引擎 │ ├── common/ # 通用交易工具 │ ├── core/ # 核心交易引擎 │ ├── middleware/ # 中间件系统 -│ │ ├── builtin.rs # 内置中间件实现 -│ │ ├── traits.rs # 中间件 trait 定义 -│ │ └── mod.rs # 中间件模块 -│ ├── bonk/ # Bonk交易实现 -│ ├── pumpfun/ # PumpFun交易实现 -│ ├── pumpswap/ # PumpSwap交易实现 -│ ├── raydium_cpmm/ # Raydium CPMM交易实现 -│ ├── raydium_amm_v4/ # Raydium AMM V4交易实现 │ └── factory.rs # 交易工厂 ├── utils/ # 工具函数 -│ ├── price/ # 价格计算工具 -│ │ ├── common.rs # 通用价格函数 -│ │ ├── bonk.rs # Bonk 价格计算 -│ │ ├── pumpfun.rs # PumpFun 价格计算 -│ │ ├── pumpswap.rs # PumpSwap 价格计算 -│ │ ├── raydium_cpmm.rs # Raydium CPMM 价格计算 -│ │ ├── raydium_clmm.rs # Raydium CLMM 价格计算 -│ │ └── raydium_amm_v4.rs # Raydium AMM V4 价格计算 -│ └── calc/ # 数量计算工具 -│ ├── common.rs # 通用计算函数 -│ ├── bonk.rs # Bonk 数量计算 -│ ├── pumpfun.rs # PumpFun 数量计算 -│ ├── pumpswap.rs # PumpSwap 数量计算 -│ ├── raydium_cpmm.rs # Raydium CPMM 数量计算 -│ └── raydium_amm_v4.rs # Raydium AMM V4 数量计算 -├── lib.rs # 主库文件 -└── main.rs # 示例程序 +│ ├── calc/ # 数量计算工具 +│ └── price/ # 价格计算工具 +└── lib.rs # 主库文件 ``` ## 📄 许可证 @@ -347,6 +293,7 @@ MIT 许可证 - 官方网站: https://fnzero.dev/ - 项目仓库: https://github.com/0xfnzero/sol-trade-sdk - Telegram 群组: https://t.me/fnzero_group +- Discord: https://discord.gg/vuazbGkqQE ## ⚠️ 重要注意事项 diff --git a/docs/ADDRESS_LOOKUP_TABLE.md b/docs/ADDRESS_LOOKUP_TABLE.md new file mode 100644 index 0000000..8d6fdae --- /dev/null +++ b/docs/ADDRESS_LOOKUP_TABLE.md @@ -0,0 +1,93 @@ +# Address Lookup Table Guide + +This guide explains how to use Address Lookup Tables (ALT) in Sol Trade SDK to optimize transaction size and reduce fees. + +## 📋 What are Address Lookup Tables? + +Address Lookup Tables are a Solana feature that allows you to store frequently used addresses in a compact table format. Instead of including full 32-byte addresses in transactions, you can reference addresses by their index in the lookup table, significantly reducing transaction size and cost. + +## 🚀 Core Benefits + +- **Transaction Size Optimization**: Reduce transaction size by using address indices instead of full addresses +- **Cost Reduction**: Lower transaction fees due to reduced transaction size +- **Performance Improvement**: Faster transaction processing and validation +- **Network Efficiency**: Reduced bandwidth usage and block space consumption + +## 🛠️ Implementation + +### 1. Setting up Address Lookup Table Cache + +The SDK provides a global cache to manage address lookup tables: + +```rust +use sol_trade_sdk::common::address_lookup_cache::AddressLookupTableCache; +use solana_sdk::pubkey::Pubkey; +use std::str::FromStr; + +/// Setup lookup table cache +async fn setup_lookup_table_cache( + client: Arc, + lookup_table_address: Pubkey, +) -> AnyResult<()> { + AddressLookupTableCache::get_instance() + .set_address_lookup_table(client, &lookup_table_address) + .await + .map_err(|e| anyhow::anyhow!("Failed to set address lookup table: {}", e))?; + Ok(()) +} +``` + +### 2. Using Lookup Tables in Trade Parameters + +Include lookup tables in your trade parameters: + +```rust +// Initialize lookup table +let lookup_table_key = Pubkey::from_str("your_lookup_table_address_here").unwrap(); +setup_lookup_table_cache(client.rpc.clone(), lookup_table_key).await?; + +// Include lookup table in trade parameters +let buy_params = sol_trade_sdk::TradeBuyParams { + dex_type: DexType::PumpFun, + mint: mint_pubkey, + sol_amount: buy_sol_amount, + slippage_basis_points: Some(100), + recent_blockhash: recent_blockhash, + extension_params: Box::new(PumpFunParams::from_trade(&trade_info, None)), + lookup_table_key: Some(lookup_table_key), // Include lookup table + wait_transaction_confirmed: true, + create_wsol_ata: false, + close_wsol_ata: false, + create_mint_ata: true, + open_seed_optimize: false, +}; + +// Execute transaction +client.buy(buy_params).await?; +``` + +## 📊 Performance Comparison + +| Aspect | Without ALT | With ALT | Improvement | +|--------|-------------|----------|-------------| +| **Transaction Size** | ~1,232 bytes | ~800 bytes | 35% reduction | +| **Address Storage** | 32 bytes per address | 1 byte per address | 97% reduction | +| **Transaction Fees** | Higher | Lower | Up to 30% savings | +| **Block Space Usage** | More | Less | Improved network efficiency | + +## ⚠️ Important Notes + +1. **Lookup Table Address**: Must provide a valid address lookup table address +2. **Cache Management**: SDK automatically manages lookup table cache +3. **RPC Compatibility**: Ensure your RPC provider supports lookup tables +4. **Network Specific**: Lookup tables are network-specific (mainnet/devnet/testnet) +5. **Testing**: Always test on devnet before using on mainnet + +## 🔗 Related Documentation + +- [Trading Parameters Reference](TRADING_PARAMETERS.md) +- [Example: Address Lookup Table](../examples/address_lookup/) + +## 📚 External Resources + +- [Solana Address Lookup Tables Documentation](https://docs.solana.com/developing/lookup-tables) \ No newline at end of file diff --git a/docs/ADDRESS_LOOKUP_TABLE_CN.md b/docs/ADDRESS_LOOKUP_TABLE_CN.md new file mode 100644 index 0000000..4f9ad0f --- /dev/null +++ b/docs/ADDRESS_LOOKUP_TABLE_CN.md @@ -0,0 +1,93 @@ +# 地址查找表指南 + +本指南介绍如何在 Sol Trade SDK 中使用地址查找表 (ALT) 来优化交易大小并降低费用。 + +## 📋 什么是地址查找表? + +地址查找表是 Solana 的一项功能,允许您将经常使用的地址以紧凑的表格格式存储。您可以通过查找表中的索引来引用地址,而不是在交易中包含完整的 32 字节地址,从而显著减少交易大小和成本。 + +## 🚀 核心优势 + +- **交易大小优化**: 使用地址索引而非完整地址来减少交易大小 +- **成本降低**: 由于交易大小减小而降低交易费用 +- **性能提升**: 更快的交易处理和验证速度 +- **网络效率**: 减少带宽使用和区块空间消耗 + +## 🛠️ 实现方法 + +### 1. 设置地址查找表缓存 + +SDK 提供了一个全局缓存来管理地址查找表: + +```rust +use sol_trade_sdk::common::address_lookup_cache::AddressLookupTableCache; +use solana_sdk::pubkey::Pubkey; +use std::str::FromStr; + +/// 设置查找表缓存 +async fn setup_lookup_table_cache( + client: Arc, + lookup_table_address: Pubkey, +) -> AnyResult<()> { + AddressLookupTableCache::get_instance() + .set_address_lookup_table(client, &lookup_table_address) + .await + .map_err(|e| anyhow::anyhow!("Failed to set address lookup table: {}", e))?; + Ok(()) +} +``` + +### 2. 在交易参数中使用查找表 + +在您的交易参数中包含查找表: + +```rust +// 初始化查找表 +let lookup_table_key = Pubkey::from_str("your_lookup_table_address_here").unwrap(); +setup_lookup_table_cache(client.rpc.clone(), lookup_table_key).await?; + +// 在交易参数中包含查找表 +let buy_params = sol_trade_sdk::TradeBuyParams { + dex_type: DexType::PumpFun, + mint: mint_pubkey, + sol_amount: buy_sol_amount, + slippage_basis_points: Some(100), + recent_blockhash: recent_blockhash, + extension_params: Box::new(PumpFunParams::from_trade(&trade_info, None)), + lookup_table_key: Some(lookup_table_key), // 包含查找表 + wait_transaction_confirmed: true, + create_wsol_ata: false, + close_wsol_ata: false, + create_mint_ata: true, + open_seed_optimize: false, +}; + +// 执行交易 +client.buy(buy_params).await?; +``` + +## 📊 性能对比 + +| 方面 | 不使用 ALT | 使用 ALT | 改进幅度 | +|------|-----------|----------|----------| +| **交易大小** | ~1,232 字节 | ~800 字节 | 减少 35% | +| **地址存储** | 每个地址 32 字节 | 每个地址 1 字节 | 减少 97% | +| **交易费用** | 更高 | 更低 | 节省高达 30% | +| **区块空间使用** | 更多 | 更少 | 提高网络效率 | + +## ⚠️ 重要注意事项 + +1. **查找表地址**: 必须提供有效的地址查找表地址 +2. **缓存管理**: SDK 自动管理查找表缓存 +3. **RPC 兼容性**: 确保您的 RPC 提供商支持查找表 +4. **网络**: 查找表是特定于网络的(主网/开发网/测试网) +5. **测试**: 在主网使用前请务必在开发网测试 + +## 🔗 相关文档 + +- [交易参数参考手册](TRADING_PARAMETERS_CN.md) +- [示例:地址查找表](../examples/address_lookup/) + +## 📚 外部资源 + +- [Solana 地址查找表文档](https://docs.solana.com/developing/lookup-tables) \ No newline at end of file diff --git a/docs/GAS_FEE_STRATEGY.md b/docs/GAS_FEE_STRATEGY.md new file mode 100644 index 0000000..4ceeec4 --- /dev/null +++ b/docs/GAS_FEE_STRATEGY.md @@ -0,0 +1,70 @@ +# 📊 Gas Fee Strategy Guide + +This document introduces the Gas Fee strategy configuration and usage methods in Sol Trade SDK. + +## Basic Usage + +### 1. Overview + +This module supports users to configure strategies for SwqosType under different TradeType (buy/sell). + +- Normal strategy: One SwqosType sends one transaction, specifying cu_limit, cu_price, and tip. +- High-low fee strategy: One SwqosType sends two transactions simultaneously, one with low tip and high priority fee, another with high tip and low priority fee. + +Each (SwqosType, TradeType) combination can only configure one strategy. Subsequent strategy configurations will override previous ones. + +### 2. Set Global Strategy (can also be configured individually) + +```rust +use sol_trade_sdk::common::{gas_fee_strategy::GasFeeStrategy}; +// Set global strategy (normal strategy) +GasFeeStrategy::set_global_fee_strategy( + 150000, // cu_limit + 500000, // cu_price + 0.001, // buy tip + 0.001 // sell tip +); +``` + +### 3. Configuring Single Strategy + +```rust +// Configure normal strategy for SwqosType::Jito during Buy +GasFeeStrategy::add_normal_fee_strategy( + SwqosType::Jito, + TradeType::Buy, + xxxxx, // cu_limit + xxxx, // cu_price + xxxxx // tip +); +``` + +### 4. Configuring High-Low Fee Strategy + +```rust +// Configure high-low fee strategy for SwqosType::Jito during Buy +GasFeeStrategy::add_high_low_fee_strategy( + SwqosType::Jito, + TradeType::Buy, + xxxxx, // cu_limit + xxxxx, // low cu_price + xxxxx, // high cu_price + xxxxx, // low tip + xxxxx // high tip +); +``` + +### 5. Viewing and Cleanup + +```rust +// Remove a specific strategy +GasFeeStrategy::remove_strategy(SwqosType::Jito, TradeType::Buy); +// View all strategies +GasFeeStrategy::print_all_strategies(); +// Clear all strategies +GasFeeStrategy::clear(); +``` + +## 🔗 Related Documents + +- [Example: Gas Fee Strategy](../examples/gas_fee_strategy/) diff --git a/docs/GAS_FEE_STRATEGY_CN.md b/docs/GAS_FEE_STRATEGY_CN.md new file mode 100644 index 0000000..001ff51 --- /dev/null +++ b/docs/GAS_FEE_STRATEGY_CN.md @@ -0,0 +1,70 @@ +# 📊 Gas Fee 策略指南 + +本文档介绍 Sol Trade SDK 中的 Gas Fee 策略配置和使用方法。 + +## 基础使用 + +### 1. 说明 + +该模块支持用户配置 SwqosType 在不同 TradeType(buy/sell) 下的策略。 + +- normal 策略: 一个 SwqosType 发送一笔交易,指定 cu_limit、cu_price 和小费。 +- 高低费率策略: 一个 SwqosType 同时发送两笔交易,一笔低小费高优先费,一笔高小费低优先费。 + +每个 (SwqosType, TradeType) 的组合仅可配置一个策略。后续配置的策略会覆盖之前的策略。 + +### 2. 设置全局策略(也可以不设置,单独去配置单个策略) + +```rust +use sol_trade_sdk::common::{gas_fee_strategy::GasFeeStrategy}; +// 设置全局策略(normal 策略) +GasFeeStrategy::set_global_fee_strategy( + 150000, // cu_limit + 500000, // cu_price + 0.001, // buy tip + 0.001 // sell tip +); +``` + +### 3. 配置单个策略 + +```rust +// 为 SwqosType::Jito 在 Buy 时配置 normal 策略 +GasFeeStrategy::add_normal_fee_strategy( + SwqosType::Jito, + TradeType::Buy, + xxxxx, // cu_limit + xxxx, // cu_price + xxxxx // tip +); +``` + +### 4. 配置高低费率策略 + +```rust +// 为 SwqosType::Jito 在 Buy 时配置高低费率策略 +GasFeeStrategy::add_high_low_fee_strategy( + SwqosType::Jito, + TradeType::Buy, + xxxxx, // cu_limit + xxxxx, // low cu_price + xxxxx, // high cu_price + xxxxx, // low tip + xxxxx // high tip +); +``` + +### 5. 查看和清理 + +```rust +// 移除某个策略 +GasFeeStrategy::remove_strategy(SwqosType::Jito, TradeType::Buy); +// 查看所有策略 +GasFeeStrategy::print_all_strategies(); +// 清空所有策略 +GasFeeStrategy::clear(); +``` + +## 🔗 相关文档 + +- [示例:Gas Fee 策略](../examples/gas_fee_strategy/) diff --git a/docs/NONCE_CACHE.md b/docs/NONCE_CACHE.md new file mode 100644 index 0000000..6e14ece --- /dev/null +++ b/docs/NONCE_CACHE.md @@ -0,0 +1,86 @@ +# Nonce Cache Guide + +This guide explains how to use Nonce Cache in Sol Trade SDK to implement transaction replay protection and optimize transaction processing. + +## 📋 What is Nonce Cache? + +Nonce Cache is a global singleton cache system for managing durable nonce accounts in the Solana network. Durable nonce is a Solana feature that allows you to create transactions that remain valid for extended periods, beyond the 150-block limitation of recent block hashes. + +## 🚀 Core Benefits + +- **Transaction Replay Protection**: Prevents identical transactions from being executed multiple times +- **Extended Time Window**: Transactions can remain valid for longer periods +- **Network Performance Optimization**: Reduces dependency on the latest block hash +- **Transaction Determinism**: Provides consistent transaction processing experience +- **Offline Transaction Support**: Supports offline processing of pre-signed transactions + +## 🛠️ Implementation + +### Prerequisites: + +You need to create a nonce account for your payer account first. +Reference: https://solana.com/developers/guides/advanced/introduction-to-durable-nonces + +### 1. Initialize Nonce Cache + +First, set up the nonce account and initialize the cache: + +```rust +use sol_trade_sdk::common::nonce_cache::NonceCache; + +// Set up nonce account +let nonce_account_str = "your_nonce_account_address_here"; +NonceCache::get_instance().init(Some(nonce_account_str.to_string())); +``` + +### 2. Fetch Nonce Information + +Get the latest nonce information from RPC: + +```rust +// Fetch and update nonce information +NonceCache::get_instance().fetch_nonce_info_use_rpc(&client.rpc).await?; +// Or manually manage nonce +// NonceCache::get_instance().update_nonce_info_partial(nonce_account, current_nonce, used); +let nonce_info = NonceCache::get_instance().get_nonce_info(); +let current_nonce = nonce_info.current_nonce; +let nonce_account = nonce_info.nonce_account; +println!("Current nonce: {}", current_nonce); +``` + +### 3. Use Nonce in Transactions + +Set nonce parameters: nonce_account and current_nonce + +```rust +let buy_params = sol_trade_sdk::TradeBuyParams { + dex_type: DexType::PumpFun, + mint: mint_pubkey, + sol_amount: buy_sol_amount, + slippage_basis_points: Some(100), + recent_blockhash: recent_blockhash, + extension_params: Box::new(PumpFunParams::from_trade(&trade_info, None)), + lookup_table_key: None, + wait_transaction_confirmed: true, + create_wsol_ata: false, + close_wsol_ata: false, + create_mint_ata: true, + open_seed_optimize: false, + nonce_account: nonce_account, // Set nonce account + current_nonce: Some(current_nonce), // Set nonce value +}; + +// Execute transaction +client.buy(buy_params).await?; +``` + +## 🔄 Nonce Lifecycle + +1. **Initialize**: Set nonce account address +2. **Fetch**: Get the latest nonce value from RPC +3. **Use**: Set nonce parameters in transactions +4. **Refresh**: Fetch new nonce value before next use + +## 🔗 Related Documentation + +- [Example: Nonce Cache](../examples/nonce_cache/) diff --git a/docs/NONCE_CACHE_CN.md b/docs/NONCE_CACHE_CN.md new file mode 100644 index 0000000..be1d7aa --- /dev/null +++ b/docs/NONCE_CACHE_CN.md @@ -0,0 +1,86 @@ +# Nonce 缓存指南 + +本指南介绍如何在 Sol Trade SDK 中使用 Nonce 缓存来实现交易重放保护和优化交易处理。 + +## 📋 什么是 Nonce 缓存? + +Nonce 缓存是一个全局单例模式的缓存系统,用于管理 Solana 网络中的 durable nonce 账户。Durable nonce 是 Solana 的一项功能,允许您创建在较长时间内有效的交易,而不受最近区块哈希的 150 个区块限制。 + +## 🚀 核心优势 + +- **交易重放保护**: 防止相同交易被重复执行 +- **时间窗口扩展**: 交易可在更长时间内保持有效 +- **网络性能优化**: 减少对最新区块哈希的依赖 +- **交易确定性**: 提供一致的交易处理体验 +- **离线交易支持**: 支持预签名交易的离线处理 + +## 🛠️ 实现方法 + +### 前提: + +需要先创建你 payer 账号使用的 nonce 账户。 +参考资料: https://solana.com/zh/developers/guides/advanced/introduction-to-durable-nonces + +### 1. 初始化 Nonce 缓存 + +首先需要设置 nonce 账户并初始化缓存: + +```rust +use sol_trade_sdk::common::nonce_cache::NonceCache; + +// 设置 nonce 账户 +let nonce_account_str = "your_nonce_account_address_here"; +NonceCache::get_instance().init(Some(nonce_account_str.to_string())); +``` + +### 2. 获取 Nonce 信息 + +从 RPC 获取最新的 nonce 信息: + +```rust +// 获取并更新 nonce 信息 +NonceCache::get_instance().fetch_nonce_info_use_rpc(&client.rpc).await?; +// 或者手动管理nonce +// NonceCache::get_instance().update_nonce_info_partial(nonce_account, current_nonce, used); +let nonce_info = NonceCache::get_instance().get_nonce_info(); +let current_nonce = nonce_info.current_nonce; +let nonce_account = nonce_info.nonce_account; +println!("Current nonce: {}", current_nonce); +``` + +### 3. 在交易中使用 Nonce + +设置 nonce 参数:nonce_account 和 recent_nonce + +```rust +let buy_params = sol_trade_sdk::TradeBuyParams { + dex_type: DexType::PumpFun, + mint: mint_pubkey, + sol_amount: buy_sol_amount, + slippage_basis_points: Some(100), + recent_blockhash: recent_blockhash, + extension_params: Box::new(PumpFunParams::from_trade(&trade_info, None)), + lookup_table_key: None, + wait_transaction_confirmed: true, + create_wsol_ata: false, + close_wsol_ata: false, + create_mint_ata: true, + open_seed_optimize: false, + nonce_account: nonce_account, // 设置 nonce 账户 + current_nonce: Some(current_nonce), // 设置 nonce 值 +}; + +// 执行交易 +client.buy(buy_params).await?; +``` + +## 🔄 Nonce 生命周期 + +1. **初始化**: 设置 nonce 账户地址 +2. **获取**: 从 RPC 获取最新 nonce 值 +3. **使用**: 在交易中设置 nonce 参数 +4. **刷新**: 下次使用前重新获取新的 nonce 值 + +## 🔗 相关文档 + +- [示例:Nonce 缓存](../examples/nonce_cache/) diff --git a/docs/TRADING_PARAMETERS.md b/docs/TRADING_PARAMETERS.md new file mode 100644 index 0000000..86be6d3 --- /dev/null +++ b/docs/TRADING_PARAMETERS.md @@ -0,0 +1,149 @@ +# 📋 Trading Parameters Reference + +This document provides a comprehensive reference for all trading parameters used in the Sol Trade SDK. + +## 📋 Table of Contents + +- [TradeBuyParams](#tradebuyparams) +- [TradeSellParams](#tradesellparams) +- [Parameter Categories](#parameter-categories) +- [Important Notes](#important-notes) + +## TradeBuyParams + +The `TradeBuyParams` struct contains all parameters required for executing buy orders across different DEX protocols. + +### Basic Trading Parameters + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `dex_type` | `DexType` | ✅ | The trading protocol to use (PumpFun, PumpSwap, Bonk, RaydiumCpmm, RaydiumAmmV4) | +| `mint` | `Pubkey` | ✅ | The public key of the token mint to purchase | +| `sol_amount` | `u64` | ✅ | Amount of SOL to spend (in lamports) | +| `slippage_basis_points` | `Option` | ❌ | Slippage tolerance in basis points (e.g., 100 = 1%, 500 = 5%) | +| `recent_blockhash` | `Hash` | ✅ | Recent blockhash for transaction validity | +| `extension_params` | `Box` | ✅ | Protocol-specific parameters (PumpFunParams, PumpSwapParams, etc.) | + +### Advanced Configuration Parameters + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `lookup_table_key` | `Option` | ❌ | Address lookup table key for transaction optimization | +| `wait_transaction_confirmed` | `bool` | ✅ | Whether to wait for transaction confirmation | +| `create_wsol_ata` | `bool` | ✅ | Whether to create wSOL Associated Token Account | +| `close_wsol_ata` | `bool` | ✅ | Whether to close wSOL ATA after transaction | +| `create_mint_ata` | `bool` | ✅ | Whether to create token mint ATA | +| `open_seed_optimize` | `bool` | ✅ | Whether to use seed optimization for reduced CU consumption | +| `nonce_account` | `Option` | ❌ | nonce account | +| `current_nonce` | `Option` | ❌ | nonce value | + + +## TradeSellParams + +The `TradeSellParams` struct contains all parameters required for executing sell orders across different DEX protocols. + +### Basic Trading Parameters + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `dex_type` | `DexType` | ✅ | The trading protocol to use (PumpFun, PumpSwap, Bonk, RaydiumCpmm, RaydiumAmmV4) | +| `mint` | `Pubkey` | ✅ | The public key of the token mint to sell | +| `token_amount` | `u64` | ✅ | Amount of tokens to sell (in smallest token units) | +| `slippage_basis_points` | `Option` | ❌ | Slippage tolerance in basis points (e.g., 100 = 1%, 500 = 5%) | +| `recent_blockhash` | `Hash` | ✅ | Recent blockhash for transaction validity | +| `with_tip` | `bool` | ✅ | Whether to include tip in the transaction | +| `extension_params` | `Box` | ✅ | Protocol-specific parameters (PumpFunParams, PumpSwapParams, etc.) | + +### Advanced Configuration Parameters + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `lookup_table_key` | `Option` | ❌ | Address lookup table key for transaction optimization | +| `wait_transaction_confirmed` | `bool` | ✅ | Whether to wait for transaction confirmation | +| `create_wsol_ata` | `bool` | ✅ | Whether to create wSOL Associated Token Account | +| `close_wsol_ata` | `bool` | ✅ | Whether to close wSOL ATA after transaction | +| `open_seed_optimize` | `bool` | ✅ | Whether to use seed optimization for reduced CU consumption | +| `nonce_account` | `Option` | ❌ | nonce account | +| `current_nonce` | `Option` | ❌ | nonce value | + + +## Parameter Categories + +### 🎯 Core Trading Parameters + +These parameters are essential for defining the basic trading operation: + +- **dex_type**: Determines which protocol to use for trading +- **mint**: Specifies the token to trade +- **sol_amount** (buy) / **token_amount** (sell): Defines the trade size +- **recent_blockhash**: Ensures transaction validity + +### ⚙️ Transaction Control Parameters + +These parameters control how the transaction is processed: + +- **slippage_basis_points**: Controls acceptable price slippage +- **wait_transaction_confirmed**: Controls whether to wait for confirmation + +### 🔧 Account Management Parameters + +These parameters control automatic account creation and management: + +- **create_wsol_ata**: Automatically wrap SOL to wSOL when needed +- **close_wsol_ata**: Automatically unwrap wSOL to SOL after trading +- **create_mint_ata**: Automatically create token accounts + +### 🚀 Optimization Parameters + +These parameters enable advanced optimizations: + +- **lookup_table_key**: Use address lookup tables for reduced transaction size +- **open_seed_optimize**: Use seed-based account creation for lower CU consumption + +### 🔄 Optional Parameters + +When you need to use durable nonce, you need to fill in these two parameters: +- **nonce_account**: nonce account +- **current_nonce**: nonce value + +## Important Notes + +### 🌱 Seed Optimization + +When `open_seed_optimize: true`: +- ⚠️ **Warning**: Tokens purchased with seed optimization must be sold through this SDK +- ⚠️ **Warning**: Official platform selling methods may fail +- 📝 **Note**: Use `get_associated_token_address_with_program_id_fast_use_seed` to get ATA addresses + +### 💰 wSOL Account Management + +The `create_wsol_ata` and `close_wsol_ata` parameters provide granular control: + +- **Independent Control**: Create and close operations can be controlled separately +- **Batch Operations**: Create once, trade multiple times, then close +- **Rent Optimization**: Automatic rent reclamation when closing accounts + +### 🔍 Address Lookup Tables + +Before using `lookup_table_key`: +- Initialize `AddressLookupTableCache` to manage cached lookup tables +- Lookup tables reduce transaction size and improve success rates +- Particularly beneficial for complex transactions with many account references + +### 📊 Slippage Configuration + +Recommended slippage settings: +- **Conservative**: 100-300 basis points (1-3%) +- **Moderate**: 300-500 basis points (3-5%) +- **Aggressive**: 500-1000 basis points (5-10%) + +### 🎯 Protocol-Specific Parameters + +Each DEX protocol requires specific `extension_params`: +- **PumpFun**: `PumpFunParams` +- **PumpSwap**: `PumpSwapParams` +- **Bonk**: `BonkParams` +- **Raydium CPMM**: `RaydiumCpmmParams` +- **Raydium AMM V4**: `RaydiumAmmV4Params` + +Refer to the respective protocol documentation for detailed parameter specifications. diff --git a/docs/TRADING_PARAMETERS_CN.md b/docs/TRADING_PARAMETERS_CN.md new file mode 100644 index 0000000..ab45e8e --- /dev/null +++ b/docs/TRADING_PARAMETERS_CN.md @@ -0,0 +1,149 @@ +# 📋 交易参数参考手册 + +本文档提供 Sol Trade SDK 中所有交易参数的完整参考说明。 + +## 📋 目录 + +- [TradeBuyParams](#tradebuyparams) +- [TradeSellParams](#tradesellparams) +- [参数分类](#参数分类) +- [重要说明](#重要说明) + +## TradeBuyParams + +`TradeBuyParams` 结构体包含在不同 DEX 协议上执行买入订单所需的所有参数。 + +### 基础交易参数 + +| 参数 | 类型 | 必需 | 描述 | +|------|------|------|------| +| `dex_type` | `DexType` | ✅ | 要使用的交易协议 (PumpFun, PumpSwap, Bonk, RaydiumCpmm, RaydiumAmmV4) | +| `mint` | `Pubkey` | ✅ | 要购买的代币 mint 公钥 | +| `sol_amount` | `u64` | ✅ | 要花费的 SOL 数量(以 lamports 为单位) | +| `slippage_basis_points` | `Option` | ❌ | 滑点容忍度(基点单位,例如 100 = 1%, 500 = 5%) | +| `recent_blockhash` | `Hash` | ✅ | 用于交易有效性的最新区块哈希 | +| `extension_params` | `Box` | ✅ | 协议特定参数 (PumpFunParams, PumpSwapParams 等) | + +### 高级配置参数 + +| 参数 | 类型 | 必需 | 描述 | +|------|------|------|------| +| `lookup_table_key` | `Option` | ❌ | 用于交易优化的地址查找表键 | +| `wait_transaction_confirmed` | `bool` | ✅ | 是否等待交易确认 | +| `create_wsol_ata` | `bool` | ✅ | 是否创建 wSOL 关联代币账户 | +| `close_wsol_ata` | `bool` | ✅ | 交易后是否关闭 wSOL ATA | +| `create_mint_ata` | `bool` | ✅ | 是否创建代币 mint ATA | +| `open_seed_optimize` | `bool` | ✅ | 是否使用 seed 优化以减少 CU 消耗 | +| `nonce_account` | `Option` | ❌ | nonce 账户 | +| `current_nonce` | `Option` | ❌ | nonce 值 | + + +## TradeSellParams + +`TradeSellParams` 结构体包含在不同 DEX 协议上执行卖出订单所需的所有参数。 + +### 基础交易参数 + +| 参数 | 类型 | 必需 | 描述 | +|------|------|------|------| +| `dex_type` | `DexType` | ✅ | 要使用的交易协议 (PumpFun, PumpSwap, Bonk, RaydiumCpmm, RaydiumAmmV4) | +| `mint` | `Pubkey` | ✅ | 要出售的代币 mint 公钥 | +| `token_amount` | `u64` | ✅ | 要出售的代币数量(最小代币单位) | +| `slippage_basis_points` | `Option` | ❌ | 滑点容忍度(基点单位,例如 100 = 1%, 500 = 5%) | +| `recent_blockhash` | `Hash` | ✅ | 用于交易有效性的最新区块哈希 | +| `with_tip` | `bool` | ✅ | 交易中是否包含小费 | +| `extension_params` | `Box` | ✅ | 协议特定参数 (PumpFunParams, PumpSwapParams 等) | + +### 高级配置参数 + +| 参数 | 类型 | 必需 | 描述 | +|------|------|------|------| +| `lookup_table_key` | `Option` | ❌ | 用于交易优化的地址查找表键 | +| `wait_transaction_confirmed` | `bool` | ✅ | 是否等待交易确认 | +| `create_wsol_ata` | `bool` | ✅ | 是否创建 wSOL 关联代币账户 | +| `close_wsol_ata` | `bool` | ✅ | 交易后是否关闭 wSOL ATA | +| `open_seed_optimize` | `bool` | ✅ | 是否使用 seed 优化以减少 CU 消耗 | +| `nonce_account` | `Option` | ❌ | nonce 账户 | +| `current_nonce` | `Option` | ❌ | nonce 值 | + + +## 参数分类 + +### 🎯 核心交易参数 + +这些参数对于定义基本交易操作至关重要: + +- **dex_type**: 确定用于交易的协议 +- **mint**: 指定要交易的代币 +- **sol_amount** (买入) / **token_amount** (卖出): 定义交易规模 +- **recent_blockhash**: 确保交易有效性 + +### ⚙️ 交易控制参数 + +这些参数控制交易的处理方式: + +- **slippage_basis_points**: 控制可接受的价格滑点 +- **wait_transaction_confirmed**: 控制是否等待确认 + +### 🔧 账户管理参数 + +这些参数控制自动账户创建和管理: + +- **create_wsol_ata**: 需要时自动将 SOL 包装为 wSOL +- **close_wsol_ata**: 交易后自动将 wSOL 解包装为 SOL +- **create_mint_ata**: 自动创建代币账户 + +### 🚀 优化参数 + +这些参数启用高级优化: + +- **lookup_table_key**: 使用地址查找表减少交易大小 +- **open_seed_optimize**: 使用基于 seed 的账户创建以降低 CU 消耗 + +### 🔄 非必填参数 + +当你需要使用 durable nonce 时,需要填入这两个参数: +- **nonce_account**: nonce 账户 +- **current_nonce**: nonce 值 + +## 重要说明 + +### 🌱 Seed 优化 + +当 `open_seed_optimize: true` 时: +- ⚠️ **警告**: 使用 seed 优化购买的代币必须通过此 SDK 出售 +- ⚠️ **警告**: 官方平台的出售方法可能会失败 +- 📝 **注意**: 使用 `get_associated_token_address_with_program_id_fast_use_seed` 获取 ATA 地址 + +### 💰 wSOL 账户管理 + +`create_wsol_ata` 和 `close_wsol_ata` 参数提供精细控制: + +- **独立控制**: 创建和关闭操作可以分别控制 +- **批量操作**: 创建一次,多次交易,然后关闭 +- **租金优化**: 关闭账户时自动回收租金 + +### 🔍 地址查找表 + +使用 `lookup_table_key` 之前: +- 初始化 `AddressLookupTableCache` 来管理缓存的查找表 +- 查找表减少交易大小并提高成功率 +- 对于有许多账户引用的复杂交易特别有益 + +### 📊 滑点配置 + +推荐的滑点设置: +- **保守**: 100-300 基点 (1-3%) +- **中等**: 300-500 基点 (3-5%) +- **激进**: 500-1000 基点 (5-10%) + +### 🎯 协议特定参数 + +每个 DEX 协议需要特定的 `extension_params`: +- **PumpFun**: `PumpFunParams` +- **PumpSwap**: `PumpSwapParams` +- **Bonk**: `BonkParams` +- **Raydium CPMM**: `RaydiumCpmmParams` +- **Raydium AMM V4**: `RaydiumAmmV4Params` + +请参阅相应的协议文档了解详细的参数规格。 diff --git a/examples/address_lookup/src/main.rs b/examples/address_lookup/src/main.rs index ef287cd..35fb1a9 100644 --- a/examples/address_lookup/src/main.rs +++ b/examples/address_lookup/src/main.rs @@ -6,7 +6,6 @@ use std::{ }, }; -use sol_trade_sdk::solana_streamer_sdk::match_event; use sol_trade_sdk::solana_streamer_sdk::streaming::event_parser::protocols::pumpfun::parser::PUMPFUN_PROGRAM_ID; use sol_trade_sdk::solana_streamer_sdk::streaming::event_parser::protocols::pumpfun::PumpFunTradeEvent; use sol_trade_sdk::solana_streamer_sdk::streaming::event_parser::{Protocol, UnifiedEvent}; @@ -19,15 +18,16 @@ use sol_trade_sdk::{ solana_streamer_sdk::streaming::event_parser::common::EventType, }; use sol_trade_sdk::{ - common::SolanaRpcClient, - solana_streamer_sdk::streaming::event_parser::common::filter::EventTypeFilter, -}; -use sol_trade_sdk::{ - common::{AnyResult, PriorityFee, TradeConfig}, + common::AnyResult, swqos::SwqosConfig, trading::{core::params::PumpFunParams, factory::DexType}, SolanaTrade, }; +use sol_trade_sdk::{ + common::SolanaRpcClient, + solana_streamer_sdk::streaming::event_parser::common::filter::EventTypeFilter, +}; +use sol_trade_sdk::{common::TradeConfig, solana_streamer_sdk::match_event}; use solana_sdk::pubkey::Pubkey; use solana_sdk::{commitment_config::CommitmentConfig, signature::Keypair}; @@ -117,28 +117,17 @@ async fn setup_lookup_table_cache( /// Create SolanaTrade client /// Initializes a new SolanaTrade client with configuration async fn create_solana_trade_client() -> AnyResult { - println!("Creating SolanaTrade client..."); - + 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 swqos_configs = vec![SwqosConfig::Default(rpc_url.clone())]; - - let mut priority_fee = PriorityFee::default(); - // Configure according to your needs - priority_fee.rpc_unit_limit = 100000; - - 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) + let commitment = CommitmentConfig::confirmed(); + let swqos_configs: Vec = vec![SwqosConfig::Default(rpc_url.clone())]; + let trade_config = TradeConfig::new(rpc_url, swqos_configs, commitment); + let solana_trade = SolanaTrade::new(Arc::new(payer), trade_config).await; + // set global strategy + sol_trade_sdk::common::GasFeeStrategy::set_global_fee_strategy(150000, 500000, 0.001, 0.001); + println!("✅ SolanaTrade client initialized successfully!"); + Ok(solana_trade) } /// PumpFun sniper trade @@ -158,23 +147,23 @@ async fn pumpfun_copy_trade_with_grpc(trade_info: PumpFunTradeEvent) -> AnyResul // 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)), - Some(lookup_table_key), // you still need to update the AddressLookupTableCache - true, - false, - false, - true, - false, - ) - .await?; + let buy_params = sol_trade_sdk::TradeBuyParams { + dex_type: DexType::PumpFun, + mint: mint_pubkey, + sol_amount: buy_sol_amount, + slippage_basis_points: slippage_basis_points, + recent_blockhash: recent_blockhash, + extension_params: Box::new(PumpFunParams::from_trade(&trade_info, None)), + lookup_table_key: Some(lookup_table_key), // you still need to update the AddressLookupTableCache + wait_transaction_confirmed: true, + create_wsol_ata: false, + close_wsol_ata: false, + create_mint_ata: true, + open_seed_optimize: false, + nonce_account: None, + current_nonce: None, + }; + client.buy(buy_params).await?; // Exit program std::process::exit(0); diff --git a/examples/bonk_copy_trading/src/main.rs b/examples/bonk_copy_trading/src/main.rs index 31274b0..9efd22b 100644 --- a/examples/bonk_copy_trading/src/main.rs +++ b/examples/bonk_copy_trading/src/main.rs @@ -3,8 +3,6 @@ use std::sync::{ Arc, }; -use sol_trade_sdk::solana_streamer_sdk::match_event; -use sol_trade_sdk::solana_streamer_sdk::streaming::event_parser::common::filter::EventTypeFilter; use sol_trade_sdk::solana_streamer_sdk::streaming::event_parser::common::EventType; use sol_trade_sdk::solana_streamer_sdk::streaming::event_parser::protocols::bonk::parser::BONK_PROGRAM_ID; use sol_trade_sdk::solana_streamer_sdk::streaming::event_parser::protocols::bonk::BonkTradeEvent; @@ -14,11 +12,16 @@ use sol_trade_sdk::solana_streamer_sdk::streaming::yellowstone_grpc::{ }; use sol_trade_sdk::solana_streamer_sdk::streaming::YellowstoneGrpc; use sol_trade_sdk::{ - common::{AnyResult, PriorityFee, TradeConfig}, + common::AnyResult, swqos::SwqosConfig, trading::{core::params::BonkParams, factory::DexType}, SolanaTrade, }; +use sol_trade_sdk::{common::TradeConfig, solana_streamer_sdk::match_event}; +use sol_trade_sdk::{ + constants::WSOL_TOKEN_ACCOUNT, + solana_streamer_sdk::streaming::event_parser::common::filter::EventTypeFilter, +}; use solana_sdk::signer::Signer; use solana_sdk::{commitment_config::CommitmentConfig, signature::Keypair}; use spl_associated_token_account::get_associated_token_address; @@ -85,6 +88,9 @@ fn create_event_callback() -> impl Fn(Box) { |event: Box| { match_event!(event, { BonkTradeEvent => |e: BonkTradeEvent| { + if e.base_token_mint != WSOL_TOKEN_ACCOUNT && e.quote_token_mint != WSOL_TOKEN_ACCOUNT { + return; + } // Test code, only test one transaction if !ALREADY_EXECUTED.swap(true, Ordering::SeqCst) { let event_clone = e.clone(); @@ -103,28 +109,17 @@ fn create_event_callback() -> impl Fn(Box) { /// Create SolanaTrade client /// Initializes a new SolanaTrade client with configuration async fn create_solana_trade_client() -> AnyResult { - println!("Creating SolanaTrade client..."); - + 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 swqos_configs = vec![SwqosConfig::Default(rpc_url.clone())]; - - let mut priority_fee = PriorityFee::default(); - // 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) + let commitment = CommitmentConfig::confirmed(); + let swqos_configs: Vec = vec![SwqosConfig::Default(rpc_url.clone())]; + let trade_config = TradeConfig::new(rpc_url, swqos_configs, commitment); + let solana_trade = SolanaTrade::new(Arc::new(payer), trade_config).await; + // set global strategy + sol_trade_sdk::common::GasFeeStrategy::set_global_fee_strategy(150000, 500000, 0.001, 0.001); + println!("✅ SolanaTrade client initialized successfully!"); + Ok(solana_trade) } /// Bonk sniper trade @@ -140,23 +135,23 @@ async fn bonk_copy_trade_with_grpc(trade_info: BonkTradeEvent) -> AnyResult<()> // Buy tokens println!("Buying tokens from Bonk..."); let buy_sol_amount = 100_000; - client - .buy( - DexType::Bonk, - mint_pubkey, - buy_sol_amount, - slippage_basis_points, - recent_blockhash, - None, - Box::new(BonkParams::from_trade(trade_info.clone())), - None, - true, - true, - true, - true, - false, - ) - .await?; + let buy_params = sol_trade_sdk::TradeBuyParams { + dex_type: DexType::Bonk, + mint: mint_pubkey, + sol_amount: buy_sol_amount, + slippage_basis_points: slippage_basis_points, + recent_blockhash: recent_blockhash, + extension_params: Box::new(BonkParams::from_trade(trade_info.clone())), + lookup_table_key: None, + wait_transaction_confirmed: true, + create_wsol_ata: true, + close_wsol_ata: true, + create_mint_ata: true, + open_seed_optimize: false, + nonce_account: None, + current_nonce: None, + }; + client.buy(buy_params).await?; // Sell tokens println!("Selling tokens from Bonk..."); @@ -169,23 +164,23 @@ async fn bonk_copy_trade_with_grpc(trade_info: BonkTradeEvent) -> AnyResult<()> let amount_token = balance.amount.parse::().unwrap(); println!("Selling {} tokens", amount_token); - client - .sell( - DexType::Bonk, - mint_pubkey, - amount_token, - slippage_basis_points, - recent_blockhash, - None, - false, - Box::new(BonkParams::from_trade(trade_info.clone())), - None, - true, - true, - true, - false, - ) - .await?; + let sell_params = sol_trade_sdk::TradeSellParams { + dex_type: DexType::Bonk, + mint: mint_pubkey, + token_amount: amount_token, + slippage_basis_points: slippage_basis_points, + recent_blockhash: recent_blockhash, + extension_params: Box::new(BonkParams::from_trade(trade_info.clone())), + lookup_table_key: None, + wait_transaction_confirmed: true, + create_wsol_ata: true, + close_wsol_ata: true, + open_seed_optimize: false, + with_tip: false, + nonce_account: None, + current_nonce: None, + }; + client.sell(sell_params).await?; // Exit program std::process::exit(0); diff --git a/examples/bonk_sniper_trading/src/main.rs b/examples/bonk_sniper_trading/src/main.rs index 1d4cc42..a197988 100644 --- a/examples/bonk_sniper_trading/src/main.rs +++ b/examples/bonk_sniper_trading/src/main.rs @@ -1,11 +1,11 @@ +use sol_trade_sdk::common::TradeConfig; use sol_trade_sdk::solana_streamer_sdk::streaming::event_parser::common::filter::EventTypeFilter; use sol_trade_sdk::solana_streamer_sdk::streaming::event_parser::common::EventType; use sol_trade_sdk::solana_streamer_sdk::streaming::event_parser::protocols::bonk::BonkTradeEvent; use sol_trade_sdk::solana_streamer_sdk::streaming::event_parser::{Protocol, UnifiedEvent}; -use sol_trade_sdk::solana_streamer_sdk::streaming::grpc::ClientConfig; use sol_trade_sdk::solana_streamer_sdk::{match_event, streaming::ShredStreamGrpc}; use sol_trade_sdk::{ - common::{AnyResult, PriorityFee, TradeConfig}, + common::AnyResult, swqos::SwqosConfig, trading::{core::params::BonkParams, factory::DexType}, SolanaTrade, @@ -72,28 +72,17 @@ fn create_event_callback() -> impl Fn(Box) { /// Create SolanaTrade client /// Initializes a new SolanaTrade client with configuration async fn create_solana_trade_client() -> AnyResult { - println!("Creating SolanaTrade client..."); - + 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 swqos_configs = vec![SwqosConfig::Default(rpc_url.clone())]; - - let mut priority_fee = PriorityFee::default(); - // Set RPC unit limit based on your requirements - 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) + let commitment = CommitmentConfig::confirmed(); + let swqos_configs: Vec = vec![SwqosConfig::Default(rpc_url.clone())]; + let trade_config = TradeConfig::new(rpc_url, swqos_configs, commitment); + let solana_trade = SolanaTrade::new(Arc::new(payer), trade_config).await; + // set global strategy + sol_trade_sdk::common::GasFeeStrategy::set_global_fee_strategy(150000, 500000, 0.001, 0.001); + println!("✅ SolanaTrade client initialized successfully!"); + Ok(solana_trade) } /// Execute Bonk sniper trading strategy based on received token creation event @@ -109,23 +98,23 @@ async fn bonk_sniper_trade_with_shreds(trade_info: BonkTradeEvent) -> AnyResult< // Buy tokens println!("Buying tokens from Bonk..."); let buy_sol_amount = 100_000; - client - .buy( - DexType::Bonk, - mint_pubkey, - buy_sol_amount, - slippage_basis_points, - recent_blockhash, - None, - Box::new(BonkParams::from_dev_trade(trade_info.clone())), - None, - true, - true, - true, - true, - false, - ) - .await?; + let buy_params = sol_trade_sdk::TradeBuyParams { + dex_type: DexType::Bonk, + mint: mint_pubkey, + sol_amount: buy_sol_amount, + slippage_basis_points: slippage_basis_points, + recent_blockhash: recent_blockhash, + extension_params: Box::new(BonkParams::from_dev_trade(trade_info.clone())), + lookup_table_key: None, + wait_transaction_confirmed: true, + create_wsol_ata: true, + close_wsol_ata: true, + create_mint_ata: true, + open_seed_optimize: false, + nonce_account: None, + current_nonce: None, + }; + client.buy(buy_params).await?; // Sell tokens println!("Selling tokens from Bonk..."); @@ -138,28 +127,28 @@ async fn bonk_sniper_trade_with_shreds(trade_info: BonkTradeEvent) -> AnyResult< let amount_token = balance.amount.parse::().unwrap(); println!("Selling {} tokens", amount_token); - client - .sell( - DexType::Bonk, - mint_pubkey, - amount_token, - slippage_basis_points, - recent_blockhash, - None, - false, - Box::new(BonkParams::immediate_sell( - trade_info.base_token_program, - trade_info.platform_config, - trade_info.platform_associated_account, - trade_info.creator_associated_account, - )), - None, - true, - true, - true, - false, - ) - .await?; + let sell_params = sol_trade_sdk::TradeSellParams { + dex_type: DexType::Bonk, + mint: mint_pubkey, + token_amount: amount_token, + slippage_basis_points: slippage_basis_points, + recent_blockhash: recent_blockhash, + extension_params: Box::new(BonkParams::immediate_sell( + trade_info.base_token_program, + trade_info.platform_config, + trade_info.platform_associated_account, + trade_info.creator_associated_account, + )), + lookup_table_key: None, + wait_transaction_confirmed: true, + create_wsol_ata: true, + close_wsol_ata: true, + open_seed_optimize: false, + with_tip: false, + nonce_account: None, + current_nonce: None, + }; + client.sell(sell_params).await?; // Exit program after completing the trade std::process::exit(0); diff --git a/examples/cli_trading/src/main.rs b/examples/cli_trading/src/main.rs index 6306d32..cb1aae1 100644 --- a/examples/cli_trading/src/main.rs +++ b/examples/cli_trading/src/main.rs @@ -1,8 +1,7 @@ use clap::Parser; use sol_trade_sdk::{ common::{ - fast_fn::get_associated_token_address_with_program_id_fast_use_seed, AnyResult, - PriorityFee, TradeConfig, + fast_fn::get_associated_token_address_with_program_id_fast_use_seed, AnyResult, TradeConfig, }, swqos::SwqosConfig, trading::{ @@ -11,7 +10,7 @@ use sol_trade_sdk::{ }, factory::DexType, }, - SolanaTrade, + SolanaTrade, TradeBuyParams, TradeSellParams, }; use solana_sdk::{ commitment_config::CommitmentConfig, native_token::sol_str_to_lamports, pubkey::Pubkey, @@ -604,24 +603,23 @@ async fn handle_buy_pumpfun( let recent_blockhash = client.rpc.get_latest_blockhash().await?; let sol_lamports = sol_str_to_lamports(sol_amount.to_string().as_str()).unwrap(); - match client - .buy( - DexType::PumpFun, - mint_pubkey, - sol_lamports, - slippage, - recent_blockhash, - None, - Box::new(param), - None, - true, - false, - false, - create_mint_ata, - use_seed, - ) - .await - { + let buy_params = TradeBuyParams { + dex_type: DexType::PumpFun, + mint: mint_pubkey, + sol_amount: sol_lamports, + slippage_basis_points: slippage, + recent_blockhash: recent_blockhash, + extension_params: Box::new(param), + lookup_table_key: None, + wait_transaction_confirmed: true, + create_wsol_ata: false, + close_wsol_ata: false, + create_mint_ata: create_mint_ata, + open_seed_optimize: use_seed, + nonce_account: None, + current_nonce: None, + }; + match client.buy(buy_params).await { Ok(signature) => { println!(" ✅ Successfully bought tokens from PumpFun!"); println!(" ✅ Transaction Signature: {}", signature); @@ -654,24 +652,23 @@ async fn handle_buy_pumpswap( let recent_blockhash = client.rpc.get_latest_blockhash().await?; let sol_lamports = sol_str_to_lamports(sol_amount.to_string().as_str()).unwrap(); - match client - .buy( - DexType::PumpSwap, - mint_pubkey, - sol_lamports, - slippage, - recent_blockhash, - None, - Box::new(param), - None, - true, - true, - false, - create_mint_ata, - use_seed, - ) - .await - { + let buy_params = TradeBuyParams { + dex_type: DexType::PumpSwap, + mint: mint_pubkey, + sol_amount: sol_lamports, + slippage_basis_points: slippage, + recent_blockhash: recent_blockhash, + extension_params: Box::new(param), + lookup_table_key: None, + wait_transaction_confirmed: true, + create_wsol_ata: true, + close_wsol_ata: false, + create_mint_ata: create_mint_ata, + open_seed_optimize: use_seed, + nonce_account: None, + current_nonce: None, + }; + match client.buy(buy_params).await { Ok(signature) => { println!(" ✅ Successfully bought tokens from PumpSwap!"); println!(" ✅ Transaction Signature: {}", signature); @@ -703,24 +700,23 @@ async fn handle_buy_bonk( let recent_blockhash = client.rpc.get_latest_blockhash().await?; let sol_lamports = sol_str_to_lamports(sol_amount.to_string().as_str()).unwrap(); - match client - .buy( - DexType::Bonk, - mint_pubkey, - sol_lamports, - slippage, - recent_blockhash, - None, - Box::new(param), - None, - true, - true, - false, - create_mint_ata, - use_seed, - ) - .await - { + let buy_params = TradeBuyParams { + dex_type: DexType::Bonk, + mint: mint_pubkey, + sol_amount: sol_lamports, + slippage_basis_points: slippage, + recent_blockhash: recent_blockhash, + extension_params: Box::new(param), + lookup_table_key: None, + wait_transaction_confirmed: true, + create_wsol_ata: true, + close_wsol_ata: false, + create_mint_ata: create_mint_ata, + open_seed_optimize: use_seed, + nonce_account: None, + current_nonce: None, + }; + match client.buy(buy_params).await { Ok(signature) => { println!(" ✅ Successfully bought tokens from Bonk!"); println!(" ✅ Transaction Signature: {}", signature); @@ -756,24 +752,23 @@ async fn handle_buy_raydium_v4( let recent_blockhash = client.rpc.get_latest_blockhash().await?; let sol_lamports = sol_str_to_lamports(sol_amount.to_string().as_str()).unwrap(); - match client - .buy( - DexType::RaydiumAmmV4, - mint_pubkey, - sol_lamports, - slippage, - recent_blockhash, - None, - Box::new(param), - None, - true, - true, - false, - create_mint_ata, - use_seed, - ) - .await - { + let buy_params = TradeBuyParams { + dex_type: DexType::RaydiumAmmV4, + mint: mint_pubkey, + sol_amount: sol_lamports, + slippage_basis_points: slippage, + recent_blockhash: recent_blockhash, + extension_params: Box::new(param), + lookup_table_key: None, + wait_transaction_confirmed: true, + create_wsol_ata: true, + close_wsol_ata: false, + create_mint_ata: create_mint_ata, + open_seed_optimize: use_seed, + nonce_account: None, + current_nonce: None, + }; + match client.buy(buy_params).await { Ok(signature) => { println!(" ✅ Successfully bought tokens from Raydium V4!"); println!(" ✅ Transaction Signature: {}", signature); @@ -809,24 +804,23 @@ async fn handle_buy_raydium_cpmm( let recent_blockhash = client.rpc.get_latest_blockhash().await?; let sol_lamports = sol_str_to_lamports(sol_amount.to_string().as_str()).unwrap(); - match client - .buy( - DexType::RaydiumCpmm, - mint_pubkey, - sol_lamports, - slippage, - recent_blockhash, - None, - Box::new(param), - None, - true, - true, - false, - create_mint_ata, - use_seed, - ) - .await - { + let buy_params = TradeBuyParams { + dex_type: DexType::RaydiumCpmm, + mint: mint_pubkey, + sol_amount: sol_lamports, + slippage_basis_points: slippage, + recent_blockhash: recent_blockhash, + extension_params: Box::new(param), + lookup_table_key: None, + wait_transaction_confirmed: true, + create_wsol_ata: true, + close_wsol_ata: false, + create_mint_ata: create_mint_ata, + open_seed_optimize: use_seed, + nonce_account: None, + current_nonce: None, + }; + match client.buy(buy_params).await { Ok(signature) => { println!(" ✅ Successfully bought tokens from Raydium CPMM!"); println!(" ✅ Transaction Signature: {}", signature); @@ -972,24 +966,24 @@ async fn handle_sell_pumpfun( let param = PumpFunParams::from_mint_by_rpc(&client.rpc, &mint_pubkey).await?; let recent_blockhash = client.rpc.get_latest_blockhash().await?; - match client - .sell( - DexType::PumpFun, - mint_pubkey, - amount as u64, - slippage, - recent_blockhash, - None, - false, - Box::new(param), - None, - true, - true, - false, - use_seed, - ) - .await - { + let sell_params = TradeSellParams { + dex_type: DexType::PumpFun, + mint: mint_pubkey, + token_amount: amount as u64, + slippage_basis_points: slippage, + recent_blockhash: recent_blockhash, + with_tip: false, + extension_params: Box::new(param), + lookup_table_key: None, + wait_transaction_confirmed: true, + create_wsol_ata: true, + close_wsol_ata: false, + open_seed_optimize: use_seed, + nonce_account: None, + current_nonce: None, + }; + + match client.sell(sell_params).await { Ok(signature) => { println!(" ✅ Successfully sold tokens from PumpFun!"); println!(" ✅ Transaction Signature: {}", signature); @@ -1024,24 +1018,23 @@ async fn handle_sell_pumpswap( let param = PumpSwapParams::from_mint_by_rpc(&client.rpc, &mint_pubkey).await?; let recent_blockhash = client.rpc.get_latest_blockhash().await?; - match client - .sell( - DexType::PumpSwap, - mint_pubkey, - amount as u64, - slippage, - recent_blockhash, - None, - false, - Box::new(param), - None, - true, - true, - false, - use_seed, - ) - .await - { + let sell_params = TradeSellParams { + dex_type: DexType::PumpSwap, + mint: mint_pubkey, + token_amount: amount as u64, + slippage_basis_points: slippage, + recent_blockhash: recent_blockhash, + with_tip: false, + extension_params: Box::new(param), + lookup_table_key: None, + wait_transaction_confirmed: true, + create_wsol_ata: true, + close_wsol_ata: false, + open_seed_optimize: use_seed, + nonce_account: None, + current_nonce: None, + }; + match client.sell(sell_params).await { Ok(signature) => { println!(" ✅ Successfully sold tokens from PumpSwap!"); println!(" ✅ Transaction Signature: {}", signature); @@ -1076,24 +1069,23 @@ async fn handle_sell_bonk( let param = BonkParams::from_mint_by_rpc(&client.rpc, &mint_pubkey).await?; let recent_blockhash = client.rpc.get_latest_blockhash().await?; - match client - .sell( - DexType::Bonk, - mint_pubkey, - amount as u64, - slippage, - recent_blockhash, - None, - false, - Box::new(param), - None, - true, - true, - false, - use_seed, - ) - .await - { + let sell_params = TradeSellParams { + dex_type: DexType::Bonk, + mint: mint_pubkey, + token_amount: amount as u64, + slippage_basis_points: slippage, + recent_blockhash: recent_blockhash, + with_tip: false, + extension_params: Box::new(param), + lookup_table_key: None, + wait_transaction_confirmed: true, + create_wsol_ata: true, + close_wsol_ata: false, + open_seed_optimize: use_seed, + nonce_account: None, + current_nonce: None, + }; + match client.sell(sell_params).await { Ok(signature) => { println!(" ✅ Successfully sold tokens from Bonk!"); println!(" ✅ Transaction Signature: {}", signature); @@ -1131,24 +1123,23 @@ async fn handle_sell_raydium_v4( let param = RaydiumAmmV4Params::from_amm_address_by_rpc(&client.rpc, amm_pubkey).await?; let recent_blockhash = client.rpc.get_latest_blockhash().await?; - match client - .sell( - DexType::RaydiumAmmV4, - mint_pubkey, - amount as u64, - slippage, - recent_blockhash, - None, - false, - Box::new(param), - None, - true, - true, - false, - use_seed, - ) - .await - { + let sell_params = TradeSellParams { + dex_type: DexType::RaydiumAmmV4, + mint: mint_pubkey, + token_amount: amount as u64, + slippage_basis_points: slippage, + recent_blockhash: recent_blockhash, + with_tip: false, + extension_params: Box::new(param), + lookup_table_key: None, + wait_transaction_confirmed: true, + create_wsol_ata: true, + close_wsol_ata: false, + open_seed_optimize: use_seed, + nonce_account: None, + current_nonce: None, + }; + match client.sell(sell_params).await { Ok(signature) => { println!(" ✅ Successfully sold tokens from Raydium V4!"); println!(" ✅ Transaction Signature: {}", signature); @@ -1186,24 +1177,23 @@ async fn handle_sell_raydium_cpmm( let param = RaydiumCpmmParams::from_pool_address_by_rpc(&client.rpc, &pool_pubkey).await?; let recent_blockhash = client.rpc.get_latest_blockhash().await?; - match client - .sell( - DexType::RaydiumCpmm, - mint_pubkey, - amount as u64, - slippage, - recent_blockhash, - None, - false, - Box::new(param), - None, - true, - true, - false, - use_seed, - ) - .await - { + let sell_params = TradeSellParams { + dex_type: DexType::RaydiumCpmm, + mint: mint_pubkey, + token_amount: amount as u64, + slippage_basis_points: slippage, + recent_blockhash: recent_blockhash, + with_tip: false, + extension_params: Box::new(param), + lookup_table_key: None, + wait_transaction_confirmed: true, + create_wsol_ata: true, + close_wsol_ata: false, + open_seed_optimize: use_seed, + nonce_account: None, + current_nonce: None, + }; + match client.sell(sell_params).await { Ok(signature) => { println!(" ✅ Successfully sold tokens from Raydium CPMM!"); println!(" ✅ Transaction Signature: {}", signature); @@ -1307,22 +1297,17 @@ async fn handle_wallet() -> Result<(), Box> { // Real implementation functions async fn initialize_real_client() -> AnyResult { // You need to update this with a real RPC URL - - let swqos_configs = vec![SwqosConfig::Default(RPC_URL.to_string())]; - - let mut priority_fee = PriorityFee::default(); - priority_fee.rpc_unit_limit = 200000; - let trade_config = TradeConfig { - rpc_url: RPC_URL.to_string(), - commitment: CommitmentConfig::confirmed(), - priority_fee, - swqos_configs, - }; - - let client = - SolanaTrade::new(Arc::new(Keypair::try_from(&PAYER.to_bytes()[..]).unwrap()), trade_config) - .await; - Ok(client) + println!("🚀 Initializing SolanaTrade client..."); + let payer = Arc::new(Keypair::try_from(&PAYER.to_bytes()[..]).unwrap()); + let rpc_url = RPC_URL.to_string(); + let commitment = CommitmentConfig::confirmed(); + let swqos_configs: Vec = vec![SwqosConfig::Default(rpc_url.clone())]; + let trade_config = TradeConfig::new(rpc_url, swqos_configs, commitment); + let solana_trade = SolanaTrade::new(payer, trade_config).await; + // set global strategy + sol_trade_sdk::common::GasFeeStrategy::set_global_fee_strategy(150000, 500000, 0.001, 0.001); + println!("✅ SolanaTrade client initialized successfully!"); + Ok(solana_trade) } async fn wrap_sol_real( diff --git a/examples/gas_fee_strategy/Cargo.toml b/examples/gas_fee_strategy/Cargo.toml new file mode 100644 index 0000000..f8e43a8 --- /dev/null +++ b/examples/gas_fee_strategy/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "gas_fee_strategy" +version = "0.1.0" +edition = "2021" + +[dependencies] +sol-trade-sdk = { path = "../.." } +tokio = { version = "1.0", features = ["full"] } diff --git a/examples/gas_fee_strategy/src/main.rs b/examples/gas_fee_strategy/src/main.rs new file mode 100644 index 0000000..97f7727 --- /dev/null +++ b/examples/gas_fee_strategy/src/main.rs @@ -0,0 +1,72 @@ +use sol_trade_sdk::{ + common::gas_fee_strategy::GasFeeStrategy, + swqos::{SwqosType, TradeType}, +}; + +#[tokio::main] +async fn main() { + println!("🚀 Gas Fee Strategy Demo"); + println!("========================"); + + // Set global strategy + println!("1. Set global strategy"); + GasFeeStrategy::set_global_fee_strategy(150000, 500000, 0.001, 0.001); + + // Print all strategies + println!("\n2. Print all strategies"); + GasFeeStrategy::print_all_strategies(); + + // Clear all strategies + println!("\n3. Clear all strategies"); + GasFeeStrategy::clear(); + + // Add normal fee strategy for SwqosType::Default on Buy + println!("\n4. Add normal fee strategy for SwqosType::Default on Buy"); + GasFeeStrategy::add_normal_fee_strategy( + SwqosType::Default, + TradeType::Buy, + 150000, // cu_limit + 500000, // cu_price + 0.0, // tip + ); + + // Add high-low fee strategy for SwqosType::Jito on Buy + println!("\n5. Add high-low fee strategy for SwqosType::Jito on Buy"); + GasFeeStrategy::add_high_low_fee_strategy( + SwqosType::Jito, + TradeType::Buy, + 150000, // cu_limit + 100, // low cu_price + 10 * 1_000_000, // high cu_price + 0.001, // low tip + 0.1, // high tip + ); + + // Print all strategies + println!("\n6. Print all current strategies"); + GasFeeStrategy::print_all_strategies(); + + // Add normal fee strategy for SwqosType::Jito on Buy (will override previous high-low strategy) + println!("\n7. Add normal fee strategy for SwqosType::Jito on Buy (will override previous high-low strategy)"); + GasFeeStrategy::add_normal_fee_strategy( + SwqosType::Jito, + TradeType::Buy, + 150000, // cu_limit + 500000, // cu_price + 0.0001, // tip + ); + + // Print all strategies + println!("\n8. Print all current strategies"); + GasFeeStrategy::print_all_strategies(); + + // Remove strategy for SwqosType::Jito on Buy + println!("\n9. Remove strategy for SwqosType::Jito on Buy"); + GasFeeStrategy::remove_strategy(SwqosType::Jito, TradeType::Buy); + + // Print all strategies + println!("\n10. Print all current strategies"); + GasFeeStrategy::print_all_strategies(); + + println!("\n✅ Gas Fee Strategy Demo completed!"); +} diff --git a/examples/middleware_system/src/main.rs b/examples/middleware_system/src/main.rs index 424d5dc..82f8fb5 100644 --- a/examples/middleware_system/src/main.rs +++ b/examples/middleware_system/src/main.rs @@ -1,6 +1,6 @@ use anyhow::Result; use sol_trade_sdk::{ - common::{AnyResult, PriorityFee, TradeConfig}, + common::{AnyResult, TradeConfig}, swqos::{SwqosConfig, SwqosRegion}, trading::{ core::params::PumpSwapParams, factory::DexType, middleware::builtin::LoggingMiddleware, @@ -59,25 +59,17 @@ impl InstructionMiddleware for CustomMiddleware { /// Create SolanaTrade client /// Initializes a new SolanaTrade client with configuration async fn create_solana_trade_client() -> AnyResult { - println!("Creating SolanaTrade client..."); - - // In real transactions, use your own private key to initialize the payer - let payer = Keypair::new(); + 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 swqos_configs = vec![SwqosConfig::Default(rpc_url.clone())]; - - let trade_config = TradeConfig { - rpc_url, - commitment: CommitmentConfig::confirmed(), - priority_fee: PriorityFee::default(), - swqos_configs, - }; - - let solana_trade_client = SolanaTrade::new(Arc::new(payer), trade_config).await; - println!("SolanaTrade client created successfully!"); - - Ok(solana_trade_client) + let commitment = CommitmentConfig::confirmed(); + let swqos_configs: Vec = vec![SwqosConfig::Default(rpc_url.clone())]; + let trade_config = TradeConfig::new(rpc_url, swqos_configs, commitment); + let solana_trade = SolanaTrade::new(Arc::new(payer), trade_config).await; + // set global strategy + sol_trade_sdk::common::GasFeeStrategy::set_global_fee_strategy(150000, 500000, 0.001, 0.001); + println!("✅ SolanaTrade client initialized successfully!"); + Ok(solana_trade) } async fn test_middleware() -> AnyResult<()> { @@ -91,23 +83,26 @@ async fn test_middleware() -> AnyResult<()> { let slippage_basis_points = Some(100); let recent_blockhash = client.rpc.get_latest_blockhash().await?; let pool_address = Pubkey::from_str("539m4mVWt6iduB6W8rDGPMarzNCMesuqY5eUTiiYHAgR")?; - client - .buy( - DexType::PumpSwap, - mint_pubkey, - buy_sol_cost, - slippage_basis_points, - recent_blockhash, - None, - Box::new(PumpSwapParams::from_pool_address_by_rpc(&client.rpc, &pool_address).await?), - None, - true, - true, - true, - true, - false, - ) - .await?; + + let buy_params = sol_trade_sdk::TradeBuyParams { + dex_type: DexType::PumpSwap, + mint: mint_pubkey, + sol_amount: buy_sol_cost, + slippage_basis_points: slippage_basis_points, + recent_blockhash: recent_blockhash, + extension_params: Box::new( + PumpSwapParams::from_pool_address_by_rpc(&client.rpc, &pool_address).await?, + ), + lookup_table_key: None, + wait_transaction_confirmed: true, + create_wsol_ata: true, + close_wsol_ata: true, + create_mint_ata: true, + open_seed_optimize: false, + nonce_account: None, + current_nonce: None, + }; + client.buy(buy_params).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"); Ok(()) } diff --git a/examples/nonce_cache/src/main.rs b/examples/nonce_cache/src/main.rs index d537d75..3d22aa7 100644 --- a/examples/nonce_cache/src/main.rs +++ b/examples/nonce_cache/src/main.rs @@ -3,7 +3,6 @@ use std::sync::{ Arc, }; -use sol_trade_sdk::solana_streamer_sdk::match_event; use sol_trade_sdk::solana_streamer_sdk::streaming::event_parser::common::filter::EventTypeFilter; use sol_trade_sdk::solana_streamer_sdk::streaming::event_parser::common::EventType; use sol_trade_sdk::solana_streamer_sdk::streaming::event_parser::protocols::pumpfun::PumpFunTradeEvent; @@ -17,11 +16,12 @@ use sol_trade_sdk::{ solana_streamer_sdk::streaming::event_parser::protocols::pumpfun::parser::PUMPFUN_PROGRAM_ID, }; use sol_trade_sdk::{ - common::{AnyResult, PriorityFee, TradeConfig}, + common::AnyResult, swqos::SwqosConfig, trading::{core::params::PumpFunParams, factory::DexType}, SolanaTrade, }; +use sol_trade_sdk::{common::TradeConfig, solana_streamer_sdk::match_event}; use solana_sdk::{commitment_config::CommitmentConfig, signature::Keypair}; // Global static flag to ensure transaction is executed only once @@ -98,28 +98,17 @@ fn create_event_callback() -> impl Fn(Box) { /// Create SolanaTrade client /// Initializes a new SolanaTrade client with configuration async fn create_solana_trade_client() -> AnyResult { - println!("Creating SolanaTrade client..."); - + 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 swqos_configs = vec![SwqosConfig::Default(rpc_url.clone())]; - - let mut priority_fee = PriorityFee::default(); - // Configure according to your needs - priority_fee.rpc_unit_limit = 100000; - - 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) + let commitment = CommitmentConfig::confirmed(); + let swqos_configs: Vec = vec![SwqosConfig::Default(rpc_url.clone())]; + let trade_config = TradeConfig::new(rpc_url, swqos_configs, commitment); + let solana_trade = SolanaTrade::new(Arc::new(payer), trade_config).await; + // set global strategy + sol_trade_sdk::common::GasFeeStrategy::set_global_fee_strategy(150000, 500000, 0.001, 0.001); + println!("✅ SolanaTrade client initialized successfully!"); + Ok(solana_trade) } /// PumpFun sniper trade @@ -130,34 +119,36 @@ async fn pumpfun_copy_trade_with_grpc(trade_info: PumpFunTradeEvent) -> AnyResul let client = 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?; // Setup nonce cache let nonce_account_str = "use_your_nonce_account_here"; NonceCache::get_instance().init(Some(nonce_account_str.to_string())); NonceCache::get_instance().fetch_nonce_info_use_rpc(&client.rpc).await?; - let last_nonce = NonceCache::get_instance().get_nonce_info().current_nonce; - println!("Last nonce: {}", last_nonce); + let current_nonce = NonceCache::get_instance().get_nonce_info().current_nonce; + let nonce_account = NonceCache::get_instance().get_nonce_info().nonce_account; + println!("current_nonce: {}", current_nonce); // 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, - last_nonce, - None, - Box::new(PumpFunParams::from_trade(&trade_info, None)), - None, - true, - false, - false, - true, - false, - ) - .await?; + let buy_params = sol_trade_sdk::TradeBuyParams { + dex_type: DexType::PumpFun, + mint: mint_pubkey, + sol_amount: buy_sol_amount, + slippage_basis_points: slippage_basis_points, + recent_blockhash: recent_blockhash, + extension_params: Box::new(PumpFunParams::from_trade(&trade_info, None)), + lookup_table_key: None, + wait_transaction_confirmed: true, + create_wsol_ata: false, + close_wsol_ata: false, + create_mint_ata: true, + open_seed_optimize: false, + nonce_account: nonce_account, + current_nonce: Some(current_nonce), + }; + client.buy(buy_params).await?; // Exit program std::process::exit(0); diff --git a/examples/pumpfun_copy_trading/src/main.rs b/examples/pumpfun_copy_trading/src/main.rs index 2ef69d6..ec0f9ef 100644 --- a/examples/pumpfun_copy_trading/src/main.rs +++ b/examples/pumpfun_copy_trading/src/main.rs @@ -3,7 +3,6 @@ use std::sync::{ Arc, }; -use sol_trade_sdk::solana_streamer_sdk::match_event; use sol_trade_sdk::solana_streamer_sdk::streaming::event_parser::common::filter::EventTypeFilter; use sol_trade_sdk::solana_streamer_sdk::streaming::event_parser::common::EventType; use sol_trade_sdk::solana_streamer_sdk::streaming::event_parser::protocols::pumpfun::parser::PUMPFUN_PROGRAM_ID; @@ -14,11 +13,12 @@ use sol_trade_sdk::solana_streamer_sdk::streaming::yellowstone_grpc::{ }; use sol_trade_sdk::solana_streamer_sdk::streaming::YellowstoneGrpc; use sol_trade_sdk::{ - common::{AnyResult, PriorityFee, TradeConfig}, + common::AnyResult, swqos::SwqosConfig, trading::{core::params::PumpFunParams, factory::DexType}, SolanaTrade, }; +use sol_trade_sdk::{common::TradeConfig, solana_streamer_sdk::match_event}; use solana_sdk::signer::Signer; use solana_sdk::{commitment_config::CommitmentConfig, signature::Keypair}; use spl_associated_token_account::get_associated_token_address; @@ -97,28 +97,17 @@ fn create_event_callback() -> impl Fn(Box) { /// Create SolanaTrade client /// Initializes a new SolanaTrade client with configuration async fn create_solana_trade_client() -> AnyResult { - println!("Creating SolanaTrade client..."); - + 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 swqos_configs = vec![SwqosConfig::Default(rpc_url.clone())]; - - let mut priority_fee = PriorityFee::default(); - // Configure according to your needs - priority_fee.rpc_unit_limit = 100000; - - 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) + let commitment = CommitmentConfig::confirmed(); + let swqos_configs: Vec = vec![SwqosConfig::Default(rpc_url.clone())]; + let trade_config = TradeConfig::new(rpc_url, swqos_configs, commitment); + let solana_trade = SolanaTrade::new(Arc::new(payer), trade_config).await; + // set global strategy + sol_trade_sdk::common::GasFeeStrategy::set_global_fee_strategy(150000, 500000, 0.001, 0.001); + println!("✅ SolanaTrade client initialized successfully!"); + Ok(solana_trade) } /// PumpFun sniper trade @@ -134,23 +123,23 @@ async fn pumpfun_copy_trade_with_grpc(trade_info: PumpFunTradeEvent) -> AnyResul // 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, - false, - false, - true, - false, - ) - .await?; + let buy_params = sol_trade_sdk::TradeBuyParams { + dex_type: DexType::PumpFun, + mint: mint_pubkey, + sol_amount: buy_sol_amount, + slippage_basis_points: slippage_basis_points, + recent_blockhash: recent_blockhash, + extension_params: Box::new(PumpFunParams::from_trade(&trade_info, None)), + lookup_table_key: None, + wait_transaction_confirmed: true, + create_wsol_ata: false, + close_wsol_ata: false, + create_mint_ata: true, + open_seed_optimize: false, + nonce_account: None, + current_nonce: None, + }; + client.buy(buy_params).await?; // Sell tokens println!("Selling tokens from PumpFun..."); @@ -163,23 +152,23 @@ async fn pumpfun_copy_trade_with_grpc(trade_info: PumpFunTradeEvent) -> AnyResul let amount_token = balance.amount.parse::().unwrap(); println!("Selling {} tokens", amount_token); - client - .sell( - DexType::PumpFun, - mint_pubkey, - amount_token, - slippage_basis_points, - recent_blockhash, - None, - false, - Box::new(PumpFunParams::from_trade(&trade_info, Some(true))), - None, - true, - false, - false, - false, - ) - .await?; + let sell_params = sol_trade_sdk::TradeSellParams { + dex_type: DexType::PumpFun, + mint: mint_pubkey, + token_amount: amount_token, + slippage_basis_points: slippage_basis_points, + recent_blockhash: recent_blockhash, + with_tip: false, + extension_params: Box::new(PumpFunParams::from_trade(&trade_info, Some(true))), + lookup_table_key: None, + wait_transaction_confirmed: true, + create_wsol_ata: false, + close_wsol_ata: false, + open_seed_optimize: false, + nonce_account: None, + current_nonce: None, + }; + client.sell(sell_params).await?; // PumpFunParams can also be set as PumpFunParams::immediate_sell(creator_vault, close_token_account_when_sell) // creator_vault can be obtained from the trade event diff --git a/examples/pumpfun_sniper_trading/src/main.rs b/examples/pumpfun_sniper_trading/src/main.rs index 4fb15df..7a5df3d 100644 --- a/examples/pumpfun_sniper_trading/src/main.rs +++ b/examples/pumpfun_sniper_trading/src/main.rs @@ -1,10 +1,11 @@ +use sol_trade_sdk::common::TradeConfig; use sol_trade_sdk::solana_streamer_sdk::streaming::event_parser::common::filter::EventTypeFilter; use sol_trade_sdk::solana_streamer_sdk::streaming::event_parser::common::EventType; use sol_trade_sdk::solana_streamer_sdk::streaming::event_parser::protocols::pumpfun::PumpFunTradeEvent; use sol_trade_sdk::solana_streamer_sdk::streaming::event_parser::{Protocol, UnifiedEvent}; use sol_trade_sdk::solana_streamer_sdk::{match_event, streaming::ShredStreamGrpc}; use sol_trade_sdk::{ - common::{AnyResult, PriorityFee, TradeConfig}, + common::AnyResult, swqos::SwqosConfig, trading::{core::params::PumpFunParams, factory::DexType}, SolanaTrade, @@ -65,28 +66,17 @@ fn create_event_callback() -> impl Fn(Box) { /// Create SolanaTrade client /// Initializes a new SolanaTrade client with configuration async fn create_solana_trade_client() -> AnyResult { - println!("Creating SolanaTrade client..."); - + 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 swqos_configs = vec![SwqosConfig::Default(rpc_url.clone())]; - - let mut priority_fee = PriorityFee::default(); - // Set RPC unit limit based on your requirements - priority_fee.rpc_unit_limit = 100000; - - 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) + let commitment = CommitmentConfig::confirmed(); + let swqos_configs: Vec = vec![SwqosConfig::Default(rpc_url.clone())]; + let trade_config = TradeConfig::new(rpc_url, swqos_configs, commitment); + let solana_trade = SolanaTrade::new(Arc::new(payer), trade_config).await; + // set global strategy + sol_trade_sdk::common::GasFeeStrategy::set_global_fee_strategy(150000, 500000, 0.001, 0.001); + println!("✅ SolanaTrade client initialized successfully!"); + Ok(solana_trade) } /// Execute PumpFun sniper trading strategy based on received token creation event @@ -102,23 +92,23 @@ async fn pumpfun_sniper_trade_with_shreds(trade_info: PumpFunTradeEvent) -> AnyR // 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_dev_trade(&trade_info, None)), - None, - true, - true, - true, - true, - false, - ) - .await?; + let buy_params = sol_trade_sdk::TradeBuyParams { + dex_type: DexType::PumpFun, + mint: mint_pubkey, + sol_amount: buy_sol_amount, + slippage_basis_points: slippage_basis_points, + recent_blockhash: recent_blockhash, + extension_params: Box::new(PumpFunParams::from_dev_trade(&trade_info, None)), + lookup_table_key: None, + wait_transaction_confirmed: true, + create_wsol_ata: true, + close_wsol_ata: true, + create_mint_ata: true, + open_seed_optimize: false, + nonce_account: None, + current_nonce: None, + }; + client.buy(buy_params).await?; // Sell tokens println!("Selling tokens from PumpFun..."); @@ -131,23 +121,23 @@ async fn pumpfun_sniper_trade_with_shreds(trade_info: PumpFunTradeEvent) -> AnyR let amount_token = balance.amount.parse::().unwrap(); println!("Selling {} tokens", amount_token); - client - .sell( - DexType::PumpFun, - mint_pubkey, - amount_token, - slippage_basis_points, - recent_blockhash, - None, - false, - Box::new(PumpFunParams::immediate_sell(trade_info.creator_vault, true)), - None, - true, - true, - true, - false, - ) - .await?; + let sell_params = sol_trade_sdk::TradeSellParams { + dex_type: DexType::PumpFun, + mint: mint_pubkey, + token_amount: amount_token, + slippage_basis_points: slippage_basis_points, + recent_blockhash: recent_blockhash, + with_tip: false, + extension_params: Box::new(PumpFunParams::immediate_sell(trade_info.creator_vault, true)), + lookup_table_key: None, + wait_transaction_confirmed: true, + create_wsol_ata: true, + close_wsol_ata: true, + open_seed_optimize: false, + nonce_account: None, + current_nonce: None, + }; + client.sell(sell_params).await?; // PumpFunParams can also be set as PumpFunParams::immediate_sell(creator_vault, close_token_account_when_sell) // creator_vault can be obtained from the trade event diff --git a/examples/pumpswap_direct_trading/src/main.rs b/examples/pumpswap_direct_trading/src/main.rs index 2fd3141..7e87cdf 100644 --- a/examples/pumpswap_direct_trading/src/main.rs +++ b/examples/pumpswap_direct_trading/src/main.rs @@ -1,5 +1,5 @@ use sol_trade_sdk::{ - common::{AnyResult, PriorityFee, TradeConfig}, + common::{AnyResult, TradeConfig}, swqos::SwqosConfig, trading::{core::params::PumpSwapParams, factory::DexType}, SolanaTrade, @@ -22,23 +22,25 @@ async fn main() -> Result<(), Box> { // 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, - false, - ) - .await?; + let buy_params = sol_trade_sdk::TradeBuyParams { + dex_type: DexType::PumpSwap, + mint: mint_pubkey, + sol_amount: buy_sol_amount, + slippage_basis_points: slippage_basis_points, + recent_blockhash: recent_blockhash, + extension_params: Box::new( + PumpSwapParams::from_pool_address_by_rpc(&client.rpc, &pool).await?, + ), + lookup_table_key: None, + wait_transaction_confirmed: true, + create_wsol_ata: true, + close_wsol_ata: true, + create_mint_ata: true, + open_seed_optimize: false, + nonce_account: None, + current_nonce: None, + }; + client.buy(buy_params).await?; // Sell tokens println!("Selling tokens from PumpSwap..."); @@ -49,23 +51,25 @@ async fn main() -> Result<(), Box> { let account = get_associated_token_address_with_program_id(&payer, &mint_pubkey, &program_id); 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, - false, - ) - .await?; + let sell_params = sol_trade_sdk::TradeSellParams { + dex_type: DexType::PumpSwap, + mint: mint_pubkey, + token_amount: amount_token, + slippage_basis_points: slippage_basis_points, + recent_blockhash: recent_blockhash, + with_tip: false, + extension_params: Box::new( + PumpSwapParams::from_pool_address_by_rpc(&client.rpc, &pool).await?, + ), + lookup_table_key: None, + wait_transaction_confirmed: true, + create_wsol_ata: true, + close_wsol_ata: true, + open_seed_optimize: false, + nonce_account: None, + current_nonce: None, + }; + client.sell(sell_params).await?; tokio::signal::ctrl_c().await?; Ok(()) @@ -74,27 +78,15 @@ async fn main() -> Result<(), Box> { /// 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"); + 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 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) + let commitment = CommitmentConfig::confirmed(); + let swqos_configs: Vec = vec![SwqosConfig::Default(rpc_url.clone())]; + let trade_config = TradeConfig::new(rpc_url, swqos_configs, commitment); + let solana_trade = SolanaTrade::new(Arc::new(payer), trade_config).await; + // set global strategy + sol_trade_sdk::common::GasFeeStrategy::set_global_fee_strategy(150000, 500000, 0.001, 0.001); + println!("✅ SolanaTrade client initialized successfully!"); + Ok(solana_trade) } diff --git a/examples/pumpswap_trading/src/main.rs b/examples/pumpswap_trading/src/main.rs index 9735fe4..c13175c 100644 --- a/examples/pumpswap_trading/src/main.rs +++ b/examples/pumpswap_trading/src/main.rs @@ -3,9 +3,6 @@ use std::sync::{ Arc, }; -use sol_trade_sdk::solana_streamer_sdk::streaming::event_parser::{ - common::EventType, protocols::pumpswap::PumpSwapSellEvent, -}; use sol_trade_sdk::solana_streamer_sdk::streaming::event_parser::{Protocol, UnifiedEvent}; use sol_trade_sdk::solana_streamer_sdk::streaming::yellowstone_grpc::{ AccountFilter, TransactionFilter, @@ -15,11 +12,17 @@ use sol_trade_sdk::solana_streamer_sdk::{ match_event, streaming::event_parser::protocols::pumpswap::parser::PUMPSWAP_PROGRAM_ID, }; use sol_trade_sdk::{ - common::{AnyResult, PriorityFee, TradeConfig}, + common::AnyResult, swqos::SwqosConfig, trading::{core::params::PumpSwapParams, factory::DexType}, SolanaTrade, }; +use sol_trade_sdk::{ + common::TradeConfig, + solana_streamer_sdk::streaming::event_parser::{ + common::EventType, protocols::pumpswap::PumpSwapSellEvent, + }, +}; use sol_trade_sdk::{ instruction::utils::pumpswap::accounts, solana_streamer_sdk::streaming::event_parser::{ @@ -120,28 +123,17 @@ fn create_event_callback() -> impl Fn(Box) { /// Create SolanaTrade client /// Initializes a new SolanaTrade client with configuration async fn create_solana_trade_client() -> AnyResult { - println!("Creating SolanaTrade client..."); - + 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 swqos_configs = vec![SwqosConfig::Default(rpc_url.clone())]; - - let mut priority_fee = PriorityFee::default(); - // 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) + let commitment = CommitmentConfig::confirmed(); + let swqos_configs: Vec = vec![SwqosConfig::Default(rpc_url.clone())]; + let trade_config = TradeConfig::new(rpc_url, swqos_configs, commitment); + let solana_trade = SolanaTrade::new(Arc::new(payer), trade_config).await; + // set global strategy + sol_trade_sdk::common::GasFeeStrategy::set_global_fee_strategy(150000, 500000, 0.001, 0.001); + println!("✅ SolanaTrade client initialized successfully!"); + Ok(solana_trade) } async fn pumpswap_trade_with_grpc_buy_event(trade_info: PumpSwapBuyEvent) -> AnyResult<()> { @@ -176,23 +168,23 @@ async fn pumpswap_trade_with_grpc(mint_pubkey: Pubkey, params: PumpSwapParams) - // 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(params.clone()), - None, - true, - true, - true, - true, - false, - ) - .await?; + let buy_params = sol_trade_sdk::TradeBuyParams { + dex_type: DexType::PumpSwap, + mint: mint_pubkey, + sol_amount: buy_sol_amount, + slippage_basis_points: slippage_basis_points, + recent_blockhash: recent_blockhash, + extension_params: Box::new(params.clone()), + lookup_table_key: None, + wait_transaction_confirmed: true, + create_wsol_ata: true, + close_wsol_ata: true, + create_mint_ata: true, + open_seed_optimize: false, + nonce_account: None, + current_nonce: None, + }; + client.buy(buy_params).await?; // Sell tokens println!("Selling tokens from PumpSwap..."); @@ -207,23 +199,23 @@ async fn pumpswap_trade_with_grpc(mint_pubkey: Pubkey, params: PumpSwapParams) - let account = get_associated_token_address_with_program_id(&payer, &mint_pubkey, &program_id); 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(params.clone()), - None, - true, - true, - true, - false, - ) - .await?; + let sell_params = sol_trade_sdk::TradeSellParams { + dex_type: DexType::PumpSwap, + mint: mint_pubkey, + token_amount: amount_token, + slippage_basis_points: slippage_basis_points, + recent_blockhash: recent_blockhash, + with_tip: false, + extension_params: Box::new(params.clone()), + lookup_table_key: None, + wait_transaction_confirmed: true, + create_wsol_ata: true, + close_wsol_ata: true, + open_seed_optimize: false, + nonce_account: None, + current_nonce: None, + }; + client.sell(sell_params).await?; // Exit program std::process::exit(0); diff --git a/examples/raydium_amm_v4_trading/src/main.rs b/examples/raydium_amm_v4_trading/src/main.rs index 11f829d..23f37f1 100644 --- a/examples/raydium_amm_v4_trading/src/main.rs +++ b/examples/raydium_amm_v4_trading/src/main.rs @@ -3,17 +3,17 @@ use std::sync::{ Arc, }; -use sol_trade_sdk::{instruction::utils::raydium_amm_v4::{accounts, fetch_amm_info}, solana_streamer_sdk::{match_event, streaming::event_parser::protocols::raydium_amm_v4::RaydiumAmmV4SwapEvent}, trading::common::get_multi_token_balances}; +use sol_trade_sdk::{common::TradeConfig, instruction::utils::raydium_amm_v4::{accounts, fetch_amm_info}, solana_streamer_sdk::{match_event, streaming::event_parser::protocols::raydium_amm_v4::RaydiumAmmV4SwapEvent}, trading::common::get_multi_token_balances}; use sol_trade_sdk::solana_streamer_sdk::streaming::event_parser::common::filter::EventTypeFilter; use sol_trade_sdk::solana_streamer_sdk::streaming::event_parser::common::EventType; use sol_trade_sdk::solana_streamer_sdk::streaming::event_parser::protocols::raydium_amm_v4::parser::RAYDIUM_AMM_V4_PROGRAM_ID; -use sol_trade_sdk::solana_streamer_sdk::streaming::event_parser::{Protocol, UnifiedEvent}; +use sol_trade_sdk::{solana_streamer_sdk::streaming::event_parser::{Protocol, UnifiedEvent}}; use sol_trade_sdk::solana_streamer_sdk::streaming::yellowstone_grpc::{ AccountFilter, TransactionFilter, }; use sol_trade_sdk::solana_streamer_sdk::streaming::YellowstoneGrpc; use sol_trade_sdk::{ - common::{AnyResult, PriorityFee, TradeConfig}, + common::AnyResult, swqos::SwqosConfig, trading::{core::params::RaydiumAmmV4Params, factory::DexType}, SolanaTrade, @@ -97,28 +97,17 @@ fn create_event_callback() -> impl Fn(Box) { /// Create SolanaTrade client /// Initializes a new SolanaTrade client with configuration async fn create_solana_trade_client() -> AnyResult { - println!("Creating SolanaTrade client..."); - + 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 swqos_configs = vec![SwqosConfig::Default(rpc_url.clone())]; - - let mut priority_fee = PriorityFee::default(); - // 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) + let commitment = CommitmentConfig::confirmed(); + let swqos_configs: Vec = vec![SwqosConfig::Default(rpc_url.clone())]; + let trade_config = TradeConfig::new(rpc_url, swqos_configs, commitment); + let solana_trade = SolanaTrade::new(Arc::new(payer), trade_config).await; + // set global strategy + sol_trade_sdk::common::GasFeeStrategy::set_global_fee_strategy(150000, 500000, 0.001, 0.001); + println!("✅ SolanaTrade client initialized successfully!"); + Ok(solana_trade) } /// Raydium_amm_v4 sniper trade @@ -147,23 +136,23 @@ async fn raydium_amm_v4_copy_trade_with_grpc(trade_info: RaydiumAmmV4SwapEvent) // Buy tokens println!("Buying tokens from Raydium_amm_v4..."); let buy_sol_amount = 100_000; - client - .buy( - DexType::RaydiumAmmV4, - mint_pubkey, - buy_sol_amount, - slippage_basis_points, - recent_blockhash, - None, - Box::new(params), - None, - true, - true, - true, - true, - false, - ) - .await?; + let buy_params = sol_trade_sdk::TradeBuyParams { + dex_type: DexType::RaydiumAmmV4, + mint: mint_pubkey, + sol_amount: buy_sol_amount, + slippage_basis_points: slippage_basis_points, + recent_blockhash: recent_blockhash, + extension_params: Box::new(params), + lookup_table_key: None, + wait_transaction_confirmed: true, + create_wsol_ata: true, + close_wsol_ata: true, + create_mint_ata: true, + open_seed_optimize: false, + nonce_account: None, + current_nonce: None, + }; + client.buy(buy_params).await?; // Sell tokens println!("Selling tokens from Raydium_amm_v4..."); @@ -177,23 +166,23 @@ async fn raydium_amm_v4_copy_trade_with_grpc(trade_info: RaydiumAmmV4SwapEvent) println!("Selling {} tokens", amount_token); let params = RaydiumAmmV4Params::from_amm_address_by_rpc(&client.rpc, trade_info.amm).await?; - client - .sell( - DexType::RaydiumAmmV4, - mint_pubkey, - amount_token, - slippage_basis_points, - recent_blockhash, - None, - false, - Box::new(params), - None, - true, - true, - true, - false, - ) - .await?; + let sell_params = sol_trade_sdk::TradeSellParams { + dex_type: DexType::RaydiumAmmV4, + mint: mint_pubkey, + token_amount: amount_token, + slippage_basis_points: slippage_basis_points, + recent_blockhash: recent_blockhash, + with_tip: false, + extension_params: Box::new(params), + lookup_table_key: None, + wait_transaction_confirmed: true, + create_wsol_ata: true, + close_wsol_ata: true, + open_seed_optimize: false, + nonce_account: None, + current_nonce: None, + }; + client.sell(sell_params).await?; // Exit program std::process::exit(0); diff --git a/examples/raydium_cpmm_trading/src/main.rs b/examples/raydium_cpmm_trading/src/main.rs index d648c48..385f2d6 100644 --- a/examples/raydium_cpmm_trading/src/main.rs +++ b/examples/raydium_cpmm_trading/src/main.rs @@ -3,18 +3,18 @@ use std::sync::{ Arc, }; -use sol_trade_sdk::solana_streamer_sdk::streaming::event_parser::{Protocol, UnifiedEvent}; -use sol_trade_sdk::solana_streamer_sdk::streaming::yellowstone_grpc::{ - AccountFilter, TransactionFilter, -}; use sol_trade_sdk::solana_streamer_sdk::streaming::YellowstoneGrpc; use sol_trade_sdk::solana_streamer_sdk::{ match_event, streaming::event_parser::protocols::raydium_cpmm::RaydiumCpmmSwapEvent, }; +use sol_trade_sdk::{common::AnyResult, swqos::SwqosConfig, SolanaTrade}; use sol_trade_sdk::{ - common::{AnyResult, PriorityFee, TradeConfig}, - swqos::SwqosConfig, - SolanaTrade, + common::TradeConfig, + solana_streamer_sdk::streaming::yellowstone_grpc::{AccountFilter, TransactionFilter}, +}; +use sol_trade_sdk::{ + constants::WSOL_TOKEN_ACCOUNT, + solana_streamer_sdk::streaming::event_parser::{Protocol, UnifiedEvent}, }; use sol_trade_sdk::{ instruction::utils::raydium_cpmm::accounts, @@ -89,6 +89,9 @@ fn create_event_callback() -> impl Fn(Box) { |event: Box| { match_event!(event, { RaydiumCpmmSwapEvent => |e: RaydiumCpmmSwapEvent| { + if e.input_token_mint != WSOL_TOKEN_ACCOUNT && e.output_token_mint != WSOL_TOKEN_ACCOUNT { + return; + } // Test code, only test one transaction if !ALREADY_EXECUTED.swap(true, Ordering::SeqCst) { let event_clone = e.clone(); @@ -107,28 +110,26 @@ fn create_event_callback() -> impl Fn(Box) { /// Create SolanaTrade client /// Initializes a new SolanaTrade client with configuration async fn create_solana_trade_client() -> AnyResult { - println!("Creating SolanaTrade client..."); + 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 swqos_configs = vec![SwqosConfig::Default(rpc_url.clone())]; - - let mut priority_fee = PriorityFee::default(); - // 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) + let payer = Keypair::from_bytes( + &std::fs::read_to_string("/Users/ysq/.config/solana/sdk_test.json") + .unwrap() + .trim_matches(|c| c == '[' || c == ']') + .split(',') + .map(|s| s.trim().parse::().unwrap()) + .collect::>(), + ) + .unwrap(); + let rpc_url = "https://ultra-bold-sunset.solana-mainnet.quiknode.pro/1210ea22139565495810678ac0aa33243fea8406/".to_string(); + let commitment = CommitmentConfig::confirmed(); + let swqos_configs: Vec = vec![SwqosConfig::Default(rpc_url.clone())]; + let trade_config = TradeConfig::new(rpc_url, swqos_configs, commitment); + let solana_trade = SolanaTrade::new(Arc::new(payer), trade_config).await; + // set global strategy + sol_trade_sdk::common::GasFeeStrategy::set_global_fee_strategy(150000, 500000, 0.001, 0.001); + println!("✅ SolanaTrade client initialized successfully!"); + Ok(solana_trade) } /// Raydium_cpmm sniper trade @@ -151,23 +152,23 @@ async fn raydium_cpmm_copy_trade_with_grpc(trade_info: RaydiumCpmmSwapEvent) -> // Buy tokens println!("Buying tokens from Raydium_cpmm..."); let buy_sol_amount = 100_000; - client - .buy( - DexType::RaydiumCpmm, - mint_pubkey, - buy_sol_amount, - slippage_basis_points, - recent_blockhash, - None, - Box::new(buy_params), - None, - true, - true, - true, - true, - false, - ) - .await?; + let buy_params = sol_trade_sdk::TradeBuyParams { + dex_type: DexType::RaydiumCpmm, + mint: mint_pubkey, + sol_amount: buy_sol_amount, + slippage_basis_points: slippage_basis_points, + recent_blockhash: recent_blockhash, + extension_params: Box::new(buy_params), + lookup_table_key: None, + wait_transaction_confirmed: true, + create_wsol_ata: true, + close_wsol_ata: true, + create_mint_ata: true, + open_seed_optimize: false, + nonce_account: None, + current_nonce: None, + }; + client.buy(buy_params).await?; // Sell tokens println!("Selling tokens from Raydium_cpmm..."); @@ -183,23 +184,23 @@ async fn raydium_cpmm_copy_trade_with_grpc(trade_info: RaydiumCpmmSwapEvent) -> RaydiumCpmmParams::from_pool_address_by_rpc(&client.rpc, &trade_info.pool_state).await?; println!("Selling {} tokens", amount_token); - client - .sell( - DexType::RaydiumCpmm, - mint_pubkey, - amount_token, - slippage_basis_points, - recent_blockhash, - None, - false, - Box::new(sell_params), - None, - true, - true, - true, - false, - ) - .await?; + let sell_params = sol_trade_sdk::TradeSellParams { + dex_type: DexType::RaydiumCpmm, + mint: mint_pubkey, + token_amount: amount_token, + slippage_basis_points: slippage_basis_points, + recent_blockhash: recent_blockhash, + with_tip: false, + extension_params: Box::new(sell_params), + lookup_table_key: None, + wait_transaction_confirmed: true, + create_wsol_ata: true, + close_wsol_ata: true, + open_seed_optimize: false, + nonce_account: None, + current_nonce: None, + }; + client.sell(sell_params).await?; // Exit program std::process::exit(0); diff --git a/examples/seed_trading/src/main.rs b/examples/seed_trading/src/main.rs index e8ac4b2..c64493b 100644 --- a/examples/seed_trading/src/main.rs +++ b/examples/seed_trading/src/main.rs @@ -1,7 +1,6 @@ use sol_trade_sdk::{ common::{ - fast_fn::get_associated_token_address_with_program_id_fast_use_seed, AnyResult, - PriorityFee, TradeConfig, + fast_fn::get_associated_token_address_with_program_id_fast_use_seed, AnyResult, TradeConfig, }, swqos::SwqosConfig, trading::{core::params::PumpSwapParams, factory::DexType}, @@ -24,23 +23,25 @@ async fn main() -> Result<(), Box> { // 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?; + let buy_params = sol_trade_sdk::TradeBuyParams { + dex_type: DexType::PumpSwap, + mint: mint_pubkey, + sol_amount: buy_sol_amount, + slippage_basis_points: slippage_basis_points, + recent_blockhash: recent_blockhash, + extension_params: Box::new( + PumpSwapParams::from_pool_address_by_rpc(&client.rpc, &pool).await?, + ), + lookup_table_key: None, + wait_transaction_confirmed: true, + create_wsol_ata: true, + close_wsol_ata: true, + create_mint_ata: true, + open_seed_optimize: true, // ❗️❗️❗️❗️ open seed optimize + nonce_account: None, + current_nonce: None, + }; + client.buy(buy_params).await?; tokio::time::sleep(std::time::Duration::from_secs(4)).await; @@ -59,23 +60,25 @@ async fn main() -> Result<(), Box> { ); 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?; + let sell_params = sol_trade_sdk::TradeSellParams { + dex_type: DexType::PumpSwap, + mint: mint_pubkey, + token_amount: amount_token, + slippage_basis_points: slippage_basis_points, + recent_blockhash: recent_blockhash, + with_tip: false, + extension_params: Box::new( + PumpSwapParams::from_pool_address_by_rpc(&client.rpc, &pool).await?, + ), + lookup_table_key: None, + wait_transaction_confirmed: true, + create_wsol_ata: true, + close_wsol_ata: true, + open_seed_optimize: true, // ❗️❗️❗️❗️ open seed optimize + nonce_account: None, + current_nonce: None, + }; + client.sell(sell_params).await?; tokio::signal::ctrl_c().await?; Ok(()) @@ -84,27 +87,15 @@ async fn main() -> Result<(), Box> { /// 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"); + 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 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) + let commitment = CommitmentConfig::confirmed(); + let swqos_configs: Vec = vec![SwqosConfig::Default(rpc_url.clone())]; + let trade_config = TradeConfig::new(rpc_url, swqos_configs, commitment); + let solana_trade = SolanaTrade::new(Arc::new(payer), trade_config).await; + // set global strategy + sol_trade_sdk::common::GasFeeStrategy::set_global_fee_strategy(150000, 500000, 0.001, 0.001); + println!("✅ SolanaTrade client initialized successfully!"); + Ok(solana_trade) } diff --git a/examples/trading_client/src/main.rs b/examples/trading_client/src/main.rs index 72c2b33..46f651f 100644 --- a/examples/trading_client/src/main.rs +++ b/examples/trading_client/src/main.rs @@ -1,5 +1,5 @@ use sol_trade_sdk::{ - common::{AnyResult, PriorityFee, TradeConfig}, + common::{AnyResult, TradeConfig}, swqos::{SwqosConfig, SwqosRegion}, SolanaTrade, }; @@ -8,53 +8,35 @@ use std::sync::Arc; #[tokio::main] async fn main() -> Result<(), Box> { - let _ = test_create_solana_trade_client().await?; + let _ = create_solana_trade_client().await?; println!("Successfully created SolanaTrade client!"); Ok(()) } /// Create SolanaTrade client /// Initializes a new SolanaTrade client with configuration -async fn test_create_solana_trade_client() -> AnyResult { +async fn create_solana_trade_client() -> AnyResult { println!("Creating SolanaTrade client..."); - - let payer = Keypair::new(); + let payer = Keypair::from_base58_string("use_your_payer_keypair_here"); let rpc_url = "https://mainnet.helius-rpc.com/?api-key=xxxxxx".to_string(); - println!("rpc_url: {}", rpc_url); - - 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![ - // First parameter is UUID, pass empty string if no UUID + let commitment = CommitmentConfig::processed(); + let swqos_configs: Vec = vec![ + SwqosConfig::Default(rpc_url.clone()), SwqosConfig::Jito("your uuid".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), - // Add tg official customer https://t.me/FlashBlock_Official to get free FlashBlock key SwqosConfig::FlashBlock("your api_token".to_string(), SwqosRegion::Frankfurt, None), - // Add tg official customer https://t.me/node1_me to get free Node1 key SwqosConfig::Node1("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, - } + ]; + let trade_config = TradeConfig::new(rpc_url, swqos_configs, commitment); + let solana_trade_client = SolanaTrade::new(Arc::new(payer), trade_config).await; + // set global strategy + sol_trade_sdk::common::GasFeeStrategy::set_global_fee_strategy(150000, 500000, 0.001, 0.001); + println!("SolanaTrade client created successfully!"); + Ok(solana_trade_client) } diff --git a/examples/wsol_wrapper/src/main.rs b/examples/wsol_wrapper/src/main.rs index 93f6a8e..ea58e60 100644 --- a/examples/wsol_wrapper/src/main.rs +++ b/examples/wsol_wrapper/src/main.rs @@ -1,7 +1,4 @@ -use sol_trade_sdk::{ - common::{PriorityFee, TradeConfig}, - SolanaTrade, -}; +use sol_trade_sdk::{common::TradeConfig, swqos::SwqosConfig, SolanaTrade}; use solana_sdk::{commitment_config::CommitmentConfig, signature::Keypair}; use std::sync::Arc; @@ -57,13 +54,12 @@ async fn create_solana_trade_client() -> Result = vec![SwqosConfig::Default(rpc_url.clone())]; + let trade_config = TradeConfig::new(rpc_url, swqos_configs, commitment); let solana_trade = SolanaTrade::new(Arc::new(payer), trade_config).await; + // set global strategy + sol_trade_sdk::common::GasFeeStrategy::set_global_fee_strategy(150000, 500000, 0.001, 0.001); println!("✅ SolanaTrade client initialized successfully!"); Ok(solana_trade) } diff --git a/src/common/address_lookup_cache.rs b/src/common/address_lookup_cache.rs index 72d73ba..91e8209 100755 --- a/src/common/address_lookup_cache.rs +++ b/src/common/address_lookup_cache.rs @@ -85,15 +85,6 @@ impl AddressLookupTableCache { key: *lookup_table_address, addresses: Vec::new(), }); - - if result.addresses.len() == 0 { - eprintln!(" ❌ Address lookup table account {} not setup", lookup_table_address); - eprintln!(" ❌ Please update the address table account information using 【AddressLookupTableCache】 first"); - eprintln!( - " ❌ The current transaction will not include this address lookup table account" - ); - } - return result; } } diff --git a/src/common/gas_fee_strategy.rs b/src/common/gas_fee_strategy.rs new file mode 100644 index 0000000..8c1eebd --- /dev/null +++ b/src/common/gas_fee_strategy.rs @@ -0,0 +1,231 @@ +use crate::swqos::{SwqosType, TradeType}; +use arc_swap::ArcSwap; +use std::collections::HashMap; +use std::sync::{Arc, LazyLock}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum GasFeeStrategyType { + Normal, + LowTipHighCuPrice, + HighTipLowCuPrice, +} + +impl GasFeeStrategyType { + pub fn values() -> Vec { + vec![Self::Normal, Self::LowTipHighCuPrice, Self::HighTipLowCuPrice] + } +} + +#[derive(Debug, Clone, Copy)] +pub struct GasFeeStrategyValue { + pub cu_limit: u32, + pub cu_price: u64, + pub tip: f64, +} + +static STRATEGIES: LazyLock< + ArcSwap>, +> = LazyLock::new(|| ArcSwap::from_pointee(HashMap::new())); + +pub struct GasFeeStrategy; + +impl GasFeeStrategy { + /// 设置全局费率策略 + /// Set global fee strategy + pub fn set_global_fee_strategy(cu_limit: u32, cu_price: u64, buy_tip: f64, sell_tip: f64) { + GasFeeStrategy::add_normal_fee_strategies( + &SwqosType::values() + .into_iter() + .filter(|swqos_type| !swqos_type.eq(&SwqosType::Default)) + .collect::>(), + TradeType::Buy, + cu_limit, + cu_price, + buy_tip, + ); + GasFeeStrategy::add_normal_fee_strategies( + &SwqosType::values() + .into_iter() + .filter(|swqos_type| !swqos_type.eq(&SwqosType::Default)) + .collect::>(), + TradeType::Sell, + cu_limit, + cu_price, + sell_tip, + ); + GasFeeStrategy::add_normal_fee_strategy( + SwqosType::Default, + TradeType::Buy, + cu_limit, + cu_price, + 0.0, + ); + GasFeeStrategy::add_normal_fee_strategy( + SwqosType::Default, + TradeType::Sell, + cu_limit, + cu_price, + 0.0, + ); + } + + /// 为多个服务类型添加高低费率策略,会移除(SwqosType,TradeType)的默认策略。 + /// Add high-low fee strategies for multiple service types, Will remove the default strategy of (SwqosType,TradeType) + pub fn add_high_low_fee_strategies( + swqos_types: &[SwqosType], + trade_type: TradeType, + cu_limit: u32, + low_cu_price: u64, + high_cu_price: u64, + low_tip: f64, + high_tip: f64, + ) { + for swqos_type in swqos_types { + GasFeeStrategy::remove_strategy(*swqos_type, trade_type); + } + STRATEGIES.rcu(|current_map| { + let mut new_map = (**current_map).clone(); + for swqos_type in swqos_types { + if swqos_type.eq(&SwqosType::Default) { + continue; + } + new_map.insert( + (*swqos_type, trade_type, GasFeeStrategyType::LowTipHighCuPrice), + GasFeeStrategyValue { cu_limit, cu_price: high_cu_price, tip: low_tip }, + ); + new_map.insert( + (*swqos_type, trade_type, GasFeeStrategyType::HighTipLowCuPrice), + GasFeeStrategyValue { cu_limit, cu_price: low_cu_price, tip: high_tip }, + ); + } + Arc::new(new_map) + }); + } + + /// 为单个服务类型添加高低费率策略,会移除(SwqosType,TradeType)的默认策略。 + /// Add high-low fee strategy for a single service type, Will remove the default strategy of (SwqosType,TradeType) + pub fn add_high_low_fee_strategy( + swqos_type: SwqosType, + trade_type: TradeType, + cu_limit: u32, + low_cu_price: u64, + high_cu_price: u64, + low_tip: f64, + high_tip: f64, + ) { + if swqos_type.eq(&SwqosType::Default) { + return; + } + GasFeeStrategy::remove_strategy(swqos_type, trade_type); + STRATEGIES.rcu(|current_map| { + let mut new_map = (**current_map).clone(); + new_map.insert( + (swqos_type, trade_type, GasFeeStrategyType::LowTipHighCuPrice), + GasFeeStrategyValue { cu_limit, cu_price: high_cu_price, tip: low_tip }, + ); + new_map.insert( + (swqos_type, trade_type, GasFeeStrategyType::HighTipLowCuPrice), + GasFeeStrategyValue { cu_limit, cu_price: low_cu_price, tip: high_tip }, + ); + Arc::new(new_map) + }); + } + + /// 为多个服务类型添加标准费率策略,会移除(SwqosType,TradeType)的高低价策略。 + /// Add normal fee strategies for multiple service types, Will remove the high-low strategies of (SwqosType,TradeType) + pub fn add_normal_fee_strategies( + swqos_types: &[SwqosType], + trade_type: TradeType, + cu_limit: u32, + cu_price: u64, + tip: f64, + ) { + for swqos_type in swqos_types { + GasFeeStrategy::remove_strategy(*swqos_type, trade_type); + } + STRATEGIES.rcu(|current_map| { + let mut new_map = (**current_map).clone(); + for swqos_type in swqos_types { + new_map.insert( + (*swqos_type, trade_type, GasFeeStrategyType::Normal), + GasFeeStrategyValue { cu_limit, cu_price, tip }, + ); + } + Arc::new(new_map) + }); + } + + /// 为单个服务类型添加标准费率策略,会移除(SwqosType,TradeType)的高低价策略。 + /// Add normal fee strategy for a single service type, Will remove the high-low strategies of (SwqosType,TradeType) + pub fn add_normal_fee_strategy( + swqos_type: SwqosType, + trade_type: TradeType, + cu_limit: u32, + cu_price: u64, + tip: f64, + ) { + GasFeeStrategy::remove_strategy(swqos_type, trade_type); + STRATEGIES.rcu(|current_map| { + let mut new_map = (**current_map).clone(); + new_map.insert( + (swqos_type, trade_type, GasFeeStrategyType::Normal), + GasFeeStrategyValue { cu_limit, cu_price, tip }, + ); + Arc::new(new_map) + }); + } + + /// 移除指定(SwqosType,TradeType)的策略。 + /// Remove strategy for specified (SwqosType,TradeType) + pub fn remove_strategy(swqos_type: SwqosType, trade_type: TradeType) { + STRATEGIES.rcu(|current_map| { + let mut new_map = (**current_map).clone(); + new_map.remove(&(swqos_type, trade_type, GasFeeStrategyType::Normal)); + new_map.remove(&(swqos_type, trade_type, GasFeeStrategyType::LowTipHighCuPrice)); + new_map.remove(&(swqos_type, trade_type, GasFeeStrategyType::HighTipLowCuPrice)); + Arc::new(new_map) + }); + } + + /// 获取指定交易类型的所有策略。 + /// Get all strategies for specified trade type + pub fn get_strategies( + trade_type: TradeType, + ) -> Vec<(SwqosType, GasFeeStrategyType, GasFeeStrategyValue)> { + let strategies = STRATEGIES.load(); + let mut result = Vec::new(); + let mut swqos_types = std::collections::HashSet::new(); + for (swqos_type, t_type, _) in strategies.keys() { + if *t_type == trade_type { + swqos_types.insert(*swqos_type); + } + } + for swqos_type in swqos_types { + for strategy_type in GasFeeStrategyType::values() { + if let Some(strategy_value) = + strategies.get(&(swqos_type, trade_type, strategy_type)) + { + result.push((swqos_type, strategy_type, *strategy_value)); + } + } + } + result + } + + /// 清空所有策略。 + /// Clear all strategies + pub fn clear() { + STRATEGIES.store(Arc::new(HashMap::new())); + } + + /// 打印所有策略。 + /// Print all strategies + pub fn print_all_strategies() { + for strategy in GasFeeStrategy::get_strategies(TradeType::Buy) { + println!("[buy] - {:?}", strategy); + } + for strategy in GasFeeStrategy::get_strategies(TradeType::Sell) { + println!("[sell] - {:?}", strategy); + } + } +} diff --git a/src/common/mod.rs b/src/common/mod.rs index abf78c9..24739dc 100755 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -6,5 +6,7 @@ pub mod nonce_cache; pub mod seed; pub mod subscription_handle; pub mod types; +pub mod gas_fee_strategy; pub use types::*; +pub use gas_fee_strategy::*; \ No newline at end of file diff --git a/src/common/nonce_cache.rs b/src/common/nonce_cache.rs index 71c8725..579aa61 100755 --- a/src/common/nonce_cache.rs +++ b/src/common/nonce_cache.rs @@ -15,8 +15,6 @@ pub struct NonceInfo { pub nonce_account: Option, /// Current nonce value pub current_nonce: Hash, - /// Next available time (Unix timestamp in seconds) - pub next_buy_time: i64, /// Whether it has been used pub used: bool, } @@ -39,7 +37,6 @@ impl NonceCache { nonce_info: Mutex::new(NonceInfo { nonce_account: None, current_nonce: Hash::default(), - next_buy_time: 0, used: false, }), }) @@ -50,7 +47,7 @@ impl NonceCache { /// Initialize nonce information pub fn init(&self, nonce_account_str: Option) { let nonce_account = nonce_account_str.and_then(|s| Pubkey::from_str(&s).ok()); - self.update_nonce_info_partial(nonce_account, None, None, Some(false)); + self.update_nonce_info_partial(nonce_account, None, Some(false)); } /// Get a copy of NonceInfo @@ -59,7 +56,6 @@ impl NonceCache { NonceInfo { nonce_account: nonce_info.nonce_account, current_nonce: nonce_info.current_nonce, - next_buy_time: nonce_info.next_buy_time, used: nonce_info.used, } } @@ -69,7 +65,6 @@ impl NonceCache { &self, nonce_account: Option, current_nonce: Option, - next_buy_time: Option, used: Option, ) { let mut current = self.nonce_info.lock(); @@ -83,10 +78,6 @@ impl NonceCache { current.current_nonce = nonce; } - if let Some(time) = next_buy_time { - current.next_buy_time = time; - } - if let Some(u) = used { current.used = u; } @@ -94,7 +85,7 @@ impl NonceCache { /// Mark nonce as used pub fn mark_used(&self) { - self.update_nonce_info_partial(None, None, None, Some(true)); + self.update_nonce_info_partial(None, None, Some(true)); } /// Fetch nonce information using RPC @@ -109,12 +100,7 @@ impl NonceCache { let blockhash = data.durable_nonce.as_hash(); let old_nonce_info = self.get_nonce_info(); if old_nonce_info.current_nonce != *blockhash { - self.update_nonce_info_partial( - None, - Some(*blockhash), - None, - Some(false), - ); + self.update_nonce_info_partial(None, Some(*blockhash), Some(false)); } } } diff --git a/src/common/types.rs b/src/common/types.rs index 3cc7714..5c692f9 100755 --- a/src/common/types.rs +++ b/src/common/types.rs @@ -1,21 +1,10 @@ -use std::sync::Arc; - -use crate::{ - constants::trade::trade::{ - DEFAULT_BUY_TIP_FEE, DEFAULT_RPC_UNIT_LIMIT, DEFAULT_RPC_UNIT_PRICE, DEFAULT_SELL_TIP_FEE, - DEFAULT_TIP_UNIT_LIMIT, DEFAULT_TIP_UNIT_PRICE, - }, - swqos::{SwqosClient, SwqosConfig}, -}; -use serde::Deserialize; -use solana_client::rpc_client::RpcClient; -use solana_sdk::{commitment_config::CommitmentConfig, signature::Keypair}; +use crate::swqos::SwqosConfig; +use solana_sdk::commitment_config::CommitmentConfig; #[derive(Debug, Clone)] pub struct TradeConfig { pub rpc_url: String, pub swqos_configs: Vec, - pub priority_fee: PriorityFee, pub commitment: CommitmentConfig, } @@ -23,58 +12,11 @@ impl TradeConfig { pub fn new( rpc_url: String, swqos_configs: Vec, - priority_fee: PriorityFee, commitment: CommitmentConfig, ) -> Self { - Self { rpc_url, swqos_configs, priority_fee, commitment } - } -} - -#[derive(Debug, Deserialize, Clone, PartialEq)] -pub struct PriorityFee { - pub tip_unit_limit: u32, - pub tip_unit_price: u64, - pub rpc_unit_limit: u32, - pub rpc_unit_price: u64, - // Matches the order of swqos - pub buy_tip_fees: Vec, - // Matches the order of swqos - pub sell_tip_fees: Vec, -} - -impl Default for PriorityFee { - fn default() -> Self { - Self { - tip_unit_limit: DEFAULT_TIP_UNIT_LIMIT, - tip_unit_price: DEFAULT_TIP_UNIT_PRICE, - rpc_unit_limit: DEFAULT_RPC_UNIT_LIMIT, - rpc_unit_price: DEFAULT_RPC_UNIT_PRICE, - // Matches the order of swqos - buy_tip_fees: vec![DEFAULT_BUY_TIP_FEE], - // Matches the order of swqos - sell_tip_fees: vec![DEFAULT_SELL_TIP_FEE], - } + Self { rpc_url, swqos_configs, commitment } } } pub type SolanaRpcClient = solana_client::nonblocking::rpc_client::RpcClient; - -pub struct MethodArgs { - pub payer: Arc, - pub rpc: Arc, - pub nonblocking_rpc: Arc, - pub jito_client: Arc, -} - -impl MethodArgs { - pub fn new( - payer: Arc, - rpc: Arc, - nonblocking_rpc: Arc, - jito_client: Arc, - ) -> Self { - Self { payer, rpc, nonblocking_rpc, jito_client } - } -} - pub type AnyResult = anyhow::Result; diff --git a/src/constants/trade.rs b/src/constants/trade.rs index 4a96442..39936eb 100644 --- a/src/constants/trade.rs +++ b/src/constants/trade.rs @@ -1,9 +1,9 @@ pub mod trade { pub const DEFAULT_SLIPPAGE: u64 = 1000; // 10% - pub const DEFAULT_TIP_UNIT_LIMIT: u32 = 78000; + pub const DEFAULT_TIP_UNIT_LIMIT: u32 = 150000; pub const DEFAULT_TIP_UNIT_PRICE: u64 = 500000; pub const DEFAULT_BUY_TIP_FEE: f64 = 0.0006; pub const DEFAULT_SELL_TIP_FEE: f64 = 0.0001; - pub const DEFAULT_RPC_UNIT_LIMIT: u32 = 78000; + pub const DEFAULT_RPC_UNIT_LIMIT: u32 = 150000; pub const DEFAULT_RPC_UNIT_PRICE: u64 = 500000; } \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 5703888..c06ba2e 100755 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,10 +5,9 @@ pub mod protos; pub mod swqos; pub mod trading; pub mod utils; -use solana_sdk::signer::Signer; -pub use solana_streamer_sdk; - +use crate::common::TradeConfig; use crate::constants::trade::trade::DEFAULT_SLIPPAGE; +use crate::swqos::SwqosClient; use crate::swqos::SwqosConfig; use crate::trading::core::params::BonkParams; use crate::trading::core::params::PumpFunParams; @@ -21,20 +20,28 @@ use crate::trading::BuyParams; use crate::trading::MiddlewareManager; use crate::trading::SellParams; use crate::trading::TradeFactory; -use common::{PriorityFee, SolanaRpcClient, TradeConfig}; +use common::SolanaRpcClient; use parking_lot::Mutex; use rustls::crypto::{ring::default_provider, CryptoProvider}; use solana_sdk::hash::Hash; +use solana_sdk::signer::Signer; use solana_sdk::{pubkey::Pubkey, signature::Keypair, signature::Signature}; +pub use solana_streamer_sdk; use std::sync::Arc; -use swqos::SwqosClient; +/// Main trading client for Solana DeFi protocols +/// +/// `SolanaTrade` provides a unified interface for trading across multiple Solana DEXs +/// including PumpFun, PumpSwap, Bonk, Raydium AMM V4, and Raydium CPMM. +/// It manages RPC connections, transaction signing, and SWQOS (Solana Web Quality of Service) settings. pub struct SolanaTrade { + /// The keypair used for signing all transactions pub payer: Arc, + /// RPC client for blockchain interactions pub rpc: Arc, - pub rpc_client: Vec>, + /// SWQOS clients for transaction priority and routing pub swqos_clients: Vec>, - pub priority_fee: Arc, + /// Optional middleware manager for custom transaction processing pub middleware_manager: Option>, } @@ -45,15 +52,102 @@ impl Clone for SolanaTrade { Self { payer: self.payer.clone(), rpc: self.rpc.clone(), - rpc_client: self.rpc_client.clone(), swqos_clients: self.swqos_clients.clone(), - priority_fee: self.priority_fee.clone(), middleware_manager: self.middleware_manager.clone(), } } } +/// Parameters for executing buy orders across different DEX protocols +/// +/// Contains all necessary configuration for purchasing tokens, including +/// protocol-specific settings, account management options, and transaction preferences. +#[derive(Clone)] +pub struct TradeBuyParams { + // Trading configuration + /// The DEX protocol to use for the trade + pub dex_type: DexType, + /// Public key of the token to purchase + pub mint: Pubkey, + /// Amount of SOL to spend (in lamports) + pub sol_amount: u64, + /// Optional slippage tolerance in basis points (e.g., 100 = 1%) + pub slippage_basis_points: Option, + /// Recent blockhash for transaction validity + pub recent_blockhash: Hash, + /// Protocol-specific parameters (PumpFun, Raydium, etc.) + pub extension_params: Box, + // Extended configuration + /// Optional address lookup table for transaction size optimization + pub lookup_table_key: Option, + /// Whether to wait for transaction confirmation before returning + pub wait_transaction_confirmed: bool, + /// Whether to create wrapped SOL associated token account + pub create_wsol_ata: bool, + /// Whether to close wrapped SOL associated token account after trade + pub close_wsol_ata: bool, + /// Whether to create token mint associated token account + pub create_mint_ata: bool, + /// Whether to enable seed-based optimization for account creation + pub open_seed_optimize: bool, + /// Nonce account for transaction validity + pub nonce_account: Option, + /// Recent nonce for transaction validity + pub current_nonce: Option, +} + +/// Parameters for executing sell orders across different DEX protocols +/// +/// Contains all necessary configuration for selling tokens, including +/// protocol-specific settings, tip preferences, account management options, and transaction preferences. +#[derive(Clone)] +pub struct TradeSellParams { + // Trading configuration + /// The DEX protocol to use for the trade + pub dex_type: DexType, + /// Public key of the token to sell + pub mint: Pubkey, + /// Amount of tokens to sell (in smallest token units) + pub token_amount: u64, + /// Optional slippage tolerance in basis points (e.g., 100 = 1%) + pub slippage_basis_points: Option, + /// Recent blockhash for transaction validity + pub recent_blockhash: Hash, + /// Whether to include tip for transaction priority + pub with_tip: bool, + /// Protocol-specific parameters (PumpFun, Raydium, etc.) + pub extension_params: Box, + // Extended configuration + /// Optional address lookup table for transaction size optimization + pub lookup_table_key: Option, + /// Whether to wait for transaction confirmation before returning + pub wait_transaction_confirmed: bool, + /// Whether to create wrapped SOL associated token account + pub create_wsol_ata: bool, + /// Whether to close wrapped SOL associated token account after trade + pub close_wsol_ata: bool, + /// Whether to enable seed-based optimization for account creation + pub open_seed_optimize: bool, + /// Nonce account for transaction validity + pub nonce_account: Option, + /// Recent nonce for transaction validity + pub current_nonce: Option, +} + impl SolanaTrade { + /// Creates a new SolanaTrade instance with the specified configuration + /// + /// This function initializes the trading system with RPC connection, SWQOS settings, + /// and sets up necessary components for trading operations. + /// + /// # Arguments + /// * `payer` - The keypair used for signing transactions + /// * `rpc_url` - Solana RPC endpoint URL + /// * `commitment` - Transaction commitment level for RPC calls + /// * `swqos_settings` - List of SWQOS (Solana Web Quality of Service) configurations + /// + /// # Returns + /// Returns a configured `SolanaTrade` instance ready for trading operations #[inline] pub async fn new(payer: Arc, trade_config: TradeConfig) -> Self { crate::common::fast_fn::fast_init(&payer.try_pubkey().unwrap()); @@ -66,7 +160,6 @@ impl SolanaTrade { let rpc_url = trade_config.rpc_url.clone(); let swqos_configs = trade_config.swqos_configs.clone(); - let priority_fee = Arc::new(trade_config.priority_fee.clone()); let commitment = trade_config.commitment.clone(); let mut swqos_clients: Vec> = vec![]; @@ -76,24 +169,12 @@ impl SolanaTrade { swqos_clients.push(swqos_client); } - let rpc = Arc::new(SolanaRpcClient::new_with_commitment(rpc_url.clone(), commitment)); + let rpc = + Arc::new(SolanaRpcClient::new_with_commitment(rpc_url.clone(), commitment.clone())); common::seed::update_rents(&rpc).await.unwrap(); common::seed::start_rent_updater(rpc.clone()); - let rpc_client = SwqosConfig::get_swqos_client( - rpc_url.clone(), - commitment, - SwqosConfig::Default(rpc_url), - ); - - let instance = Self { - payer, - rpc, - rpc_client: vec![rpc_client], - swqos_clients, - priority_fee, - middleware_manager: None, - }; + let instance = Self { payer, rpc, swqos_clients, middleware_manager: None }; let mut current = INSTANCE.lock(); *current = Some(Arc::new(instance.clone())); @@ -101,22 +182,47 @@ impl SolanaTrade { instance } + /// Adds a middleware manager to the SolanaTrade instance + /// + /// Middleware managers can be used to implement custom logic that runs before or after trading operations, + /// such as logging, monitoring, or custom validation. + /// + /// # Arguments + /// * `middleware_manager` - The middleware manager to attach + /// + /// # Returns + /// Returns the modified SolanaTrade instance with middleware manager attached pub fn with_middleware_manager(mut self, middleware_manager: MiddlewareManager) -> Self { self.middleware_manager = Some(Arc::new(middleware_manager)); self } - /// Get the RPC client instance + /// Gets the RPC client instance for direct Solana blockchain interactions + /// + /// This provides access to the underlying Solana RPC client that can be used + /// for custom blockchain operations outside of the trading framework. + /// + /// # Returns + /// Returns a reference to the Arc-wrapped SolanaRpcClient instance pub fn get_rpc(&self) -> &Arc { &self.rpc } - /// Get the current instance + /// Gets the current globally shared SolanaTrade instance + /// + /// This provides access to the singleton instance that was created with `new()`. + /// Useful for accessing the trading instance from different parts of the application. + /// + /// # Returns + /// Returns the Arc-wrapped SolanaTrade instance + /// + /// # Panics + /// Panics if no instance has been initialized yet. Make sure to call `new()` first. pub fn get_instance() -> Arc { let instance = INSTANCE.lock(); instance .as_ref() - .expect("PumpFun instance not initialized. Please call new() first.") + .expect("SolanaTrade instance not initialized. Please call new() first.") .clone() } @@ -124,80 +230,54 @@ impl SolanaTrade { /// /// # Arguments /// - /// * `dex_type` - The trading protocol to use (PumpFun, PumpSwap, or Bonk) - /// * `mint` - The public key of the token mint to buy - /// * `sol_amount` - Amount of SOL to spend on the purchase (in lamports) - /// * `slippage_basis_points` - Optional slippage tolerance in basis points (e.g., 100 = 1%) - /// * `recent_blockhash` - Recent blockhash for transaction validity - /// * `custom_priority_fee` - Optional custom priority fee for priority processing - /// * `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 + /// * `params` - Buy trade parameters containing all necessary trading configuration /// /// # Returns /// - /// Returns `Ok(())` if the buy order is successfully executed, or an error if the transaction fails. + /// Returns `Ok(Signature)` with the transaction signature if the buy order is successfully executed, + /// or an error if the transaction fails. /// /// # Errors /// /// This function will return an error if: - /// - Invalid protocol parameters are provided + /// - Invalid protocol parameters are provided for the specified DEX type /// - The transaction fails to execute /// - Network or RPC errors occur /// - Insufficient SOL balance for the purchase - pub async fn buy( - &self, - dex_type: DexType, - mint: Pubkey, - sol_amount: u64, - slippage_basis_points: Option, - recent_blockhash: Hash, - custom_priority_fee: Option, - extension_params: Box, - lookup_table_key: Option, - wait_transaction_confirmed: bool, - create_wsol_ata: bool, - close_wsol_ata: bool, - create_mint_ata: bool, - open_seed_optimize: bool, - ) -> Result { - if slippage_basis_points.is_none() { + /// - Required accounts cannot be created or accessed + pub async fn buy(&self, params: TradeBuyParams) -> Result { + if params.slippage_basis_points.is_none() { println!( "slippage_basis_points is none, use default slippage basis points: {}", DEFAULT_SLIPPAGE ); } - let executor = TradeFactory::create_executor(dex_type.clone()); - let protocol_params = extension_params; + let executor = TradeFactory::create_executor(params.dex_type.clone()); + let protocol_params = params.extension_params; - let mut buy_params = BuyParams { + let 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.priority_fee.clone(), - lookup_table_key, - recent_blockhash, + mint: params.mint, + sol_amount: params.sol_amount, + slippage_basis_points: params.slippage_basis_points, + lookup_table_key: params.lookup_table_key, + recent_blockhash: params.recent_blockhash, data_size_limit: 256 * 1024, - wait_transaction_confirmed: wait_transaction_confirmed, + wait_transaction_confirmed: params.wait_transaction_confirmed, protocol_params: protocol_params.clone(), - open_seed_optimize, - create_wsol_ata, - close_wsol_ata, - create_mint_ata, + open_seed_optimize: params.open_seed_optimize, + create_wsol_ata: params.create_wsol_ata, + close_wsol_ata: params.close_wsol_ata, + create_mint_ata: params.create_mint_ata, swqos_clients: self.swqos_clients.clone(), middleware_manager: self.middleware_manager.clone(), + nonce_account: params.nonce_account, + current_nonce: params.current_nonce, }; - if custom_priority_fee.is_some() { - buy_params.priority_fee = Arc::new(custom_priority_fee.unwrap()); - } // Validate protocol params - let is_valid_params = match dex_type { + let is_valid_params = match params.dex_type { DexType::PumpFun => protocol_params.as_any().downcast_ref::().is_some(), DexType::PumpSwap => { protocol_params.as_any().downcast_ref::().is_some() @@ -222,86 +302,54 @@ impl SolanaTrade { /// /// # Arguments /// - /// * `dex_type` - The trading protocol to use (PumpFun, PumpSwap, or Bonk) - /// * `mint` - The public key of the token mint to sell - /// * `token_amount` - Amount of tokens to sell (in smallest token units) - /// * `slippage_basis_points` - Optional slippage tolerance in basis points (e.g., 100 = 1%) - /// * `recent_blockhash` - Recent blockhash for transaction validity - /// * `custom_priority_fee` - Optional custom priority fee for priority processing - /// * `with_tip` - Optional boolean to indicate if the transaction should be sent with tip - /// * `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 + /// * `params` - Sell trade parameters containing all necessary trading configuration /// /// # Returns /// - /// Returns `Ok(())` if the sell order is successfully executed, or an error if the transaction fails. + /// Returns `Ok(Signature)` with the transaction signature if the sell order is successfully executed, + /// or an error if the transaction fails. /// /// # Errors /// /// This function will return an error if: - /// - Invalid protocol parameters are provided + /// - Invalid protocol parameters are provided for the specified DEX type /// - The transaction fails to execute /// - Network or RPC errors occur /// - Insufficient token balance for the sale /// - Token account doesn't exist or is not properly initialized - pub async fn sell( - &self, - dex_type: DexType, - mint: Pubkey, - token_amount: u64, - slippage_basis_points: Option, - recent_blockhash: Hash, - custom_priority_fee: Option, - with_tip: bool, - extension_params: Box, - lookup_table_key: Option, - wait_transaction_confirmed: bool, - create_wsol_ata: bool, - close_wsol_ata: bool, - open_seed_optimize: bool, - ) -> Result { - if slippage_basis_points.is_none() { + /// - Required accounts cannot be created or accessed + pub async fn sell(&self, params: TradeSellParams) -> Result { + if params.slippage_basis_points.is_none() { println!( "slippage_basis_points is none, use default slippage basis points: {}", DEFAULT_SLIPPAGE ); } - let executor = TradeFactory::create_executor(dex_type.clone()); - let protocol_params = extension_params; + let executor = TradeFactory::create_executor(params.dex_type.clone()); + let protocol_params = params.extension_params; - let mut sell_params = SellParams { + let 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.priority_fee.clone(), - lookup_table_key, - recent_blockhash, - wait_transaction_confirmed: wait_transaction_confirmed, + mint: params.mint, + token_amount: Some(params.token_amount), + slippage_basis_points: params.slippage_basis_points, + lookup_table_key: params.lookup_table_key, + recent_blockhash: params.recent_blockhash, + wait_transaction_confirmed: params.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() - }, + with_tip: params.with_tip, + open_seed_optimize: params.open_seed_optimize, + swqos_clients: self.swqos_clients.clone(), middleware_manager: self.middleware_manager.clone(), - create_wsol_ata, - close_wsol_ata, + create_wsol_ata: params.create_wsol_ata, + close_wsol_ata: params.close_wsol_ata, + nonce_account: params.nonce_account, + current_nonce: params.current_nonce, }; - if custom_priority_fee.is_some() { - sell_params.priority_fee = Arc::new(custom_priority_fee.unwrap()); - } - // Validate protocol params - let is_valid_params = match dex_type { + let is_valid_params = match params.dex_type { DexType::PumpFun => protocol_params.as_any().downcast_ref::().is_some(), DexType::PumpSwap => { protocol_params.as_any().downcast_ref::().is_some() @@ -330,82 +378,59 @@ impl SolanaTrade { /// /// # Arguments /// - /// * `dex_type` - The trading protocol to use (PumpFun, PumpSwap, or Bonk) - /// * `mint` - The public key of the token mint to sell + /// * `params` - Sell trade parameters (will be modified with calculated token amount) /// * `amount_token` - Total amount of tokens available (in smallest token units) /// * `percent` - Percentage of tokens to sell (1-100, where 100 = 100%) - /// * `slippage_basis_points` - Optional slippage tolerance in basis points (e.g., 100 = 1%) - /// * `recent_blockhash` - Recent blockhash for transaction validity - /// * `custom_priority_fee` - Optional custom priority fee for priority processing - /// * `with_tip` - Whether to use tip for priority processing - /// * `extension_params` - Optional protocol-specific parameters (uses defaults if None) - /// * `lookup_table_key` - Optional lookup table key for address lookup optimization - /// * `wait_transaction_confirmed` - Whether to wait for the transaction to be confirmed /// /// # Returns /// - /// Returns `Ok(())` if the sell order is successfully executed, or an error if the transaction fails. + /// Returns `Ok(Signature)` with the transaction signature if the sell order is successfully executed, + /// or an error if the transaction fails. /// /// # Errors /// /// This function will return an error if: /// - `percent` is 0 or greater than 100 - /// - Invalid protocol parameters are provided + /// - Invalid protocol parameters are provided for the specified DEX type /// - The transaction fails to execute /// - Network or RPC errors occur /// - Insufficient token balance for the calculated sale amount /// - Token account doesn't exist or is not properly initialized + /// - Required accounts cannot be created or accessed pub async fn sell_by_percent( &self, - dex_type: DexType, - mint: Pubkey, + mut params: TradeSellParams, amount_token: u64, percent: u64, - slippage_basis_points: Option, - recent_blockhash: Hash, - custom_priority_fee: Option, - with_tip: bool, - extension_params: Box, - lookup_table_key: Option, - wait_transaction_confirmed: bool, - 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")); } let amount = amount_token * percent / 100; - self.sell( - dex_type, - mint, - amount, - slippage_basis_points, - recent_blockhash, - custom_priority_fee, - with_tip, - extension_params, - lookup_table_key, - wait_transaction_confirmed, - create_wsol_ata, - close_wsol_ata, - open_seed_optimize, - ) - .await + params.token_amount = amount; + self.sell(params).await } - /// Wraps SOL into wSOL (Wrapped SOL) + /// Wraps native SOL into wSOL (Wrapped SOL) for use in SPL token operations /// /// 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. + /// token balance to make SOL usable as an SPL token in trading operations. /// /// # Arguments - /// - `amount`: The amount of SOL to wrap (in lamports) + /// * `amount` - The amount of SOL to wrap (in lamports) /// /// # Returns - /// - `Ok(String)`: Transaction signature - /// - `Err(anyhow::Error)`: If the transaction fails + /// * `Ok(String)` - Transaction signature if successful + /// * `Err(anyhow::Error)` - If the transaction fails to execute + /// + /// # Errors + /// + /// This function will return an error if: + /// - Insufficient SOL balance for the wrap operation + /// - wSOL associated token account creation fails + /// - Transaction fails to execute or confirm + /// - Network or RPC errors occur pub async fn wrap_sol_to_wsol(&self, amount: u64) -> Result { use crate::trading::common::wsol_manager::handle_wsol; use solana_sdk::transaction::Transaction; @@ -417,15 +442,23 @@ impl SolanaTrade { 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 + /// Closes the wSOL associated token account and unwraps remaining balance 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. + /// This is useful for cleaning up wSOL accounts and recovering wrapped SOL after trading operations. /// /// # Returns - /// - `Ok(String)`: Transaction signature - /// - `Err(anyhow::Error)`: If the transaction fails + /// * `Ok(String)` - Transaction signature if successful + /// * `Err(anyhow::Error)` - If the transaction fails to execute + /// + /// # Errors + /// + /// This function will return an error if: + /// - wSOL associated token account doesn't exist + /// - Account closure fails due to insufficient permissions + /// - Transaction fails to execute or confirm + /// - Network or RPC errors occur pub async fn close_wsol(&self) -> Result { use crate::trading::common::wsol_manager::close_wsol; use solana_sdk::transaction::Transaction; diff --git a/src/swqos/astralane.rs b/src/swqos/astralane.rs index baf648a..c5ff5d4 100644 --- a/src/swqos/astralane.rs +++ b/src/swqos/astralane.rs @@ -149,7 +149,6 @@ impl AstralaneClient { pub async fn send_transaction(&self, trade_type: TradeType, transaction: &VersionedTransaction) -> Result<()> { let start_time = Instant::now(); let (content, signature) = serialize_transaction_and_encode(transaction, UiTransactionEncoding::Base64).await?; - println!(" Transaction encoded to base64: {:?}", start_time.elapsed()); let request_body = serde_json::to_string(&json!({ "jsonrpc": "2.0", @@ -175,12 +174,12 @@ impl AstralaneClient { // Parse JSON response if let Ok(response_json) = serde_json::from_str::(&response_text) { if response_json.get("result").is_some() { - println!(" astralane {} submitted: {:?}", trade_type, start_time.elapsed()); + println!(" [astralane] {} submitted: {:?}", trade_type, start_time.elapsed()); } else if let Some(_error) = response_json.get("error") { - eprintln!(" astralane {} submission failed: {:?}", trade_type, _error); + eprintln!(" [astralane] {} submission failed: {:?}", trade_type, _error); } } else { - eprintln!(" astralane {} submission failed: {:?}", trade_type, response_text); + eprintln!(" [astralane] {} submission failed: {:?}", trade_type, response_text); } let start_time: Instant = Instant::now(); @@ -188,12 +187,12 @@ impl AstralaneClient { Ok(_) => (), Err(e) => { println!(" signature: {:?}", signature); - println!(" astralane {} confirmation failed: {:?}", trade_type, start_time.elapsed()); + println!(" [astralane] {} confirmation failed: {:?}", trade_type, start_time.elapsed()); return Err(e); }, } println!(" signature: {:?}", signature); - println!(" astralane {} confirmed: {:?}", trade_type, start_time.elapsed()); + println!(" [astralane] {} confirmed: {:?}", trade_type, start_time.elapsed()); Ok(()) } diff --git a/src/swqos/blockrazor.rs b/src/swqos/blockrazor.rs index f698541..77ffb5b 100644 --- a/src/swqos/blockrazor.rs +++ b/src/swqos/blockrazor.rs @@ -156,7 +156,6 @@ impl BlockRazorClient { pub async fn send_transaction(&self, trade_type: TradeType, transaction: &VersionedTransaction) -> Result<()> { let start_time = Instant::now(); let (content, signature) = serialize_transaction_and_encode(transaction, UiTransactionEncoding::Base64).await?; - println!(" Transaction encoded to base64: {:?}", start_time.elapsed()); // BlockRazor使用fast模式的请求格式 let request_body = serde_json::to_string(&json!({ @@ -177,12 +176,12 @@ impl BlockRazorClient { // Parse JSON response if let Ok(response_json) = serde_json::from_str::(&response_text) { if response_json.get("result").is_some() || response_json.get("signature").is_some() { - println!(" blockrazor {} submitted: {:?}", trade_type, start_time.elapsed()); + println!(" [blockrazor] {} submitted: {:?}", trade_type, start_time.elapsed()); } else if let Some(_error) = response_json.get("error") { - eprintln!(" blockrazor {} submission failed: {:?}", trade_type, _error); + eprintln!(" [blockrazor] {} submission failed: {:?}", trade_type, _error); } } else { - eprintln!(" blockrazor {} submission failed: {:?}", trade_type, response_text); + eprintln!(" [blockrazor] {} submission failed: {:?}", trade_type, response_text); } let start_time: Instant = Instant::now(); @@ -190,12 +189,12 @@ impl BlockRazorClient { Ok(_) => (), Err(e) => { println!(" signature: {:?}", signature); - println!(" blockrazor {} confirmation failed: {:?}", trade_type, start_time.elapsed()); + println!(" [blockrazor] {} confirmation failed: {:?}", trade_type, start_time.elapsed()); return Err(e); }, } println!(" signature: {:?}", signature); - println!(" blockrazor {} confirmed: {:?}", trade_type, start_time.elapsed()); + println!(" [blockrazor] {} confirmed: {:?}", trade_type, start_time.elapsed()); Ok(()) } diff --git a/src/swqos/bloxroute.rs b/src/swqos/bloxroute.rs index 4902b2a..a81104c 100755 --- a/src/swqos/bloxroute.rs +++ b/src/swqos/bloxroute.rs @@ -60,7 +60,6 @@ impl BloxrouteClient { pub async fn send_transaction(&self, trade_type: TradeType, transaction: &VersionedTransaction) -> Result<()> { let start_time = Instant::now(); let (content, signature) = serialize_transaction_and_encode(transaction, UiTransactionEncoding::Base64).await?; - println!(" Transaction encoded to base64: {:?}", start_time.elapsed()); let body = serde_json::json!({ "transaction": { @@ -83,12 +82,12 @@ impl BloxrouteClient { // 5. Use `serde_json::from_str()` to parse JSON, reducing extra wait from `.json().await?` if let Ok(response_json) = serde_json::from_str::(&response_text) { if response_json.get("result").is_some() { - println!(" bloxroute {} submitted: {:?}", trade_type, start_time.elapsed()); + println!(" [bloxroute] {} submitted: {:?}", trade_type, start_time.elapsed()); } else if let Some(_error) = response_json.get("error") { - eprintln!(" bloxroute {} submission failed: {:?}", trade_type, _error); + eprintln!(" [bloxroute] {} submission failed: {:?}", trade_type, _error); } } else { - eprintln!(" bloxroute {} submission failed: {:?}", trade_type, response_text); + eprintln!(" [bloxroute] {} submission failed: {:?}", trade_type, response_text); } let start_time: Instant = Instant::now(); @@ -96,19 +95,18 @@ impl BloxrouteClient { Ok(_) => (), Err(e) => { println!(" signature: {:?}", signature); - println!(" bloxroute {} confirmation failed: {:?}", trade_type, start_time.elapsed()); + println!(" [bloxroute] {} confirmation failed: {:?}", trade_type, start_time.elapsed()); return Err(e); }, } println!(" signature: {:?}", signature); - println!(" bloxroute {} confirmed: {:?}", trade_type, start_time.elapsed()); + println!(" [bloxroute] {} confirmed: {:?}", trade_type, start_time.elapsed()); Ok(()) } pub async fn send_transactions(&self, trade_type: TradeType, transactions: &Vec) -> Result<()> { let start_time = Instant::now(); - println!(" Transaction encoded to base64: {:?}", start_time.elapsed()); let body = serde_json::json!({ "entries": transactions diff --git a/src/swqos/flashblock.rs b/src/swqos/flashblock.rs index 6fa7dca..4e0c935 100644 --- a/src/swqos/flashblock.rs +++ b/src/swqos/flashblock.rs @@ -61,7 +61,6 @@ impl FlashBlockClient { pub async fn send_transaction(&self, trade_type: TradeType, transaction: &VersionedTransaction) -> Result<()> { let start_time = Instant::now(); let (content, signature) = serialize_transaction_and_encode(transaction, UiTransactionEncoding::Base64).await?; - println!(" Transaction encoded to base64: {:?}", start_time.elapsed()); // FlashBlock API format let request_body = serde_json::to_string(&json!({ @@ -85,12 +84,12 @@ impl FlashBlockClient { // Parse response if let Ok(response_json) = serde_json::from_str::(&response_text) { if response_json.get("success").is_some() || response_json.get("result").is_some() { - println!(" FlashBlock {} submitted: {:?}", trade_type, start_time.elapsed()); + println!(" [FlashBlock] {} submitted: {:?}", trade_type, start_time.elapsed()); } else if let Some(_error) = response_json.get("error") { - eprintln!(" FlashBlock {} submission failed: {:?}", trade_type, _error); + eprintln!(" [FlashBlock] {} submission failed: {:?}", trade_type, _error); } } else { - eprintln!(" FlashBlock {} submission failed: {:?}", trade_type, response_text); + eprintln!(" [FlashBlock] {} submission failed: {:?}", trade_type, response_text); } let start_time: Instant = Instant::now(); @@ -98,12 +97,12 @@ impl FlashBlockClient { Ok(_) => (), Err(e) => { println!(" signature: {:?}", signature); - println!(" FlashBlock {} confirmation failed: {:?}", trade_type, start_time.elapsed()); + println!(" [FlashBlock] {} confirmation failed: {:?}", trade_type, start_time.elapsed()); return Err(e); }, } println!(" signature: {:?}", signature); - println!(" FlashBlock {} confirmed: {:?}", trade_type, start_time.elapsed()); + println!(" [FlashBlock] {} confirmed: {:?}", trade_type, start_time.elapsed()); Ok(()) } diff --git a/src/swqos/jito.rs b/src/swqos/jito.rs index 38c4e47..c9283f0 100755 --- a/src/swqos/jito.rs +++ b/src/swqos/jito.rs @@ -64,7 +64,6 @@ impl JitoClient { pub async fn send_transaction(&self, trade_type: TradeType, transaction: &VersionedTransaction) -> Result<()> { let start_time = Instant::now(); let (content, signature) = serialize_transaction_and_encode(transaction, UiTransactionEncoding::Base64).await?; - println!(" Transaction encoded to base64: {:?}", start_time.elapsed()); let request_body = serde_json::to_string(&json!({ "id": 1, @@ -99,12 +98,12 @@ impl JitoClient { if let Ok(response_json) = serde_json::from_str::(&response_text) { if response_json.get("result").is_some() { - println!(" jito {} submitted: {:?}", trade_type, start_time.elapsed()); + println!(" [jito] {} submitted: {:?}", trade_type, start_time.elapsed()); } else if let Some(_error) = response_json.get("error") { - eprintln!(" jito {} submission failed: {:?}", trade_type, _error); + eprintln!(" [jito] {} submission failed: {:?}", trade_type, _error); } } else { - eprintln!(" jito {} submission failed: {:?}", trade_type, response_text); + eprintln!(" [jito] {} submission failed: {:?}", trade_type, response_text); } let start_time: Instant = Instant::now(); @@ -112,12 +111,12 @@ impl JitoClient { Ok(_) => (), Err(e) => { println!(" signature: {:?}", signature); - println!(" jito {} confirmation failed: {:?}", trade_type, start_time.elapsed()); + println!(" [jito] {} confirmation failed: {:?}", trade_type, start_time.elapsed()); return Err(e); }, } println!(" signature: {:?}", signature); - println!(" jito {} confirmed: {:?}", trade_type, start_time.elapsed()); + println!(" [jito] {} confirmed: {:?}", trade_type, start_time.elapsed()); Ok(()) } diff --git a/src/swqos/mod.rs b/src/swqos/mod.rs index 5b8f981..1239af0 100755 --- a/src/swqos/mod.rs +++ b/src/swqos/mod.rs @@ -48,7 +48,7 @@ lazy_static::lazy_static! { static ref TIP_ACCOUNT_CACHE: RwLock> = RwLock::new(Vec::new()); } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum TradeType { Create, CreateAndBuy, @@ -68,7 +68,7 @@ impl std::fmt::Display for TradeType { } } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum SwqosType { Jito, NextBlock, @@ -82,6 +82,23 @@ pub enum SwqosType { Default, } +impl SwqosType { + pub fn values() -> Vec { + vec![ + Self::Jito, + Self::NextBlock, + Self::ZeroSlot, + Self::Temporal, + Self::Bloxroute, + Self::Node1, + Self::FlashBlock, + Self::BlockRazor, + Self::Astralane, + Self::Default, + ] + } +} + pub type SwqosClient = dyn SwqosClientTrait + Send + Sync + 'static; #[async_trait::async_trait] @@ -107,14 +124,23 @@ pub enum SwqosRegion { #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum SwqosConfig { Default(String), + /// Jito(uuid, region, custom_url) Jito(String, SwqosRegion, Option), + /// NextBlock(api_token, region, custom_url) NextBlock(String, SwqosRegion, Option), + /// Bloxroute(api_token, region, custom_url) Bloxroute(String, SwqosRegion, Option), + /// Temporal(api_token, region, custom_url) Temporal(String, SwqosRegion, Option), + /// ZeroSlot(api_token, region, custom_url) ZeroSlot(String, SwqosRegion, Option), + /// Node1(api_token, region, custom_url) Node1(String, SwqosRegion, Option), + /// FlashBlock(api_token, region, custom_url) FlashBlock(String, SwqosRegion, Option), + /// BlockRazor(api_token, region, custom_url) BlockRazor(String, SwqosRegion, Option), + /// Astralane(api_token, region, custom_url) Astralane(String, SwqosRegion, Option), } diff --git a/src/swqos/nextblock.rs b/src/swqos/nextblock.rs index 64a5d54..c78ffeb 100755 --- a/src/swqos/nextblock.rs +++ b/src/swqos/nextblock.rs @@ -66,7 +66,6 @@ impl NextBlockClient { pub async fn send_transaction(&self, trade_type: TradeType, transaction: &VersionedTransaction) -> Result<()> { let start_time = Instant::now(); let (content, signature) = serialize_transaction_and_encode(transaction, UiTransactionEncoding::Base64).await?; - println!(" Transaction encoded to base64: {:?}", start_time.elapsed()); let request_body = serde_json::to_string(&json!({ "transaction": { @@ -86,12 +85,12 @@ impl NextBlockClient { if let Ok(response_json) = serde_json::from_str::(&response_text) { if response_json.get("result").is_some() { - println!(" nextblock {} submitted: {:?}", trade_type, start_time.elapsed()); + println!(" [nextblock] {} submitted: {:?}", trade_type, start_time.elapsed()); } else if let Some(_error) = response_json.get("error") { - eprintln!(" nextblock {} submission failed: {:?}", trade_type, _error); + eprintln!(" [nextblock] {} submission failed: {:?}", trade_type, _error); } } else { - eprintln!(" nextblock {} submission failed: {:?}", trade_type, response_text); + eprintln!(" [nextblock] {} submission failed: {:?}", trade_type, response_text); } let start_time: Instant = Instant::now(); @@ -99,12 +98,12 @@ impl NextBlockClient { Ok(_) => (), Err(e) => { println!(" signature: {:?}", signature); - println!(" nextblock {} confirmation failed: {:?}", trade_type, start_time.elapsed()); + println!(" [nextblock] {} confirmation failed: {:?}", trade_type, start_time.elapsed()); return Err(e); }, } println!(" signature: {:?}", signature); - println!(" nextblock {} confirmed: {:?}", trade_type, start_time.elapsed()); + println!(" [nextblock] {} confirmed: {:?}", trade_type, start_time.elapsed()); Ok(()) } diff --git a/src/swqos/node1.rs b/src/swqos/node1.rs index b47143d..7632f08 100644 --- a/src/swqos/node1.rs +++ b/src/swqos/node1.rs @@ -143,7 +143,6 @@ impl Node1Client { pub async fn send_transaction(&self, trade_type: TradeType, transaction: &VersionedTransaction) -> Result<()> { let start_time = Instant::now(); let (content, signature) = serialize_transaction_and_encode(transaction, UiTransactionEncoding::Base64).await?; - println!(" Transaction encoded to base64: {:?}", start_time.elapsed()); let request_body = serde_json::to_string(&json!({ "jsonrpc": "2.0", @@ -168,12 +167,12 @@ impl Node1Client { // Parse JSON response if let Ok(response_json) = serde_json::from_str::(&response_text) { if response_json.get("result").is_some() { - println!(" node1 {} submitted: {:?}", trade_type, start_time.elapsed()); + println!(" [node1] {} submitted: {:?}", trade_type, start_time.elapsed()); } else if let Some(_error) = response_json.get("error") { - eprintln!(" node1 {} submission failed: {:?}", trade_type, _error); + eprintln!(" [node1] {} submission failed: {:?}", trade_type, _error); } } else { - eprintln!(" node1 {} submission failed: {:?}", trade_type, response_text); + eprintln!(" [node1] {} submission failed: {:?}", trade_type, response_text); } let start_time: Instant = Instant::now(); @@ -181,12 +180,12 @@ impl Node1Client { Ok(_) => (), Err(e) => { println!(" signature: {:?}", signature); - println!(" node1 {} confirmation failed: {:?}", trade_type, start_time.elapsed()); + println!(" [node1] {} confirmation failed: {:?}", trade_type, start_time.elapsed()); return Err(e); }, } println!(" signature: {:?}", signature); - println!(" node1 {} confirmed: {:?}", trade_type, start_time.elapsed()); + println!(" [node1] {} confirmed: {:?}", trade_type, start_time.elapsed()); Ok(()) } diff --git a/src/swqos/solana_rpc.rs b/src/swqos/solana_rpc.rs index 2535535..59368c9 100755 --- a/src/swqos/solana_rpc.rs +++ b/src/swqos/solana_rpc.rs @@ -42,12 +42,12 @@ impl SwqosClientTrait for SolRpcClient { Ok(_) => (), Err(e) => { println!(" signature: {:?}", signature); - println!(" rpc {} confirmation failed: {:?}", trade_type, start_time.elapsed()); + println!(" [rpc] {} confirmation failed: {:?}", trade_type, start_time.elapsed()); return Err(e); } } println!(" signature: {:?}", signature); - println!(" rpc {} confirmed: {:?}", trade_type, start_time.elapsed()); + println!(" [rpc] {} confirmed: {:?}", trade_type, start_time.elapsed()); Ok(()) } diff --git a/src/swqos/temporal.rs b/src/swqos/temporal.rs index 23c20c3..e5587ec 100755 --- a/src/swqos/temporal.rs +++ b/src/swqos/temporal.rs @@ -147,7 +147,6 @@ impl TemporalClient { pub async fn send_transaction(&self, trade_type: TradeType, transaction: &VersionedTransaction) -> Result<()> { let start_time = Instant::now(); let (content, signature) = serialize_transaction_and_encode(transaction, UiTransactionEncoding::Base64).await?; - println!(" Transaction encoded to base64: {:?}", start_time.elapsed()); // Build request body according to Nozomi documentation requirements let request_body = serde_json::to_string(&json!({ @@ -175,12 +174,12 @@ impl TemporalClient { if let Ok(response_json) = serde_json::from_str::(&response_text) { if response_json.get("result").is_some() { - println!(" nozomi {} submitted: {:?}", trade_type, start_time.elapsed()); + println!(" [nozomi] {} submitted: {:?}", trade_type, start_time.elapsed()); } else if let Some(_error) = response_json.get("error") { // eprintln!("nozomi transaction submission failed: {:?}", _error); } } else { - eprintln!(" nozomi {} submission failed: {:?}", trade_type, response_text); + eprintln!(" [nozomi] {} submission failed: {:?}", trade_type, response_text); } let start_time: Instant = Instant::now(); @@ -188,12 +187,12 @@ impl TemporalClient { Ok(_) => (), Err(e) => { println!(" signature: {:?}", signature); - println!(" nozomi {} confirmation failed: {:?}", trade_type, start_time.elapsed()); + println!(" [nozomi] {} confirmation failed: {:?}", trade_type, start_time.elapsed()); return Err(e); }, } println!(" signature: {:?}", signature); - println!(" nozomi {} confirmed: {:?}", trade_type, start_time.elapsed()); + println!(" [nozomi] {} confirmed: {:?}", trade_type, start_time.elapsed()); Ok(()) } diff --git a/src/swqos/zeroslot.rs b/src/swqos/zeroslot.rs index e137426..ee6f3fb 100755 --- a/src/swqos/zeroslot.rs +++ b/src/swqos/zeroslot.rs @@ -61,7 +61,6 @@ impl ZeroSlotClient { pub async fn send_transaction(&self, trade_type: TradeType, transaction: &VersionedTransaction) -> Result<()> { let start_time = Instant::now(); let (content, signature) = serialize_transaction_and_encode(transaction, UiTransactionEncoding::Base64).await?; - println!(" Transaction encoded to base64: {:?}", start_time.elapsed()); let request_body = serde_json::to_string(&json!({ "jsonrpc": "2.0", @@ -90,12 +89,12 @@ impl ZeroSlotClient { // 5. Use `serde_json::from_str()` to parse JSON, reducing extra wait from `.json().await?` if let Ok(response_json) = serde_json::from_str::(&response_text) { if response_json.get("result").is_some() { - println!(" 0slot {} submitted: {:?}", trade_type, start_time.elapsed()); + println!(" [0slot] {} submitted: {:?}", trade_type, start_time.elapsed()); } else if let Some(_error) = response_json.get("error") { - eprintln!(" 0slot {} submission failed: {:?}", trade_type, _error); + eprintln!(" [0slot] {} submission failed: {:?}", trade_type, _error); } } else { - eprintln!(" 0slot {} submission failed: {:?}", trade_type, response_text); + eprintln!(" [0slot] {} submission failed: {:?}", trade_type, response_text); } let start_time: Instant = Instant::now(); @@ -103,12 +102,12 @@ impl ZeroSlotClient { Ok(_) => (), Err(e) => { println!(" signature: {:?}", signature); - println!(" 0slot {} confirmation failed: {:?}", trade_type, start_time.elapsed()); + println!(" [0slot] {} confirmation failed: {:?}", trade_type, start_time.elapsed()); return Err(e); }, } println!(" signature: {:?}", signature); - println!(" 0slot {} confirmed: {:?}", trade_type, start_time.elapsed()); + println!(" [0slot] {} confirmed: {:?}", trade_type, start_time.elapsed()); Ok(()) } diff --git a/src/trading/common/address_lookup_manager.rs b/src/trading/common/address_lookup_manager.rs index 9f8d379..97b6fde 100755 --- a/src/trading/common/address_lookup_manager.rs +++ b/src/trading/common/address_lookup_manager.rs @@ -1,16 +1,37 @@ +use std::sync::Arc; + use solana_sdk::{message::AddressLookupTableAccount, pubkey::Pubkey}; -use crate::common::address_lookup_cache::get_address_lookup_table_account; +use crate::common::{ + address_lookup_cache::{get_address_lookup_table_account, AddressLookupTableCache}, + SolanaRpcClient, +}; /// Get address lookup table account list /// If lookup_table_key is provided, get the corresponding account, otherwise return empty list pub async fn get_address_lookup_table_accounts( + rpc: Option>, lookup_table_key: Option, ) -> Vec { match lookup_table_key { Some(key) => { let account = get_address_lookup_table_account(&key).await; - vec![account] + if account.addresses.len() == 0 { + if rpc.is_some() { + let _ = AddressLookupTableCache::get_instance() + .set_address_lookup_table(rpc.unwrap(), &key) + .await; + let new_account = get_address_lookup_table_account(&key).await; + if new_account.addresses.len() == 0 { + return Vec::new(); + } else { + return vec![new_account]; + } + } else { + return Vec::new(); + } + } + return vec![account]; } None => Vec::new(), } diff --git a/src/trading/common/compute_budget_manager.rs b/src/trading/common/compute_budget_manager.rs index 952055d..4491607 100755 --- a/src/trading/common/compute_budget_manager.rs +++ b/src/trading/common/compute_budget_manager.rs @@ -1,4 +1,3 @@ -use crate::common::PriorityFee; use dashmap::DashMap; use once_cell::sync::Lazy; use smallvec::SmallVec; @@ -20,19 +19,18 @@ static COMPUTE_BUDGET_CACHE: Lazy SmallVec<[Instruction; 3]> { - let (unit_price, unit_limit) = if is_rpc { - (priority_fee.rpc_unit_price, priority_fee.rpc_unit_limit) - } else { - (priority_fee.tip_unit_price, priority_fee.tip_unit_limit) - }; - // Create cache key - let cache_key = ComputeBudgetCacheKey { data_size_limit, unit_price, unit_limit, is_buy }; + let cache_key = ComputeBudgetCacheKey { + data_size_limit, + unit_price: unit_price, + unit_limit: unit_limit, + is_buy, + }; // Try to get from cache first if let Some(cached_insts) = COMPUTE_BUDGET_CACHE.get(&cache_key) { diff --git a/src/trading/common/nonce_manager.rs b/src/trading/common/nonce_manager.rs index e55b4ea..32ebca2 100755 --- a/src/trading/common/nonce_manager.rs +++ b/src/trading/common/nonce_manager.rs @@ -1,10 +1,7 @@ -use anyhow::anyhow; use solana_hash::Hash; -use solana_sdk::{instruction::Instruction, signature::Keypair, signer::Signer}; +use solana_sdk::{instruction::Instruction, pubkey::Pubkey, signature::Keypair, signer::Signer}; use solana_system_interface::instruction::advance_nonce_account; -use crate::common::nonce_cache::NonceCache; - /// Add nonce advance instruction to the instruction set /// /// Nonce functionality is only used when nonce_pubkey is provided @@ -13,36 +10,25 @@ use crate::common::nonce_cache::NonceCache; pub fn add_nonce_instruction( instructions: &mut Vec, payer: &Keypair, + nonce_account: Option, + current_nonce: Option, ) -> Result<(), anyhow::Error> { - let nonce_cache = NonceCache::get_instance(); - let nonce_info = nonce_cache.get_nonce_info(); - - // Only check if nonce_account exists - if let Some(nonce_pubkey) = nonce_info.nonce_account { - if nonce_info.used { - return Err(anyhow!("Nonce is used")); - } - if nonce_info.current_nonce == Hash::default() { - return Err(anyhow!("Nonce is not ready")); - } - - // Create Solana system nonce advance instruction - using system program ID - let nonce_advance_ix = advance_nonce_account(&nonce_pubkey, &payer.pubkey()); - + if nonce_account.is_some() && current_nonce.is_some() { + let nonce_advance_ix = advance_nonce_account(&nonce_account.unwrap(), &payer.pubkey()); instructions.push(nonce_advance_ix); } - Ok(()) } /// Get blockhash for transaction /// If nonce account is used, return blockhash from nonce, otherwise return the provided recent_blockhash -pub fn get_transaction_blockhash(recent_blockhash: Hash) -> Hash { - let nonce_cache = NonceCache::get_instance(); - let nonce_info = nonce_cache.get_nonce_info(); - - if nonce_info.nonce_account.is_some() { - nonce_info.current_nonce +pub fn get_transaction_blockhash( + recent_blockhash: Hash, + nonce_account: Option, + current_nonce: Option, +) -> Hash { + if nonce_account.is_some() && current_nonce.is_some() { + current_nonce.unwrap() } else { recent_blockhash } diff --git a/src/trading/common/transaction_builder.rs b/src/trading/common/transaction_builder.rs index bf2029f..80262c4 100755 --- a/src/trading/common/transaction_builder.rs +++ b/src/trading/common/transaction_builder.rs @@ -16,12 +16,14 @@ use super::{ compute_budget_manager::compute_budget_instructions, nonce_manager::{add_nonce_instruction, get_transaction_blockhash}, }; -use crate::{common::PriorityFee, trading::MiddlewareManager}; +use crate::{common::SolanaRpcClient, trading::MiddlewareManager}; /// Build standard RPC transaction pub async fn build_transaction( payer: Arc, - priority_fee: &PriorityFee, + rpc: Option>, + unit_limit: u32, + unit_price: u64, business_instructions: Vec, lookup_table_key: Option, recent_blockhash: Hash, @@ -32,14 +34,16 @@ pub async fn build_transaction( with_tip: bool, tip_account: &Pubkey, tip_amount: f64, + nonce_account: Option, + current_nonce: Option, ) -> Result { let mut instructions = Vec::with_capacity(business_instructions.len() + 5); // Add nonce instruction - if is_buy { - if let Err(e) = add_nonce_instruction(&mut instructions, payer.as_ref()) { - return Err(e); - } + if let Err(e) = + add_nonce_instruction(&mut instructions, payer.as_ref(), nonce_account, current_nonce) + { + return Err(e); } // Add tip transfer instruction @@ -53,9 +57,9 @@ pub async fn build_transaction( // Add compute budget instructions instructions.extend(compute_budget_instructions( - priority_fee, + unit_price, + unit_limit, data_size_limit, - !with_tip, is_buy, )); @@ -63,11 +67,11 @@ pub async fn build_transaction( instructions.extend(business_instructions); // Get blockhash for transaction - let blockhash = - if is_buy { get_transaction_blockhash(recent_blockhash) } else { recent_blockhash }; + let blockhash = get_transaction_blockhash(recent_blockhash, nonce_account, current_nonce); // Get address lookup table accounts - let address_lookup_table_accounts = get_address_lookup_table_accounts(lookup_table_key).await; + let address_lookup_table_accounts = + get_address_lookup_table_accounts(rpc, lookup_table_key).await; // Build transaction build_versioned_transaction( diff --git a/src/trading/core/parallel.rs b/src/trading/core/parallel.rs index a386875..e0142b2 100755 --- a/src/trading/core/parallel.rs +++ b/src/trading/core/parallel.rs @@ -8,7 +8,7 @@ use tokio::sync::mpsc; use tokio::task::JoinHandle; use crate::{ - common::PriorityFee, + common::{GasFeeStrategy, SolanaRpcClient}, swqos::{SwqosClient, SwqosType, TradeType}, trading::{common::build_transaction, BuyParams, MiddlewareManager, SellParams}, }; @@ -21,10 +21,12 @@ pub async fn buy_parallel_execute( parallel_execute( params.swqos_clients, params.payer, + params.rpc, instructions, - params.priority_fee, params.lookup_table_key, params.recent_blockhash, + params.nonce_account, + params.current_nonce, params.data_size_limit, params.middleware_manager, protocol_name, @@ -43,10 +45,12 @@ pub async fn sell_parallel_execute( parallel_execute( params.swqos_clients, params.payer, + params.rpc, instructions, - params.priority_fee, params.lookup_table_key, params.recent_blockhash, + params.nonce_account, + params.current_nonce, 0, params.middleware_manager, protocol_name, @@ -61,10 +65,12 @@ pub async fn sell_parallel_execute( async fn parallel_execute( swqos_clients: Vec>, payer: Arc, + rpc: Option>, instructions: Vec, - priority_fee: Arc, lookup_table_key: Option, recent_blockhash: Hash, + nonce_account: Option, + current_nonce: Option, data_size_limit: u32, middleware_manager: Option>, protocol_name: &'static str, @@ -72,68 +78,74 @@ async fn parallel_execute( wait_transaction_confirmed: bool, with_tip: bool, ) -> Result { + if swqos_clients.is_empty() { + return Err(anyhow!("swqos_clients is empty")); + } + if !with_tip + && swqos_clients + .iter() + .find(|swqos| matches!(swqos.get_swqos_type(), SwqosType::Default)) + .is_none() + { + return Err(anyhow!("No Rpc Default Swqos configured.")); + } let cores = core_affinity::get_core_ids().unwrap(); let mut handles: Vec>> = Vec::with_capacity(swqos_clients.len()); - if is_buy && with_tip && priority_fee.buy_tip_fees.is_empty() { - return Err(anyhow!("buy_tip_fees is empty")); - } - if !is_buy && with_tip && priority_fee.sell_tip_fees.is_empty() { - return Err(anyhow!("sell_tip_fees is empty")); - } - let instructions = Arc::new(instructions); - for i in 0..swqos_clients.len() { - let swqos_client = swqos_clients[i].clone(); - if !with_tip && !matches!(swqos_client.get_swqos_type(), SwqosType::Default) { - continue; - } + // 预先计算所有有效的组合 + let task_configs: Vec<_> = swqos_clients + .iter() + .enumerate() + .filter(|(_, swqos_client)| { + with_tip || matches!(swqos_client.get_swqos_type(), SwqosType::Default) + }) + .flat_map(|(i, swqos_client)| { + let gas_fee_strategy_configs = GasFeeStrategy::get_strategies(if is_buy { + TradeType::Buy + } else { + TradeType::Sell + }); + gas_fee_strategy_configs + .into_iter() + .filter(|config| config.0.eq(&swqos_client.get_swqos_type())) + .map(move |config| (i, swqos_client.clone(), config)) + }) + .collect(); + + if task_configs.is_empty() { + return Err(anyhow!("No available gas fee strategy configs. Please configure GasFeeStrategy for specific SwqosType.")); + } + + for (i, swqos_client, gas_fee_strategy_config) in task_configs { + let core_id = cores[i % cores.len()]; let payer = payer.clone(); let instructions = instructions.clone(); - let priority_fee = priority_fee.clone(); - let core_id = cores[i % cores.len()]; - let middleware_manager = middleware_manager.clone(); + let swqos_type = swqos_client.get_swqos_type(); + let tip_account_str = swqos_client.get_tip_account()?; + let tip_account = Arc::new(Pubkey::from_str(&tip_account_str).unwrap_or_default()); + + let tip = gas_fee_strategy_config.2.tip; + let unit_limit = gas_fee_strategy_config.2.cu_limit; + let unit_price = gas_fee_strategy_config.2.cu_price; + let swqos_type = swqos_type.clone(); + let tip_account = tip_account.clone(); + let rpc = rpc.clone(); let handle = tokio::spawn(async move { core_affinity::set_for_current(core_id); - let swqos_type = swqos_client.get_swqos_type(); let mut start = Instant::now(); - let tip_account_str = swqos_client.get_tip_account()?; - let tip_account = Arc::new(Pubkey::from_str(&tip_account_str).unwrap_or_default()); - - let tip_amount = if with_tip { - if is_buy { - if priority_fee.buy_tip_fees.len() > i { - priority_fee.buy_tip_fees[i] - } else { - println!( - "❗️❗️❗️[{:?}] - Using buy_tip_fees[0]: {:?}", - swqos_type, priority_fee.buy_tip_fees[0] - ); - priority_fee.buy_tip_fees[0] - } - } else { - if priority_fee.sell_tip_fees.len() > i { - priority_fee.sell_tip_fees[i] - } else { - println!( - "❗️❗️❗️[{:?}] - Using sell_tip_fees[0]: {:?}", - swqos_type, priority_fee.sell_tip_fees[0] - ); - priority_fee.sell_tip_fees[0] - } - } - } else { - 0.0 - }; + let tip_amount = if with_tip { tip } else { 0.0 }; let transaction = build_transaction( payer, - &priority_fee, + rpc, + unit_limit, + unit_price, instructions.as_ref().clone(), lookup_table_key, recent_blockhash, @@ -144,12 +156,15 @@ async fn parallel_execute( swqos_type != SwqosType::Default, &tip_account, tip_amount, + nonce_account, + current_nonce, ) .await?; println!( - "[{:?}] - Building transaction instructions: {:?}", + "[{:?}] - [{:?}] - Building transaction instructions: {:?}", swqos_type, + gas_fee_strategy_config.1, start.elapsed() ); @@ -163,8 +178,9 @@ async fn parallel_execute( .await?; println!( - "[{:?}] - Submitting transaction instructions: {:?}", + "[{:?}] - [{:?}] - Submitting transaction instructions: {:?}", swqos_type, + gas_fee_strategy_config.1, start.elapsed() ); @@ -178,7 +194,7 @@ async fn parallel_execute( handles.push(handle); } // Return as soon as any one succeeds - let (tx, mut rx) = mpsc::channel(swqos_clients.len()); + let (tx, mut rx) = mpsc::channel(handles.len()); // Start monitoring tasks for handle in handles { diff --git a/src/trading/core/params.rs b/src/trading/core/params.rs index ccc535e..41d6df4 100755 --- a/src/trading/core/params.rs +++ b/src/trading/core/params.rs @@ -1,6 +1,6 @@ use super::traits::ProtocolParams; use crate::common::bonding_curve::BondingCurveAccount; -use crate::common::{PriorityFee, SolanaRpcClient}; +use crate::common::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; @@ -24,7 +24,6 @@ pub struct BuyParams { pub mint: Pubkey, pub sol_amount: u64, pub slippage_basis_points: Option, - pub priority_fee: Arc, pub lookup_table_key: Option, pub recent_blockhash: Hash, pub data_size_limit: u32, @@ -36,6 +35,8 @@ pub struct BuyParams { pub create_wsol_ata: bool, pub close_wsol_ata: bool, pub create_mint_ata: bool, + pub nonce_account: Option, + pub current_nonce: Option, } /// Sell parameters @@ -46,7 +47,6 @@ pub struct SellParams { pub mint: Pubkey, pub token_amount: Option, pub slippage_basis_points: Option, - pub priority_fee: Arc, pub lookup_table_key: Option, pub recent_blockhash: Hash, pub wait_transaction_confirmed: bool, @@ -57,6 +57,8 @@ pub struct SellParams { pub middleware_manager: Option>, pub create_wsol_ata: bool, pub close_wsol_ata: bool, + pub nonce_account: Option, + pub current_nonce: Option, } impl std::fmt::Debug for BuyParams { diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 184fd35..0c003a6 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,7 +1,5 @@ pub mod calc; pub mod price; - -use crate::solana_streamer_sdk::streaming::event_parser::protocols::pumpfun::PumpFunTradeEvent; use crate::trading; use crate::SolanaTrade; use solana_sdk::pubkey::Pubkey; @@ -57,107 +55,4 @@ impl SolanaTrade { pub async fn close_token_account(&self, mint: &Pubkey) -> Result<(), anyhow::Error> { trading::common::utils::close_token_account(&self.rpc, self.payer.as_ref(), mint).await } - - // -------------------------------- PumpFun -------------------------------- - - #[deprecated(since = "0.6.7", note = "This function is deprecated and will be removed in a future version")] - #[inline] - pub fn get_pumpfun_token_buy_price(&self, amount: u64, trade_info: &PumpFunTradeEvent) -> u64 { - crate::instruction::utils::pumpfun::get_buy_price(amount, trade_info) - } - - #[deprecated(since = "0.6.7", note = "This function is deprecated and will be removed in a future version")] - #[inline] - pub async fn get_pumpfun_token_current_price( - &self, - mint: &Pubkey, - ) -> Result { - let (bonding_curve, _) = - crate::instruction::utils::pumpfun::fetch_bonding_curve_account(&self.rpc, mint) - .await?; - - let virtual_sol_reserves = bonding_curve.virtual_sol_reserves; - let virtual_token_reserves = bonding_curve.virtual_token_reserves; - - Ok(price::pumpfun::price_token_in_sol(virtual_sol_reserves, virtual_token_reserves)) - } - - #[deprecated(since = "0.6.7", note = "This function is deprecated and will be removed in a future version")] - #[inline] - pub async fn get_pumpfun_token_real_sol_reserves( - &self, - mint: &Pubkey, - ) -> Result { - let (bonding_curve, _) = - crate::instruction::utils::pumpfun::fetch_bonding_curve_account(&self.rpc, mint) - .await?; - - let actual_sol_reserves = bonding_curve.real_sol_reserves; - - Ok(actual_sol_reserves) - } - - #[deprecated(since = "0.6.7", note = "This function is deprecated and will be removed in a future version")] - #[inline] - pub async fn get_pumpfun_token_creator(&self, mint: &Pubkey) -> Result { - let (bonding_curve, _) = - crate::instruction::utils::pumpfun::fetch_bonding_curve_account(&self.rpc, mint) - .await?; - - let creator = bonding_curve.creator; - - Ok(creator) - } - - // -------------------------------- PumpSwap -------------------------------- - - #[deprecated(since = "0.6.7", note = "This function is deprecated and will be removed in a future version")] - #[inline] - pub async fn get_pumpswap_token_current_price( - &self, - pool_address: &Pubkey, - ) -> Result { - let pool = crate::instruction::utils::pumpswap::fetch_pool(&self.rpc, pool_address).await?; - - let (base_amount, quote_amount) = - crate::instruction::utils::pumpswap::get_token_balances(&pool, &self.rpc).await?; - - // Calculate price using constant product formula (x * y = k) - // Price = quote_amount / base_amount - if base_amount == 0 { - return Err(anyhow::anyhow!("Base amount is zero, cannot calculate price")); - } - - let price = quote_amount as f64 / base_amount as f64; - - Ok(price) - } - - #[deprecated(since = "0.6.7", note = "This function is deprecated and will be removed in a future version")] - #[inline] - pub async fn get_pumpswap_token_real_sol_reserves( - &self, - pool_address: &Pubkey, - ) -> Result { - let pool = crate::instruction::utils::pumpswap::fetch_pool(&self.rpc, pool_address).await?; - - let (_, quote_amount) = - crate::instruction::utils::pumpswap::get_token_balances(&pool, &self.rpc).await?; - - Ok(quote_amount) - } - - #[deprecated(since = "0.6.7", note = "This function is deprecated and will be removed in a future version")] - #[inline] - pub async fn get_pumpswap_payer_token_balance( - &self, - pool_address: &Pubkey, - ) -> Result { - let pool = crate::instruction::utils::pumpswap::fetch_pool(&self.rpc, pool_address).await?; - - let (base_amount, _) = - crate::instruction::utils::pumpswap::get_token_balances(&pool, &self.rpc).await?; - - Ok(base_amount) - } }