diff --git a/Cargo.toml b/Cargo.toml index 3e54c6e..905c016 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sol-trade-sdk" -version = "0.4.0" +version = "0.4.1" edition = "2021" authors = ["William ", "sgxiang ", "wei <1415121722@qq.com>"] repository = "https://github.com/0xfnzero/sol-trade-sdk" @@ -13,7 +13,7 @@ readme = "README.md" crate-type = ["cdylib", "rlib"] [dependencies] -solana-streamer-sdk = "0.3.3" +solana-streamer-sdk = "0.3.4" solana-sdk = "2.3.0" solana-client = "2.3.6" solana-program = "2.3.0" diff --git a/README.md b/README.md index 38b2771..5c50337 100755 --- a/README.md +++ b/README.md @@ -9,12 +9,14 @@ A comprehensive Rust SDK for seamless interaction with Solana DEX trading progra 2. **PumpSwap Trading**: Support for PumpSwap pool trading operations 3. **Bonk Trading**: Support for Bonk trading operations 4. **Raydium CPMM Trading**: Support for Raydium CPMM (Concentrated Pool Market Maker) trading operations -5. **Event Subscription**: Subscribe to PumpFun, PumpSwap, Bonk, and Raydium CPMM program trading events -6. **Yellowstone gRPC**: Subscribe to program events using Yellowstone gRPC -7. **ShredStream Support**: Subscribe to program events using ShredStream -8. **Multiple MEV Protection**: Support for Jito, Nextblock, ZeroSlot, Temporal, Bloxroute, and other services -9. **Concurrent Trading**: Send transactions using multiple MEV services simultaneously; the fastest succeeds while others fail -10. **Unified Trading Interface**: Use unified trading protocol enums for 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, 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 ## Installation @@ -31,14 +33,14 @@ Add the dependency to your `Cargo.toml`: ```toml # Add to your Cargo.toml -sol-trade-sdk = { path = "./sol-trade-sdk", version = "0.4.0" } +sol-trade-sdk = { path = "./sol-trade-sdk", version = "0.4.1" } ``` ### Use crates.io ```toml # Add to your Cargo.toml -sol-trade-sdk = "0.4.0" +sol-trade-sdk = "0.4.1" ``` ## Usage Examples @@ -536,7 +538,62 @@ async fn test_raydium_cpmm() -> Result<(), Box> { } ``` -### 6. Bonk Trading Operations +### 6. Raydium AMM V4 Trading Operations + +```rust +use sol_trade_sdk::trading::core::params::RaydiumAmmV4Params; + +async fn test_raydium_amm_v4() -> Result<(), Box> { + println!("Testing Raydium AMM V4 trading..."); + + let trade_client = test_create_solana_trade_client().await?; + + let mint_pubkey = Pubkey::from_str("xxxxxxx")?; // Token address + let buy_sol_cost = 100_000; // 0.0001 SOL (in lamports) + let slippage_basis_points = Some(100); // 1% slippage + let recent_blockhash = trade_client.rpc.get_latest_blockhash().await?; + let amm_address = Pubkey::from_str("xxxxxx")?; // AMM pool address + + println!("Buying tokens from Raydium AMM V4..."); + trade_client.buy( + DexType::RaydiumAmmV4, + mint_pubkey, + None, + buy_sol_cost, + slippage_basis_points, + recent_blockhash, + None, + // Through RPC call, adds latency, or from_amm_info_and_reserves or manually initialize RaydiumAmmV4Params + Box::new( + RaydiumAmmV4Params::from_amm_address_by_rpc(&trade_client.rpc, amm_address).await?, + ), + None, + ).await?; + + println!("Selling tokens from Raydium AMM V4..."); + let amount_token = 100_000_000; // Token amount to sell + + trade_client.sell( + DexType::RaydiumAmmV4, + mint_pubkey, + None, + amount_token, + slippage_basis_points, + recent_blockhash, + None, + false, + // Through RPC call, adds latency, or from_amm_info_and_reserves or manually initialize RaydiumAmmV4Params + Box::new( + RaydiumAmmV4Params::from_amm_address_by_rpc(&trade_client.rpc, amm_address).await?, + ), + None, + ).await?; + + Ok(()) +} +``` + +### 7. Bonk Trading Operations ```rust @@ -679,7 +736,138 @@ async fn test_bonk() -> Result<(), Box> { } ``` -### 7. Custom Priority Fee Configuration +### 8. Middleware System + +The SDK provides a powerful middleware system that allows you to modify, add, or remove instructions before transaction execution. This gives you tremendous flexibility to customize trading behavior. + +#### 8.1 Using Built-in Logging Middleware + +```rust +use sol_trade_sdk::{ + trading::{ + factory::DexType, + middleware::builtin::LoggingMiddleware, + MiddlewareManager, + }, +}; + +async fn test_middleware() -> AnyResult<()> { + let mut client = test_create_solana_trade_client().await?; + + // SDK example middleware that prints instruction information + // You can reference LoggingMiddleware to implement the InstructionMiddleware trait for your own middleware + let middleware_manager = MiddlewareManager::new() + .add_middleware(Box::new(LoggingMiddleware)); + + client = client.with_middleware_manager(middleware_manager); + + let creator = Pubkey::from_str("11111111111111111111111111111111")?; + let mint_pubkey = Pubkey::from_str("xxxxx")?; + let buy_sol_cost = 100_000; + let slippage_basis_points = Some(100); + let recent_blockhash = client.rpc.get_latest_blockhash().await?; + let pool_address = Pubkey::from_str("xxxx")?; + + // Buy tokens + println!("Buying tokens from PumpSwap..."); + client + .buy( + DexType::PumpSwap, + mint_pubkey, + Some(creator), + buy_sol_cost, + slippage_basis_points, + recent_blockhash, + None, + Box::new(PumpSwapParams::from_pool_address_by_rpc(&client.rpc, &pool_address).await?), + None, + ) + .await?; + Ok(()) +} +``` + +#### 8.2 Creating Custom Middleware + +You can create custom middleware by implementing the `InstructionMiddleware` trait: + +```rust +use sol_trade_sdk::trading::middleware::traits::InstructionMiddleware; +use anyhow::Result; +use solana_sdk::instruction::Instruction; + +/// Custom middleware example - Add additional instructions +#[derive(Clone)] +pub struct CustomMiddleware; + +impl InstructionMiddleware for CustomMiddleware { + fn name(&self) -> &'static str { + "CustomMiddleware" + } + + fn process_protocol_instructions( + &self, + protocol_instructions: Vec, + protocol_name: String, + is_buy: bool, + ) -> Result> { + println!("Custom middleware processing, protocol: {}", protocol_name); + + // Here you can: + // 1. Modify existing instructions + // 2. Add new instructions + // 3. Remove specific instructions + + // Example: Add a custom instruction at the beginning + // let custom_instruction = create_your_custom_instruction(); + // instructions.insert(0, custom_instruction); + + Ok(protocol_instructions) + } + + fn process_full_instructions( + &self, + full_instructions: Vec, + protocol_name: String, + is_buy: bool, + ) -> Result> { + println!("Custom middleware processing, instruction count: {}", full_instructions.len()); + Ok(full_instructions) + } + + fn clone_box(&self) -> Box { + Box::new(self.clone()) + } +} + +// Using custom middleware +async fn test_custom_middleware() -> AnyResult<()> { + let mut client = test_create_solana_trade_client().await?; + + let middleware_manager = MiddlewareManager::new() + .add_middleware(Box::new(LoggingMiddleware)) // Logging middleware + .add_middleware(Box::new(CustomMiddleware)); + + client = client.with_middleware_manager(middleware_manager); + + // Now all transactions will be processed through your middleware + // ... + Ok(()) +} +``` + +#### 8.3 Middleware Execution Order + +Middleware executes in the order they are added: + +```rust +let middleware_manager = MiddlewareManager::new() + .add_middleware(Box::new(FirstMiddleware)) // Executes first + .add_middleware(Box::new(SecondMiddleware)) // Executes second + .add_middleware(Box::new(ThirdMiddleware)); // Executes last +``` + +### 9. Custom Priority Fee Configuration ```rust use sol_trade_sdk::common::PriorityFee; @@ -711,6 +899,7 @@ let trade_config = TradeConfig { - **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 ## MEV Protection Services @@ -724,9 +913,9 @@ let trade_config = TradeConfig { ### Unified Trading Interface -- **TradingProtocol Enum**: Use unified protocol enums (PumpFun, PumpSwap, Bonk, RaydiumCpmm) +- **TradingProtocol Enum**: Use unified protocol enums (PumpFun, PumpSwap, Bonk, RaydiumCpmm, RaydiumAmmV4) - **Unified buy/sell Methods**: All protocols use the same trading method signatures -- **Protocol-specific Parameters**: Each protocol has its own parameter structure (PumpFunParams, RaydiumCpmmParams, etc.) +- **Protocol-specific Parameters**: Each protocol has its own parameter structure (PumpFunParams, RaydiumCpmmParams, RaydiumAmmV4Params, etc.) ### Event Parsing System @@ -744,6 +933,24 @@ let trade_config = TradeConfig { 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 ``` @@ -755,20 +962,32 @@ src/ ├── 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 +│ ├── 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 ``` diff --git a/README_CN.md b/README_CN.md index 7178686..2632c33 100755 --- a/README_CN.md +++ b/README_CN.md @@ -9,12 +9,14 @@ 2. **PumpSwap 交易**: 支持 PumpSwap 池的交易操作 3. **Bonk 交易**: 支持 Bonk 的交易操作 4. **Raydium CPMM 交易**: 支持 Raydium CPMM (Concentrated Pool Market Maker) 的交易操作 -5. **事件订阅**: 订阅 PumpFun、PumpSwap、Bonk 和 Raydium CPMM 程序的交易事件 -6. **Yellowstone gRPC**: 使用 Yellowstone gRPC 订阅程序事件 -7. **ShredStream 支持**: 使用 ShredStream 订阅程序事件 -8. **多种 MEV 保护**: 支持 Jito、Nextblock、ZeroSlot、Temporal、Bloxroute 等服务 -9. **并发交易**: 同时使用多个 MEV 服务发送交易,最快的成功,其他失败 -10. **统一交易接口**: 使用统一的交易协议枚举进行交易操作 +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 等服务 +10. **并发交易**: 同时使用多个 MEV 服务发送交易,最快的成功,其他失败 +11. **统一交易接口**: 使用统一的交易协议枚举进行交易操作 +12. **中间件系统**: 支持自定义指令中间件,可在交易执行前对指令进行修改、添加或移除 ## 安装 @@ -31,14 +33,14 @@ git clone https://github.com/0xfnzero/sol-trade-sdk ```toml # 添加到您的 Cargo.toml -sol-trade-sdk = { path = "./sol-trade-sdk", version = "0.4.0" } +sol-trade-sdk = { path = "./sol-trade-sdk", version = "0.4.1" } ``` ### 使用 crates.io ```toml # 添加到您的 Cargo.toml -sol-trade-sdk = "0.4.0" +sol-trade-sdk = "0.4.1" ``` ## 使用示例 @@ -550,7 +552,62 @@ async fn test_raydium_cpmm() -> Result<(), Box> { } ``` -### 6. Bonk 交易操作 +### 6. Raydium AMM V4 交易操作 + +```rust +use sol_trade_sdk::trading::core::params::RaydiumAmmV4Params; + +async fn test_raydium_amm_v4() -> Result<(), Box> { + println!("Testing Raydium AMM V4 trading..."); + + let trade_client = test_create_solana_trade_client().await?; + + let mint_pubkey = Pubkey::from_str("xxxxxxx")?; // 代币地址 + let buy_sol_cost = 100_000; // 0.0001 SOL(以lamports为单位) + let slippage_basis_points = Some(100); // 1% 滑点 + let recent_blockhash = trade_client.rpc.get_latest_blockhash().await?; + let amm_address = Pubkey::from_str("xxxxxx")?; // AMM 池地址 + + println!("Buying tokens from Raydium AMM V4..."); + trade_client.buy( + DexType::RaydiumAmmV4, + mint_pubkey, + None, + buy_sol_cost, + slippage_basis_points, + recent_blockhash, + None, + // 通过 RPC 调用,会增加延迟,或使用 from_amm_info_and_reserves 或手动初始化 RaydiumAmmV4Params + Box::new( + RaydiumAmmV4Params::from_amm_address_by_rpc(&trade_client.rpc, amm_address).await?, + ), + None, + ).await?; + + println!("Selling tokens from Raydium AMM V4..."); + let amount_token = 100_000_000; // 卖出代币数量 + + trade_client.sell( + DexType::RaydiumAmmV4, + mint_pubkey, + None, + amount_token, + slippage_basis_points, + recent_blockhash, + None, + false, + // 通过 RPC 调用,会增加延迟,或使用 from_amm_info_and_reserves 或手动初始化 RaydiumAmmV4Params + Box::new( + RaydiumAmmV4Params::from_amm_address_by_rpc(&trade_client.rpc, amm_address).await?, + ), + None, + ).await?; + + Ok(()) +} +``` + +### 7. Bonk 交易操作 ```rust use sol_trade_sdk::{ @@ -699,7 +756,138 @@ async fn test_bonk() -> Result<(), Box> { } ``` -### 7. 自定义优先费用配置 +### 8. 中间件系统 + +SDK 提供了强大的中间件系统,允许您在交易执行前对指令进行修改、添加或移除。这为您提供了极大的灵活性来自定义交易行为。 + +#### 8.1 使用内置的日志中间件 + +```rust +use sol_trade_sdk::{ + trading::{ + factory::DexType, + middleware::builtin::LoggingMiddleware, + MiddlewareManager, + }, +}; + +async fn test_middleware() -> AnyResult<()> { + let mut client = test_create_solana_trade_client().await?; + + // SDK 内置的示例中间件,打印指令信息 + // 您可以参考 LoggingMiddleware 来实现 InstructionMiddleware trait 来实现自己的中间件 + let middleware_manager = MiddlewareManager::new() + .add_middleware(Box::new(LoggingMiddleware)); + + client = client.with_middleware_manager(middleware_manager); + + let creator = Pubkey::from_str("11111111111111111111111111111111")?; + let mint_pubkey = Pubkey::from_str("xxxxx")?; + let buy_sol_cost = 100_000; + let slippage_basis_points = Some(100); + let recent_blockhash = client.rpc.get_latest_blockhash().await?; + let pool_address = Pubkey::from_str("xxxx")?; + + // 购买代币 + println!("Buying tokens from PumpSwap..."); + client + .buy( + DexType::PumpSwap, + mint_pubkey, + Some(creator), + buy_sol_cost, + slippage_basis_points, + recent_blockhash, + None, + Box::new(PumpSwapParams::from_pool_address_by_rpc(&client.rpc, &pool_address).await?), + None, + ) + .await?; + Ok(()) +} +``` + +#### 8.2 创建自定义中间件 + +您可以通过实现 `InstructionMiddleware` trait 来创建自定义中间件: + +```rust +use sol_trade_sdk::trading::middleware::traits::InstructionMiddleware; +use anyhow::Result; +use solana_sdk::instruction::Instruction; + +/// 自定义中间件示例 - 添加额外指令 +#[derive(Clone)] +pub struct CustomMiddleware; + +impl InstructionMiddleware for CustomMiddleware { + fn name(&self) -> &'static str { + "CustomMiddleware" + } + + fn process_protocol_instructions( + &self, + protocol_instructions: Vec, + protocol_name: String, + is_buy: bool, + ) -> Result> { + println!("自定义中间件处理中,协议: {}", protocol_name); + + // 在这里您可以: + // 1. 修改现有指令 + // 2. 添加新指令 + // 3. 移除特定指令 + + // 示例:在指令开始前添加一个自定义指令 + // let custom_instruction = create_your_custom_instruction(); + // instructions.insert(0, custom_instruction); + + Ok(protocol_instructions) + } + + fn process_full_instructions( + &self, + full_instructions: Vec, + protocol_name: String, + is_buy: bool, + ) -> Result> { + println!("自定义中间件处理中,指令数量: {}", full_instructions.len()); + Ok(full_instructions) + } + + fn clone_box(&self) -> Box { + Box::new(self.clone()) + } +} + +// 使用自定义中间件 +async fn test_custom_middleware() -> AnyResult<()> { + let mut client = test_create_solana_trade_client().await?; + + let middleware_manager = MiddlewareManager::new() + .add_middleware(Box::new(LoggingMiddleware)) // 日志中间件 + .add_middleware(Box::new(CustomMiddleware)); + + client = client.with_middleware_manager(middleware_manager); + + // 现在所有交易都会通过您的中间件处理 + // ... + Ok(()) +} +``` + +#### 8.3 中间件执行顺序 + +中间件按照添加顺序依次执行: + +```rust +let middleware_manager = MiddlewareManager::new() + .add_middleware(Box::new(FirstMiddleware)) // 第一个执行 + .add_middleware(Box::new(SecondMiddleware)) // 第二个执行 + .add_middleware(Box::new(ThirdMiddleware)); // 最后执行 +``` + +### 9. 自定义优先费用配置 ```rust use sol_trade_sdk::common::PriorityFee; @@ -731,6 +919,7 @@ let trade_config = TradeConfig { - **PumpSwap**: PumpFun 的交换协议 - **Bonk**: 代币发行平台(letsbonk.fun) - **Raydium CPMM**: Raydium 的集中流动性做市商协议 +- **Raydium AMM V4**: Raydium 的自动做市商 V4 协议 ## MEV 保护服务 @@ -744,9 +933,9 @@ let trade_config = TradeConfig { ### 统一交易接口 -- **TradingProtocol 枚举**: 使用统一的协议枚举(PumpFun、PumpSwap、Bonk、RaydiumCpmm) +- **TradingProtocol 枚举**: 使用统一的协议枚举(PumpFun、PumpSwap、Bonk、RaydiumCpmm、RaydiumAmmV4) - **统一的 buy/sell 方法**: 所有协议都使用相同的交易方法签名 -- **协议特定参数**: 每个协议都有自己的参数结构(PumpFunParams、RaydiumCpmmParams 等) +- **协议特定参数**: 每个协议都有自己的参数结构(PumpFunParams、RaydiumCpmmParams、RaydiumAmmV4Params 等) ### 事件解析系统 @@ -764,6 +953,24 @@ let trade_config = TradeConfig { SDK 包含所有支持协议的价格计算工具,位于 `src/utils/price/` 目录。 +## 数量计算工具 + +SDK 提供各种协议的交易数量计算功能,位于 `src/utils/calc/` 目录: + +- **通用计算函数**: 提供通用的手续费计算和除法运算工具 +- **协议特定计算**: 针对每个协议的特定计算逻辑 + - **PumpFun**: 基于联合曲线的代币购买/销售数量计算 + - **PumpSwap**: 支持多种交易对的数量计算 + - **Raydium AMM V4**: 自动做市商池的数量和手续费计算 + - **Raydium CPMM**: 恒定乘积做市商的数量计算 + - **Bonk**: 专门的 Bonk 代币计算逻辑 + +主要功能包括: +- 根据输入金额计算输出数量 +- 手续费计算和分配 +- 滑点保护计算 +- 流动性池状态计算 + ## 项目结构 ``` @@ -775,20 +982,32 @@ src/ ├── 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 价格计算 +│ ├── 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 # 示例程序 ``` diff --git a/src/constants/mod.rs b/src/constants/mod.rs index 70ece3c..472b7bb 100755 --- a/src/constants/mod.rs +++ b/src/constants/mod.rs @@ -4,6 +4,7 @@ pub mod pumpswap; pub mod swqos; pub mod trade; pub mod raydium_cpmm; +pub mod raydium_amm_v4; pub mod decimals; pub mod trade_platform { diff --git a/src/constants/raydium_amm_v4.rs b/src/constants/raydium_amm_v4.rs new file mode 100755 index 0000000..6c44296 --- /dev/null +++ b/src/constants/raydium_amm_v4.rs @@ -0,0 +1,33 @@ +//! Constants used by the crate. +//! +//! This module contains various constants used throughout the crate, including: +//! +//! - Seeds for deriving Program Derived Addresses (PDAs) +//! - Program account addresses and public keys +//! +//! The constants are organized into submodules for better organization: +//! +//! - `seeds`: Contains seed values used for PDA derivation +//! - `accounts`: Contains important program account addresses + +/// Constants used as seeds for deriving PDAs (Program Derived Addresses) +pub mod seeds { + pub const POOL_SEED: &[u8] = b"pool"; +} + +/// Constants related to program accounts and authorities +pub mod accounts { + use solana_sdk::{pubkey, pubkey::Pubkey}; + pub const AUTHORITY: Pubkey = pubkey!("5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1"); + pub const TOKEN_PROGRAM: Pubkey = spl_token::ID; + pub const WSOL_TOKEN_ACCOUNT: Pubkey = pubkey!("So11111111111111111111111111111111111111112"); + pub const RAYDIUM_AMM_V4: Pubkey = pubkey!("675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8"); + + pub const TRADE_FEE_NUMERATOR: u64 = 25; + pub const TRADE_FEE_DENOMINATOR: u64 = 10000; + pub const SWAP_FEE_NUMERATOR: u64 = 25; + pub const SWAP_FEE_DENOMINATOR: u64 = 10000; +} + +pub const SWAP_BASE_IN_DISCRIMINATOR: &[u8] = &[9]; +pub const SWAP_BASE_OUT_DISCRIMINATOR: &[u8] = &[11]; diff --git a/src/instruction/mod.rs b/src/instruction/mod.rs index baab225..81bdcc0 100755 --- a/src/instruction/mod.rs +++ b/src/instruction/mod.rs @@ -1,4 +1,5 @@ pub mod pumpfun; pub mod pumpswap; pub mod bonk; -pub mod raydium_cpmm; \ No newline at end of file +pub mod raydium_cpmm; +pub mod raydium_amm_v4; \ No newline at end of file diff --git a/src/instruction/raydium_amm_v4.rs b/src/instruction/raydium_amm_v4.rs new file mode 100755 index 0000000..4256f9c --- /dev/null +++ b/src/instruction/raydium_amm_v4.rs @@ -0,0 +1,248 @@ +use anyhow::{anyhow, Result}; +use solana_sdk::{instruction::Instruction, signer::Signer}; +use solana_system_interface::instruction::transfer; +use spl_associated_token_account::instruction::create_associated_token_account_idempotent; +use spl_token::instruction::close_account; + +use crate::{ + constants::{ + raydium_amm_v4::{accounts, SWAP_BASE_IN_DISCRIMINATOR}, + trade::trade::DEFAULT_SLIPPAGE, + }, + trading::core::{ + params::{BuyParams, RaydiumAmmV4Params, SellParams}, + traits::InstructionBuilder, + }, + utils::calc::raydium_amm_v4::compute_swap_amount, +}; + +/// Instruction builder for RaydiumCpmm protocol +pub struct RaydiumAmmV4InstructionBuilder; + +#[async_trait::async_trait] +impl InstructionBuilder for RaydiumAmmV4InstructionBuilder { + async fn build_buy_instructions(&self, params: &BuyParams) -> Result> { + if params.sol_amount == 0 { + return Err(anyhow!("Amount cannot be zero")); + } + self.build_buy_instructions_with_accounts(params).await + } + + async fn build_sell_instructions(&self, params: &SellParams) -> Result> { + self.build_sell_instructions_with_accounts(params).await + } +} + +impl RaydiumAmmV4InstructionBuilder { + /// Build buy instructions with provided account information + async fn build_buy_instructions_with_accounts( + &self, + params: &BuyParams, + ) -> Result> { + let protocol_params = params + .protocol_params + .as_any() + .downcast_ref::() + .ok_or_else(|| anyhow!("Invalid protocol params for RaydiumCpmm"))?; + + let wsol_token_account = spl_associated_token_account::get_associated_token_address( + ¶ms.payer.pubkey(), + &accounts::WSOL_TOKEN_ACCOUNT, + ); + + let is_base_in = protocol_params.coin_mint == accounts::WSOL_TOKEN_ACCOUNT; + + let amount_in: u64 = params.sol_amount; + let swap_result = compute_swap_amount( + protocol_params.coin_reserve, + protocol_params.pc_reserve, + is_base_in, + amount_in, + params.slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE), + ); + let minimum_amount_out = swap_result.min_amount_out; + + let mut instructions = vec![]; + + if protocol_params.auto_handle_wsol { + // Handle wSOL + instructions.push( + // Create wSOL ATA account if it doesn't exist + create_associated_token_account_idempotent( + ¶ms.payer.pubkey(), + ¶ms.payer.pubkey(), + &accounts::WSOL_TOKEN_ACCOUNT, + &accounts::TOKEN_PROGRAM, + ), + ); + instructions.push( + // Transfer SOL to wSOL ATA account + transfer(¶ms.payer.pubkey(), &wsol_token_account, amount_in), + ); + + // Sync wSOL balance + instructions.push( + spl_token::instruction::sync_native(&accounts::TOKEN_PROGRAM, &wsol_token_account) + .unwrap(), + ); + } + + instructions.push(create_associated_token_account_idempotent( + ¶ms.payer.pubkey(), + ¶ms.payer.pubkey(), + ¶ms.mint, + &accounts::TOKEN_PROGRAM, + )); + + let user_source_token_account = spl_associated_token_account::get_associated_token_address( + ¶ms.payer.pubkey(), + &accounts::WSOL_TOKEN_ACCOUNT, + ); + let user_destination_token_account = + spl_associated_token_account::get_associated_token_address( + ¶ms.payer.pubkey(), + ¶ms.mint, + ); + + // Create buy instruction + let accounts = vec![ + solana_sdk::instruction::AccountMeta::new_readonly(accounts::TOKEN_PROGRAM, false), // Token Program (readonly) + solana_sdk::instruction::AccountMeta::new(protocol_params.amm, false), // Amm + solana_sdk::instruction::AccountMeta::new_readonly(accounts::AUTHORITY, false), // Authority (readonly) + solana_sdk::instruction::AccountMeta::new(protocol_params.amm, false), // Amm Open Orders + solana_sdk::instruction::AccountMeta::new(protocol_params.token_coin, false), // Pool Coin Token Account + solana_sdk::instruction::AccountMeta::new(protocol_params.token_pc, false), // Pool Pc Token Account + solana_sdk::instruction::AccountMeta::new(protocol_params.amm, false), // Serum Program + solana_sdk::instruction::AccountMeta::new(protocol_params.amm, false), // Serum Market + solana_sdk::instruction::AccountMeta::new(protocol_params.amm, false), // Serum Bids + solana_sdk::instruction::AccountMeta::new(protocol_params.amm, false), // Serum Asks + solana_sdk::instruction::AccountMeta::new(protocol_params.amm, false), // Serum Event Queue + solana_sdk::instruction::AccountMeta::new(protocol_params.amm, false), // Serum Coin Vault Account + solana_sdk::instruction::AccountMeta::new(protocol_params.amm, false), // Serum Pc Vault Account + solana_sdk::instruction::AccountMeta::new(protocol_params.amm, false), // Serum Vault Signer + solana_sdk::instruction::AccountMeta::new(user_source_token_account, false), // User Source Token Account + solana_sdk::instruction::AccountMeta::new(user_destination_token_account, false), // User Destination Token Account + solana_sdk::instruction::AccountMeta::new(params.payer.pubkey(), true), // User Source Owner + ]; + // Create instruction data + let mut data = vec![]; + data.extend_from_slice(&SWAP_BASE_IN_DISCRIMINATOR); + data.extend_from_slice(&amount_in.to_le_bytes()); + data.extend_from_slice(&minimum_amount_out.to_le_bytes()); + + instructions.push(Instruction { program_id: accounts::RAYDIUM_AMM_V4, accounts, data }); + + if protocol_params.auto_handle_wsol { + // Close wSOL ATA account, reclaim rent + instructions.push( + spl_token::instruction::close_account( + &accounts::TOKEN_PROGRAM, + &wsol_token_account, + ¶ms.payer.pubkey(), + ¶ms.payer.pubkey(), + &[], + ) + .unwrap(), + ); + } + + Ok(instructions) + } + + /// Build sell instructions with provided account information + async fn build_sell_instructions_with_accounts( + &self, + params: &SellParams, + ) -> Result> { + let protocol_params = params + .protocol_params + .as_any() + .downcast_ref::() + .ok_or_else(|| anyhow!("Invalid protocol params for RaydiumCpmm"))?; + + if params.token_amount.is_none() || params.token_amount.unwrap_or(0) == 0 { + return Err(anyhow!("Token amount is not set")); + } + + let wsol_token_account = spl_associated_token_account::get_associated_token_address( + ¶ms.payer.pubkey(), + &accounts::WSOL_TOKEN_ACCOUNT, + ); + + let is_base_in = protocol_params.pc_mint == accounts::WSOL_TOKEN_ACCOUNT; + let swap_result = compute_swap_amount( + protocol_params.coin_reserve, + protocol_params.pc_reserve, + is_base_in, + params.token_amount.unwrap_or(0), + params.slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE), + ); + let minimum_amount_out = swap_result.min_amount_out; + + let mut instructions = vec![]; + + // Handle wSOL + instructions.push( + // Create wSOL ATA account if it doesn't exist + create_associated_token_account_idempotent( + ¶ms.payer.pubkey(), + ¶ms.payer.pubkey(), + &accounts::WSOL_TOKEN_ACCOUNT, + &accounts::TOKEN_PROGRAM, + ), + ); + + let user_source_token_account = spl_associated_token_account::get_associated_token_address( + ¶ms.payer.pubkey(), + ¶ms.mint, + ); + let user_destination_token_account = + spl_associated_token_account::get_associated_token_address( + ¶ms.payer.pubkey(), + &accounts::WSOL_TOKEN_ACCOUNT, + ); + + // Create buy instruction + let accounts = vec![ + solana_sdk::instruction::AccountMeta::new_readonly(accounts::TOKEN_PROGRAM, false), // Token Program (readonly) + solana_sdk::instruction::AccountMeta::new(protocol_params.amm, false), // Amm + solana_sdk::instruction::AccountMeta::new_readonly(accounts::AUTHORITY, false), // Authority (readonly) + solana_sdk::instruction::AccountMeta::new(protocol_params.amm, false), // Amm Open Orders + solana_sdk::instruction::AccountMeta::new(protocol_params.token_coin, false), // Pool Coin Token Account + solana_sdk::instruction::AccountMeta::new(protocol_params.token_pc, false), // Pool Pc Token Account + solana_sdk::instruction::AccountMeta::new(protocol_params.amm, false), // Serum Program + solana_sdk::instruction::AccountMeta::new(protocol_params.amm, false), // Serum Market + solana_sdk::instruction::AccountMeta::new(protocol_params.amm, false), // Serum Bids + solana_sdk::instruction::AccountMeta::new(protocol_params.amm, false), // Serum Asks + solana_sdk::instruction::AccountMeta::new(protocol_params.amm, false), // Serum Event Queue + solana_sdk::instruction::AccountMeta::new(protocol_params.amm, false), // Serum Coin Vault Account + solana_sdk::instruction::AccountMeta::new(protocol_params.amm, false), // Serum Pc Vault Account + solana_sdk::instruction::AccountMeta::new(protocol_params.amm, false), // Serum Vault Signer + solana_sdk::instruction::AccountMeta::new(user_source_token_account, false), // User Source Token Account + solana_sdk::instruction::AccountMeta::new(user_destination_token_account, false), // User Destination Token Account + solana_sdk::instruction::AccountMeta::new(params.payer.pubkey(), true), // User Source Owner + ]; + // Create instruction data + let mut data = vec![]; + data.extend_from_slice(&SWAP_BASE_IN_DISCRIMINATOR); + data.extend_from_slice(¶ms.token_amount.unwrap_or(0).to_le_bytes()); + data.extend_from_slice(&minimum_amount_out.to_le_bytes()); + + instructions.push(Instruction { program_id: accounts::RAYDIUM_AMM_V4, accounts, data }); + + if protocol_params.auto_handle_wsol { + instructions.push( + close_account( + &accounts::TOKEN_PROGRAM, + &wsol_token_account, + ¶ms.payer.pubkey(), + ¶ms.payer.pubkey(), + &[¶ms.payer.pubkey()], + ) + .unwrap(), + ); + } + + Ok(instructions) + } +} diff --git a/src/lib.rs b/src/lib.rs index 19d0934..85c31e8 100755 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,10 +11,12 @@ use crate::swqos::SwqosConfig; use crate::trading::core::params::BonkParams; use crate::trading::core::params::PumpFunParams; use crate::trading::core::params::PumpSwapParams; +use crate::trading::core::params::RaydiumAmmV4Params; use crate::trading::core::params::RaydiumCpmmParams; use crate::trading::core::traits::ProtocolParams; use crate::trading::factory::DexType; use crate::trading::BuyParams; +use crate::trading::MiddlewareManager; use crate::trading::SellParams; use crate::trading::TradeFactory; use common::{PriorityFee, SolanaRpcClient, TradeConfig}; @@ -31,6 +33,7 @@ pub struct SolanaTrade { pub swqos_clients: Vec>, pub priority_fee: PriorityFee, pub trade_config: TradeConfig, + pub middleware_manager: Option>, } static INSTANCE: Mutex>> = Mutex::new(None); @@ -43,6 +46,7 @@ impl Clone for SolanaTrade { swqos_clients: self.swqos_clients.clone(), priority_fee: self.priority_fee.clone(), trade_config: self.trade_config.clone(), + middleware_manager: self.middleware_manager.clone(), } } } @@ -83,10 +87,7 @@ 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)); let instance = Self { payer, @@ -94,6 +95,7 @@ impl SolanaTrade { swqos_clients, priority_fee, trade_config: trade_config.clone(), + middleware_manager: None, }; let mut current = INSTANCE.lock().unwrap(); @@ -102,6 +104,11 @@ impl SolanaTrade { instance } + 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 pub fn get_rpc(&self) -> &Arc { &self.rpc @@ -180,9 +187,9 @@ impl SolanaTrade { ) -> Result<(), anyhow::Error> { let executor = TradeFactory::create_executor(dex_type.clone()); let protocol_params = extension_params; - + let final_lookup_table_key = lookup_table_key.or(self.trade_config.lookup_table_key); - + let buy_params = BuyParams { rpc: Some(self.rpc.clone()), payer: self.payer.clone(), @@ -199,39 +206,31 @@ impl SolanaTrade { let mut priority_fee = buy_params.priority_fee.clone(); if custom_buy_tip_fee.is_some() { priority_fee.buy_tip_fee = custom_buy_tip_fee.unwrap(); - priority_fee.buy_tip_fees = priority_fee - .buy_tip_fees - .iter() - .map(|_| custom_buy_tip_fee.unwrap()) - .collect(); + priority_fee.buy_tip_fees = + priority_fee.buy_tip_fees.iter().map(|_| custom_buy_tip_fee.unwrap()).collect(); } let buy_with_tip_params = buy_params.clone().with_tip(self.swqos_clients.clone()); // Validate protocol params let is_valid_params = match dex_type { - DexType::PumpFun => protocol_params - .as_any() - .downcast_ref::() - .is_some(), - DexType::PumpSwap => protocol_params - .as_any() - .downcast_ref::() - .is_some(), - DexType::Bonk => protocol_params - .as_any() - .downcast_ref::() - .is_some(), - DexType::RaydiumCpmm => protocol_params - .as_any() - .downcast_ref::() - .is_some(), + DexType::PumpFun => protocol_params.as_any().downcast_ref::().is_some(), + DexType::PumpSwap => { + protocol_params.as_any().downcast_ref::().is_some() + } + DexType::Bonk => protocol_params.as_any().downcast_ref::().is_some(), + DexType::RaydiumCpmm => { + protocol_params.as_any().downcast_ref::().is_some() + } + DexType::RaydiumAmmV4 => { + protocol_params.as_any().downcast_ref::().is_some() + } }; if !is_valid_params { return Err(anyhow::anyhow!("Invalid protocol params for Trade")); } - executor.buy_with_tip(buy_with_tip_params).await + executor.buy_with_tip(buy_with_tip_params, self.middleware_manager.clone()).await } /// Execute a sell order for a specified token @@ -302,9 +301,9 @@ impl SolanaTrade { ) -> Result<(), anyhow::Error> { let executor = TradeFactory::create_executor(dex_type.clone()); let protocol_params = extension_params; - + let final_lookup_table_key = lookup_table_key.or(self.trade_config.lookup_table_key); - + let sell_params = SellParams { rpc: Some(self.rpc.clone()), payer: self.payer.clone(), @@ -320,32 +319,24 @@ impl SolanaTrade { let mut priority_fee = sell_params.priority_fee.clone(); if custom_buy_tip_fee.is_some() { priority_fee.buy_tip_fee = custom_buy_tip_fee.unwrap(); - priority_fee.buy_tip_fees = priority_fee - .buy_tip_fees - .iter() - .map(|_| custom_buy_tip_fee.unwrap()) - .collect(); + priority_fee.buy_tip_fees = + priority_fee.buy_tip_fees.iter().map(|_| custom_buy_tip_fee.unwrap()).collect(); } let sell_with_tip_params = sell_params.clone().with_tip(self.swqos_clients.clone()); // Validate protocol params let is_valid_params = match dex_type { - DexType::PumpFun => protocol_params - .as_any() - .downcast_ref::() - .is_some(), - DexType::PumpSwap => protocol_params - .as_any() - .downcast_ref::() - .is_some(), - DexType::Bonk => protocol_params - .as_any() - .downcast_ref::() - .is_some(), - DexType::RaydiumCpmm => protocol_params - .as_any() - .downcast_ref::() - .is_some(), + DexType::PumpFun => protocol_params.as_any().downcast_ref::().is_some(), + DexType::PumpSwap => { + protocol_params.as_any().downcast_ref::().is_some() + } + DexType::Bonk => protocol_params.as_any().downcast_ref::().is_some(), + DexType::RaydiumCpmm => { + protocol_params.as_any().downcast_ref::().is_some() + } + DexType::RaydiumAmmV4 => { + protocol_params.as_any().downcast_ref::().is_some() + } }; if !is_valid_params { @@ -354,9 +345,9 @@ impl SolanaTrade { // Execute sell based on tip preference if with_tip { - executor.sell_with_tip(sell_with_tip_params).await + executor.sell_with_tip(sell_with_tip_params, self.middleware_manager.clone()).await } else { - executor.sell(sell_params).await + executor.sell(sell_params, self.middleware_manager.clone()).await } } diff --git a/src/main.rs b/src/main.rs index ca0977e..0cd73bb 100755 --- a/src/main.rs +++ b/src/main.rs @@ -1,32 +1,37 @@ use std::{str::FromStr, sync::Arc}; -use sol_trade_sdk::solana_streamer_sdk::{ - match_event, - streaming::{ - event_parser::{ - protocols::{ - bonk::{BonkPoolCreateEvent, BonkTradeEvent}, - pumpfun::{PumpFunCreateTokenEvent, PumpFunTradeEvent}, - pumpswap::{ - PumpSwapBuyEvent, PumpSwapCreatePoolEvent, PumpSwapDepositEvent, - PumpSwapSellEvent, PumpSwapWithdrawEvent, - }, - raydium_cpmm::RaydiumCpmmSwapEvent, - }, - Protocol, UnifiedEvent, - }, - ShredStreamGrpc, YellowstoneGrpc, - }, -}; use sol_trade_sdk::{ common::{AnyResult, PriorityFee, TradeConfig}, swqos::{SwqosConfig, SwqosRegion}, trading::{ core::params::{BonkParams, PumpFunParams, PumpSwapParams, RaydiumCpmmParams}, factory::DexType, + middleware::builtin::LoggingMiddleware, + MiddlewareManager, }, SolanaTrade, }; +use sol_trade_sdk::{ + solana_streamer_sdk::{ + match_event, + streaming::{ + event_parser::{ + protocols::{ + bonk::{BonkPoolCreateEvent, BonkTradeEvent}, + pumpfun::{PumpFunCreateTokenEvent, PumpFunTradeEvent}, + pumpswap::{ + PumpSwapBuyEvent, PumpSwapCreatePoolEvent, PumpSwapDepositEvent, + PumpSwapSellEvent, PumpSwapWithdrawEvent, + }, + raydium_cpmm::RaydiumCpmmSwapEvent, + }, + Protocol, UnifiedEvent, + }, + ShredStreamGrpc, YellowstoneGrpc, + }, + }, + trading::core::params::RaydiumAmmV4Params, +}; use solana_sdk::{commitment_config::CommitmentConfig, pubkey::Pubkey, signature::Keypair}; use solana_streamer_sdk::streaming::{ event_parser::protocols::{ @@ -41,9 +46,11 @@ use solana_streamer_sdk::streaming::{ #[tokio::main] async fn main() -> Result<(), Box> { test_create_solana_trade_client().await?; + test_middleware().await?; test_pumpswap().await?; test_bonk().await?; test_raydium_cpmm().await?; + test_raydium_amm_v4().await?; test_grpc().await?; test_shreds().await?; Ok(()) @@ -86,6 +93,36 @@ fn create_trade_config(rpc_url: String, swqos_configs: Vec) -> Trad lookup_table_key: None, } } +async fn test_middleware() -> AnyResult<()> { + let mut client = test_create_solana_trade_client().await?; + // SDK example middleware that prints instruction information + // You can reference LoggingMiddleware to implement the InstructionMiddleware trait for your own middleware + let middleware_manager = MiddlewareManager::new().add_middleware(Box::new(LoggingMiddleware)); + client = client.with_middleware_manager(middleware_manager); + let creator = Pubkey::from_str("11111111111111111111111111111111")?; + let mint_pubkey = Pubkey::from_str("xxxxx")?; + let buy_sol_cost = 100_000; + let slippage_basis_points = Some(100); + let recent_blockhash = client.rpc.get_latest_blockhash().await?; + let pool_address = Pubkey::from_str("xxxx")?; + // Buy tokens + println!("Buying tokens from PumpSwap..."); + client + .buy( + DexType::PumpSwap, + mint_pubkey, + Some(creator), + buy_sol_cost, + slippage_basis_points, + recent_blockhash, + None, + // Through RPC call, adds latency. Can optimize by using from_buy_trade or manually initializing PumpSwapParams + Box::new(PumpSwapParams::from_pool_address_by_rpc(&client.rpc, &pool_address).await?), + None, + ) + .await?; + Ok(()) +} async fn test_pumpfun_copy_trade_with_grpc(trade_info: PumpFunTradeEvent) -> AnyResult<()> { println!("Testing PumpFun trading..."); @@ -443,6 +480,54 @@ async fn test_raydium_cpmm() -> Result<(), Box> { Ok(()) } +async fn test_raydium_amm_v4() -> Result<(), Box> { + println!("Testing Raydium Amm V4 trading..."); + + let client = test_create_solana_trade_client().await?; + let mint_pubkey = Pubkey::from_str("xxxxxxx")?; + let buy_sol_cost = 100_000; + let slippage_basis_points = Some(100); + let recent_blockhash = client.rpc.get_latest_blockhash().await?; + let amm_address = Pubkey::from_str("xxxxxx")?; + // Buy tokens + println!("Buying tokens from Raydium Amm V4..."); + client + .buy( + DexType::RaydiumAmmV4, + mint_pubkey, + None, + buy_sol_cost, + slippage_basis_points, + recent_blockhash, + None, + // Through RPC call, adds latency, or from_amm_info_and_reserves or manually initialize RaydiumAmmV4Params + Box::new(RaydiumAmmV4Params::from_amm_address_by_rpc(&client.rpc, amm_address).await?), + None, + ) + .await?; + + // Sell tokens + println!("Selling tokens from Raydium Amm V4..."); + let amount_token = 0; + client + .sell( + DexType::RaydiumAmmV4, + mint_pubkey, + None, + amount_token, + slippage_basis_points, + recent_blockhash, + None, + false, + // Through RPC call, adds latency, or from_amm_info_and_reserves or manually initialize RaydiumAmmV4Params + Box::new(RaydiumAmmV4Params::from_amm_address_by_rpc(&client.rpc, amm_address).await?), + None, + ) + .await?; + + Ok(()) +} + async fn test_grpc() -> Result<(), Box> { println!("Subscribing to GRPC events..."); diff --git a/src/trading/common/transaction_builder.rs b/src/trading/common/transaction_builder.rs index 178c77e..8549453 100755 --- a/src/trading/common/transaction_builder.rs +++ b/src/trading/common/transaction_builder.rs @@ -20,8 +20,9 @@ use super::{ }; use crate::{ common::PriorityFee, - trading::common::{ - add_sell_compute_budget_instructions, add_sell_tip_compute_budget_instructions, + trading::{ + common::{add_sell_compute_budget_instructions, add_sell_tip_compute_budget_instructions}, + MiddlewareManager, }, }; @@ -33,6 +34,9 @@ pub async fn build_rpc_transaction( lookup_table_key: Option, recent_blockhash: Hash, data_size_limit: u32, + middleware_manager: Option>, + protocol_name: String, + is_buy: bool, ) -> Result { let mut instructions = vec![]; @@ -54,7 +58,16 @@ pub async fn build_rpc_transaction( let address_lookup_table_accounts = get_address_lookup_table_accounts(lookup_table_key).await; // 构建交易 - build_versioned_transaction(payer, instructions, address_lookup_table_accounts, blockhash).await + build_versioned_transaction( + payer, + instructions, + address_lookup_table_accounts, + blockhash, + middleware_manager, + protocol_name, + is_buy, + ) + .await } /// 构建带小费的交易 @@ -67,6 +80,9 @@ pub async fn build_tip_transaction( lookup_table_key: Option, recent_blockhash: Hash, data_size_limit: u32, + middleware_manager: Option>, + protocol_name: String, + is_buy: bool, ) -> Result { let mut instructions = vec![]; @@ -95,7 +111,16 @@ pub async fn build_tip_transaction( let address_lookup_table_accounts = get_address_lookup_table_accounts(lookup_table_key).await; // 构建交易 - build_versioned_transaction(payer, instructions, address_lookup_table_accounts, blockhash).await + build_versioned_transaction( + payer, + instructions, + address_lookup_table_accounts, + blockhash, + middleware_manager, + protocol_name, + is_buy, + ) + .await } /// 构建版本化交易的底层函数 @@ -104,10 +129,18 @@ async fn build_versioned_transaction( instructions: Vec, address_lookup_table_accounts: Vec, blockhash: Hash, + middleware_manager: Option>, + protocol_name: String, + is_buy: bool, ) -> Result { + let full_instructions = match middleware_manager { + Some(middleware_manager) => middleware_manager + .apply_middlewares_process_full_instructions(instructions, protocol_name, is_buy)?, + None => instructions, + }; let v0_message: v0::Message = v0::Message::try_compile( &payer.pubkey(), - &instructions, + &full_instructions, &address_lookup_table_accounts, blockhash, )?; @@ -127,6 +160,9 @@ pub async fn build_tip_transaction_with_priority_fee( lookup_table_key: Option, recent_blockhash: Hash, data_size_limit: u32, + middleware_manager: Option>, + protocol_name: String, + is_buy: bool, ) -> Result { build_tip_transaction( payer, @@ -137,6 +173,9 @@ pub async fn build_tip_transaction_with_priority_fee( lookup_table_key, recent_blockhash, data_size_limit, + middleware_manager, + protocol_name, + is_buy, ) .await } @@ -148,6 +187,9 @@ pub async fn build_sell_transaction( business_instructions: Vec, lookup_table_key: Option, recent_blockhash: Hash, + middleware_manager: Option>, + protocol_name: String, + is_buy: bool, ) -> Result { let mut instructions = vec![]; @@ -166,6 +208,9 @@ pub async fn build_sell_transaction( instructions, address_lookup_table_accounts, recent_blockhash, + middleware_manager, + protocol_name, + is_buy, ) .await } @@ -178,6 +223,9 @@ pub async fn build_sell_tip_transaction( tip_amount: f64, lookup_table_key: Option, recent_blockhash: Hash, + middleware_manager: Option>, + protocol_name: String, + is_buy: bool, ) -> Result { let mut instructions = vec![]; @@ -203,6 +251,9 @@ pub async fn build_sell_tip_transaction( instructions, address_lookup_table_accounts, recent_blockhash, + middleware_manager, + protocol_name, + is_buy, ) .await } @@ -214,6 +265,9 @@ pub async fn build_sell_tip_transaction_with_priority_fee( tip_account: &Pubkey, lookup_table_key: Option, recent_blockhash: Hash, + middleware_manager: Option>, + protocol_name: String, + is_buy: bool, ) -> Result { build_sell_tip_transaction( payer, @@ -223,6 +277,9 @@ pub async fn build_sell_tip_transaction_with_priority_fee( priority_fee.sell_tip_fee, lookup_table_key, recent_blockhash, + middleware_manager, + protocol_name, + is_buy, ) .await } diff --git a/src/trading/common/utils.rs b/src/trading/common/utils.rs index 7a38c88..ba9b4cf 100644 --- a/src/trading/common/utils.rs +++ b/src/trading/common/utils.rs @@ -6,6 +6,25 @@ use spl_token::instruction::close_account; use crate::common::SolanaRpcClient; use anyhow::anyhow; +/// Get the balances of two tokens in the pool +/// +/// # Returns +/// Returns token0_balance, token1_balance +pub async fn get_multi_token_balances( + rpc: &SolanaRpcClient, + token0_vault: &Pubkey, + token1_vault: &Pubkey, +) -> Result<(u64, u64), anyhow::Error> { + let token0_balance = rpc.get_token_account_balance(&token0_vault).await?; + let token1_balance = rpc.get_token_account_balance(&token1_vault).await?; + // Parse balance string to u64 + let token0_amount = + token0_balance.amount.parse::().map_err(|e| anyhow!("Failed to parse token0 balance: {}", e))?; + let token1_amount = + token1_balance.amount.parse::().map_err(|e| anyhow!("Failed to parse token1 balance: {}", e))?; + Ok((token0_amount, token1_amount)) +} + #[inline] pub async fn get_token_balance( rpc: &SolanaRpcClient, diff --git a/src/trading/core/executor.rs b/src/trading/core/executor.rs index 0e3184e..fbcceeb 100755 --- a/src/trading/core/executor.rs +++ b/src/trading/core/executor.rs @@ -9,7 +9,10 @@ use super::{ }; use crate::{ swqos::TradeType, - trading::common::{build_rpc_transaction, build_sell_transaction}, + trading::{ + common::{build_rpc_transaction, build_sell_transaction}, + middleware::MiddlewareManager, + }, }; const MAX_LOADED_ACCOUNTS_DATA_SIZE_LIMIT: u32 = 256 * 1024; @@ -25,16 +28,17 @@ impl GenericTradeExecutor { instruction_builder: Arc, protocol_name: &'static str, ) -> Self { - Self { - instruction_builder, - protocol_name, - } + Self { instruction_builder, protocol_name } } } #[async_trait::async_trait] impl TradeExecutor for GenericTradeExecutor { - async fn buy(&self, mut params: BuyParams) -> Result<()> { + async fn buy( + &self, + mut params: BuyParams, + middleware_manager: Option>, + ) -> Result<()> { if params.data_size_limit == 0 { params.data_size_limit = MAX_LOADED_ACCOUNTS_DATA_SIZE_LIMIT; } @@ -44,20 +48,29 @@ impl TradeExecutor for GenericTradeExecutor { let rpc = params.rpc.as_ref().unwrap().clone(); let mut timer = TradeTimer::new("构建买入交易指令"); // 构建指令 - let instructions = self - .instruction_builder - .build_buy_instructions(¶ms) - .await?; + let instructions = self.instruction_builder.build_buy_instructions(¶ms).await?; + let final_instructions = match middleware_manager.clone() { + Some(middleware_manager) => middleware_manager + .apply_middlewares_process_protocol_instructions( + instructions, + self.protocol_name.to_string(), + true, + )?, + None => instructions, + }; timer.stage("构建rpc交易指令"); // 构建交易 let transaction = build_rpc_transaction( params.payer.clone(), ¶ms.priority_fee, - instructions, + final_instructions, params.lookup_table_key, params.recent_blockhash, params.data_size_limit, + middleware_manager, + self.protocol_name.to_string(), + true, ) .await?; timer.stage("rpc提交确认"); @@ -69,7 +82,11 @@ impl TradeExecutor for GenericTradeExecutor { Ok(()) } - async fn buy_with_tip(&self, mut params: BuyWithTipParams) -> Result<()> { + async fn buy_with_tip( + &self, + mut params: BuyWithTipParams, + middleware_manager: Option>, + ) -> Result<()> { if params.data_size_limit == 0 { params.data_size_limit = MAX_LOADED_ACCOUNTS_DATA_SIZE_LIMIT; } @@ -91,10 +108,16 @@ impl TradeExecutor for GenericTradeExecutor { }; // 构建指令 - let instructions = self - .instruction_builder - .build_buy_instructions(&buy_params) - .await?; + let instructions = self.instruction_builder.build_buy_instructions(&buy_params).await?; + let final_instructions = match middleware_manager.clone() { + Some(middleware_manager) => middleware_manager + .apply_middlewares_process_protocol_instructions( + instructions, + self.protocol_name.to_string(), + true, + )?, + None => instructions, + }; timer.finish(); @@ -102,19 +125,26 @@ impl TradeExecutor for GenericTradeExecutor { parallel_execute_with_tips( params.swqos_clients, params.payer, - instructions, + final_instructions, params.priority_fee, params.lookup_table_key, params.recent_blockhash, params.data_size_limit, TradeType::Buy, + middleware_manager, + self.protocol_name.to_string(), + true, ) .await?; Ok(()) } - async fn sell(&self, params: SellParams) -> Result<()> { + async fn sell( + &self, + params: SellParams, + middleware_manager: Option>, + ) -> Result<()> { if params.rpc.is_none() { return Err(anyhow!("RPC is not set")); } @@ -122,19 +152,28 @@ impl TradeExecutor for GenericTradeExecutor { let mut timer = TradeTimer::new("构建卖出交易指令"); // 构建指令 - let instructions = self - .instruction_builder - .build_sell_instructions(¶ms) - .await?; + let instructions = self.instruction_builder.build_sell_instructions(¶ms).await?; + let final_instructions = match middleware_manager.clone() { + Some(middleware_manager) => middleware_manager + .apply_middlewares_process_protocol_instructions( + instructions, + self.protocol_name.to_string(), + false, + )?, + None => instructions, + }; timer.stage("卖出交易指令"); // 构建交易 let transaction = build_sell_transaction( params.payer.clone(), ¶ms.priority_fee, - instructions, + final_instructions, params.lookup_table_key, params.recent_blockhash, + middleware_manager, + self.protocol_name.to_string(), + false, ) .await?; timer.stage("卖出交易签名"); @@ -146,7 +185,11 @@ impl TradeExecutor for GenericTradeExecutor { Ok(()) } - async fn sell_with_tip(&self, params: SellWithTipParams) -> Result<()> { + async fn sell_with_tip( + &self, + params: SellWithTipParams, + middleware_manager: Option>, + ) -> Result<()> { let timer = TradeTimer::new("构建卖出交易指令"); // 转换为SellParams进行指令构建 @@ -164,10 +207,16 @@ impl TradeExecutor for GenericTradeExecutor { }; // 构建指令 - let instructions = self - .instruction_builder - .build_sell_instructions(&sell_params) - .await?; + let instructions = self.instruction_builder.build_sell_instructions(&sell_params).await?; + let final_instructions = match middleware_manager.clone() { + Some(middleware_manager) => middleware_manager + .apply_middlewares_process_protocol_instructions( + instructions, + self.protocol_name.to_string(), + false, + )?, + None => instructions, + }; timer.finish(); @@ -175,12 +224,15 @@ impl TradeExecutor for GenericTradeExecutor { parallel_execute_with_tips( params.swqos_clients, params.payer, - instructions, + final_instructions, params.priority_fee, params.lookup_table_key, params.recent_blockhash, 0, TradeType::Sell, + middleware_manager, + self.protocol_name.to_string(), + false, ) .await?; diff --git a/src/trading/core/parallel.rs b/src/trading/core/parallel.rs index 057a742..68e60ba 100755 --- a/src/trading/core/parallel.rs +++ b/src/trading/core/parallel.rs @@ -6,11 +6,14 @@ use tokio::task::JoinHandle; use crate::{ common::PriorityFee, - swqos::{SwqosType, SwqosClient, TradeType}, - trading::core::timer::TradeTimer, - trading::common::{ - build_rpc_transaction, build_sell_tip_transaction_with_priority_fee, - build_sell_transaction, build_tip_transaction_with_priority_fee, + swqos::{SwqosClient, SwqosType, TradeType}, + trading::{ + common::{ + build_rpc_transaction, build_sell_tip_transaction_with_priority_fee, + build_sell_transaction, build_tip_transaction_with_priority_fee, + }, + core::timer::TradeTimer, + MiddlewareManager, }, }; @@ -24,6 +27,9 @@ pub async fn parallel_execute_with_tips( recent_blockhash: Hash, data_size_limit: u32, trade_type: TradeType, + middleware_manager: Option>, + protocol_name: String, + is_buy: bool, ) -> Result<()> { let cores = core_affinity::get_core_ids().unwrap(); let mut handles: Vec>> = vec![]; @@ -35,10 +41,14 @@ pub async fn parallel_execute_with_tips( let mut priority_fee = priority_fee.clone(); let core_id = cores[i % cores.len()]; + let middleware_manager = middleware_manager.clone(); + let protocol_name = protocol_name.clone(); + let handle = tokio::spawn(async move { core_affinity::set_for_current(core_id); - let mut timer = TradeTimer::new(format!("构建交易指令: {:?}", swqos_client.get_swqos_type())); + let mut timer = + TradeTimer::new(format!("构建交易指令: {:?}", swqos_client.get_swqos_type())); let transaction = if matches!(trade_type, TradeType::Sell) && swqos_client.get_swqos_type() == SwqosType::Default @@ -49,6 +59,9 @@ pub async fn parallel_execute_with_tips( instructions, lookup_table_key, recent_blockhash, + middleware_manager, + protocol_name, + is_buy, ) .await? } else if matches!(trade_type, TradeType::Sell) @@ -63,6 +76,9 @@ pub async fn parallel_execute_with_tips( &tip_account, lookup_table_key, recent_blockhash, + middleware_manager, + protocol_name, + is_buy, ) .await? } else if swqos_client.get_swqos_type() == SwqosType::Default { @@ -73,6 +89,9 @@ pub async fn parallel_execute_with_tips( lookup_table_key, recent_blockhash, data_size_limit, + middleware_manager, + protocol_name, + is_buy, ) .await? } else { @@ -88,15 +107,16 @@ pub async fn parallel_execute_with_tips( lookup_table_key, recent_blockhash, data_size_limit, + middleware_manager, + protocol_name, + is_buy, ) .await? }; timer.stage(format!("提交交易指令: {:?}", swqos_client.get_swqos_type())); - swqos_client - .send_transaction(trade_type, &transaction) - .await?; + swqos_client.send_transaction(trade_type, &transaction).await?; timer.finish(); Ok::<(), anyhow::Error>(()) diff --git a/src/trading/core/params.rs b/src/trading/core/params.rs index 60a4cf8..0f37980 100755 --- a/src/trading/core/params.rs +++ b/src/trading/core/params.rs @@ -4,6 +4,8 @@ use solana_streamer_sdk::streaming::event_parser::protocols::pumpfun::PumpFunTra use solana_streamer_sdk::streaming::event_parser::protocols::pumpswap::{ PumpSwapBuyEvent, PumpSwapSellEvent, }; +use solana_streamer_sdk::streaming::event_parser::protocols::raydium_amm_v4::types::AmmInfo; +use solana_streamer_sdk::streaming::event_parser::protocols::raydium_amm_v4::RaydiumAmmV4SwapEvent; use std::sync::Arc; use super::traits::ProtocolParams; @@ -16,6 +18,7 @@ use crate::solana_streamer_sdk::streaming::event_parser::common::EventType; use crate::solana_streamer_sdk::streaming::event_parser::protocols::bonk::BonkTradeEvent; use crate::swqos::SwqosClient; use crate::trading::bonk::common::{get_amount_in, get_amount_in_net, get_amount_out}; +use crate::trading::common::get_multi_token_balances; use crate::trading::pumpswap::common::get_token_balances; use crate::trading::raydium_cpmm::common::get_pool_token_balances; @@ -367,6 +370,76 @@ impl ProtocolParams for RaydiumCpmmParams { } } +/// RaydiumCpmm protocol specific parameters +/// Configuration parameters specific to Raydium CPMM trading protocol +#[derive(Clone)] +pub struct RaydiumAmmV4Params { + /// AMM pool address + pub amm: Pubkey, + /// Base token (coin) mint address + pub coin_mint: Pubkey, + /// Quote token (pc) mint address + pub pc_mint: Pubkey, + /// Pool's coin token account address + pub token_coin: Pubkey, + /// Pool's pc token account address + pub token_pc: Pubkey, + /// Current coin reserve amount in the pool + pub coin_reserve: u64, + /// Current pc reserve amount in the pool + pub pc_reserve: u64, + /// Whether to automatically handle wSOL wrapping and unwrapping + pub auto_handle_wsol: bool, +} + +impl RaydiumAmmV4Params { + pub fn from_amm_info_and_reserves( + amm: Pubkey, + amm_info: AmmInfo, + coin_reserve: u64, + pc_reserve: u64, + ) -> Self { + Self { + amm, + coin_mint: amm_info.coin_mint, + pc_mint: amm_info.pc_mint, + token_coin: amm_info.token_coin, + token_pc: amm_info.token_pc, + coin_reserve, + pc_reserve, + auto_handle_wsol: true, + } + } + pub async fn from_amm_address_by_rpc( + rpc: &SolanaRpcClient, + amm: Pubkey, + ) -> Result { + let amm_info = crate::trading::raydium_amm_v4::common::fetch_amm_info(rpc, amm).await?; + let (coin_reserve, pc_reserve) = + get_multi_token_balances(rpc, &amm_info.token_coin, &amm_info.token_pc).await?; + Ok(Self { + amm, + coin_mint: amm_info.coin_mint, + pc_mint: amm_info.pc_mint, + token_coin: amm_info.token_coin, + token_pc: amm_info.token_pc, + coin_reserve, + pc_reserve, + auto_handle_wsol: true, + }) + } +} + +impl ProtocolParams for RaydiumAmmV4Params { + fn as_any(&self) -> &dyn std::any::Any { + self + } + + fn clone_box(&self) -> Box { + Box::new(self.clone()) + } +} + impl BuyParams { /// Convert to BuyWithTipParams /// Transforms basic buy parameters into MEV-enabled parameters diff --git a/src/trading/core/traits.rs b/src/trading/core/traits.rs index 80a05b9..95d30b4 100755 --- a/src/trading/core/traits.rs +++ b/src/trading/core/traits.rs @@ -1,21 +1,25 @@ +use std::sync::Arc; + use anyhow::Result; use solana_sdk::instruction::Instruction; +use crate::trading::MiddlewareManager; + use super::params::{BuyParams, BuyWithTipParams, SellParams, SellWithTipParams}; /// 交易执行器trait - 定义了所有交易协议都需要实现的核心方法 #[async_trait::async_trait] pub trait TradeExecutor: Send + Sync { /// 执行买入交易 - async fn buy(&self, params: BuyParams) -> Result<()>; + async fn buy(&self, params: BuyParams, middleware_manager: Option>) -> Result<()>; /// 使用MEV服务执行买入交易 - async fn buy_with_tip(&self, params: BuyWithTipParams) -> Result<()>; + async fn buy_with_tip(&self, params: BuyWithTipParams, middleware_manager: Option>) -> Result<()>; /// 执行卖出交易 - async fn sell(&self, params: SellParams) -> Result<()>; + async fn sell(&self, params: SellParams, middleware_manager: Option>) -> Result<()>; /// 使用MEV服务执行卖出交易 - async fn sell_with_tip(&self, params: SellWithTipParams) -> Result<()>; + async fn sell_with_tip(&self, params: SellWithTipParams, middleware_manager: Option>) -> Result<()>; /// 获取协议名称 fn protocol_name(&self) -> &'static str; diff --git a/src/trading/factory.rs b/src/trading/factory.rs index 3db4704..1050d96 100755 --- a/src/trading/factory.rs +++ b/src/trading/factory.rs @@ -3,7 +3,8 @@ use std::sync::Arc; use crate::instruction::{ bonk::BonkInstructionBuilder, pumpfun::PumpFunInstructionBuilder, - pumpswap::PumpSwapInstructionBuilder, raydium_cpmm::RaydiumCpmmInstructionBuilder, + pumpswap::PumpSwapInstructionBuilder, raydium_amm_v4::RaydiumAmmV4InstructionBuilder, + raydium_cpmm::RaydiumCpmmInstructionBuilder, }; use super::core::{executor::GenericTradeExecutor, traits::TradeExecutor}; @@ -15,6 +16,7 @@ pub enum DexType { PumpSwap, Bonk, RaydiumCpmm, + RaydiumAmmV4, } impl std::fmt::Display for DexType { @@ -24,6 +26,7 @@ impl std::fmt::Display for DexType { DexType::PumpSwap => write!(f, "PumpSwap"), DexType::Bonk => write!(f, "Bonk"), DexType::RaydiumCpmm => write!(f, "RaydiumCpmm"), + DexType::RaydiumAmmV4 => write!(f, "RaydiumAmmV4"), } } } @@ -37,6 +40,7 @@ impl std::str::FromStr for DexType { "pumpswap" => Ok(DexType::PumpSwap), "bonk" => Ok(DexType::Bonk), "raydiumcpmm" => Ok(DexType::RaydiumCpmm), + "raydiumammv4" => Ok(DexType::RaydiumAmmV4), _ => Err(anyhow!("Unsupported protocol: {}", s)), } } @@ -63,10 +67,11 @@ impl TradeFactory { } DexType::RaydiumCpmm => { let instruction_builder = Arc::new(RaydiumCpmmInstructionBuilder); - Arc::new(GenericTradeExecutor::new( - instruction_builder, - "RaydiumCpmm", - )) + Arc::new(GenericTradeExecutor::new(instruction_builder, "RaydiumCpmm")) + } + DexType::RaydiumAmmV4 => { + let instruction_builder = Arc::new(RaydiumAmmV4InstructionBuilder); + Arc::new(GenericTradeExecutor::new(instruction_builder, "RaydiumAmmV4")) } } } diff --git a/src/trading/middleware/builtin.rs b/src/trading/middleware/builtin.rs new file mode 100644 index 0000000..65dc101 --- /dev/null +++ b/src/trading/middleware/builtin.rs @@ -0,0 +1,53 @@ +use crate::trading::middleware::traits::InstructionMiddleware; +use anyhow::Result; +use solana_sdk::instruction::Instruction; + +/// Logging middleware - Records instruction information +#[derive(Clone)] +pub struct LoggingMiddleware; + +impl InstructionMiddleware for LoggingMiddleware { + fn name(&self) -> &'static str { + "LoggingMiddleware" + } + + fn process_protocol_instructions( + &self, + protocol_instructions: Vec, + protocol_name: String, + is_buy: bool, + ) -> Result> { + println!("-------------------[{}]-------------------", self.name()); + println!("process_protocol_instructions"); + println!("[{}] Instruction count: {}", self.name(), protocol_instructions.len()); + println!("[{}] Protocol name: {}\n", self.name(), protocol_name); + println!("[{}] Is buy: {}", self.name(), is_buy); + for (i, instruction) in protocol_instructions.iter().enumerate() { + println!("Instruction {}:", i + 1); + println!("{:?}\n", instruction); + } + Ok(protocol_instructions) + } + + fn process_full_instructions( + &self, + full_instructions: Vec, + protocol_name: String, + is_buy: bool, + ) -> Result> { + println!("-------------------[{}]-------------------", self.name()); + println!("process_full_instructions"); + println!("[{}] Instruction count: {}", self.name(), full_instructions.len()); + println!("[{}] Protocol name: {}\n", self.name(), protocol_name); + println!("[{}] Is buy: {}", self.name(), is_buy); + for (i, instruction) in full_instructions.iter().enumerate() { + println!("Instruction {}:", i + 1); + println!("{:?}\n", instruction); + } + Ok(full_instructions) + } + + fn clone_box(&self) -> Box { + Box::new(self.clone()) + } +} diff --git a/src/trading/middleware/mod.rs b/src/trading/middleware/mod.rs new file mode 100644 index 0000000..6a8fa19 --- /dev/null +++ b/src/trading/middleware/mod.rs @@ -0,0 +1,4 @@ +pub mod traits; +pub mod builtin; + +pub use traits::{InstructionMiddleware, MiddlewareManager}; diff --git a/src/trading/middleware/traits.rs b/src/trading/middleware/traits.rs new file mode 100644 index 0000000..081f6a8 --- /dev/null +++ b/src/trading/middleware/traits.rs @@ -0,0 +1,115 @@ +use anyhow::Result; +use solana_sdk::instruction::Instruction; + +/// Instruction middleware trait +/// +/// Used to modify, add or remove protocol_instructions before transaction execution +pub trait InstructionMiddleware: Send + Sync { + /// Middleware name + fn name(&self) -> &'static str; + + /// Core method for processing protocol_instructions + /// + /// # Arguments + /// * `protocol_instructions` - Current instruction list + /// * `protocol_name` - Protocol name + /// * `is_buy` - Whether the transaction is a buy transaction + /// + /// # Returns + /// Returns modified instruction list + fn process_protocol_instructions( + &self, + protocol_instructions: Vec, + protocol_name: String, + is_buy: bool, + ) -> Result>; + + /// Core method for processing full_instructions + /// + /// # Arguments + /// * `full_instructions` - Current instruction list + /// * `protocol_name` - Protocol name + /// * `is_buy` - Whether the transaction is a buy transaction + /// + /// # Returns + /// Returns modified instruction list + fn process_full_instructions( + &self, + full_instructions: Vec, + protocol_name: String, + is_buy: bool, + ) -> Result>; + + /// Clone middleware + fn clone_box(&self) -> Box; +} + +/// Middleware manager +pub struct MiddlewareManager { + middlewares: Vec>, +} + +impl Clone for MiddlewareManager { + fn clone(&self) -> Self { + Self { + middlewares: self.middlewares.iter().map(|middleware| middleware.clone_box()).collect(), + } + } +} + +impl MiddlewareManager { + /// Create new middleware manager + pub fn new() -> Self { + Self { middlewares: Vec::new() } + } + + /// Add middleware + pub fn add_middleware(mut self, middleware: Box) -> Self { + self.middlewares.push(middleware); + self + } + + pub fn apply_middlewares_process_full_instructions( + &self, + mut full_instructions: Vec, + protocol_name: String, + is_buy: bool, + ) -> Result> { + for middleware in &self.middlewares { + full_instructions = middleware.process_full_instructions( + full_instructions, + protocol_name.clone(), + is_buy, + )?; + if full_instructions.is_empty() { + break; + } + } + Ok(full_instructions) + } + + /// Apply all middlewares to process protocol_instructions + pub fn apply_middlewares_process_protocol_instructions( + &self, + mut protocol_instructions: Vec, + protocol_name: String, + is_buy: bool, + ) -> Result> { + for middleware in &self.middlewares { + protocol_instructions = middleware.process_protocol_instructions( + protocol_instructions, + protocol_name.clone(), + is_buy, + )?; + if protocol_instructions.is_empty() { + break; + } + } + Ok(protocol_instructions) + } + + /// Create manager with common middlewares + pub fn with_common_middlewares() -> Self { + Self::new().add_middleware(Box::new(crate::trading::middleware::builtin::LoggingMiddleware)) + } +} diff --git a/src/trading/mod.rs b/src/trading/mod.rs index 318a478..c8dd2d8 100755 --- a/src/trading/mod.rs +++ b/src/trading/mod.rs @@ -1,11 +1,14 @@ +pub mod bonk; pub mod common; pub mod core; pub mod factory; -pub mod bonk; +pub mod middleware; pub mod pumpfun; pub mod pumpswap; +pub mod raydium_amm_v4; pub mod raydium_cpmm; pub use core::params::{BuyParams, BuyWithTipParams, SellParams, SellWithTipParams}; pub use core::traits::{InstructionBuilder, TradeExecutor}; pub use factory::TradeFactory; +pub use middleware::{InstructionMiddleware, MiddlewareManager}; diff --git a/src/trading/raydium_amm_v4/common.rs b/src/trading/raydium_amm_v4/common.rs new file mode 100755 index 0000000..e0e849d --- /dev/null +++ b/src/trading/raydium_amm_v4/common.rs @@ -0,0 +1,14 @@ +use anyhow::anyhow; +use solana_sdk::pubkey::Pubkey; +use solana_streamer_sdk::streaming::event_parser::protocols::raydium_amm_v4::types::{ + amm_info_decode, AmmInfo, +}; + +use crate::common::SolanaRpcClient; + +pub async fn fetch_amm_info(rpc: &SolanaRpcClient, amm: Pubkey) -> Result { + let amm_info = rpc.get_account_data(&amm).await?; + let amm_info = + amm_info_decode(&amm_info).ok_or_else(|| anyhow!("Failed to decode amm info"))?; + Ok(amm_info) +} diff --git a/src/trading/raydium_amm_v4/mod.rs b/src/trading/raydium_amm_v4/mod.rs new file mode 100755 index 0000000..34994bf --- /dev/null +++ b/src/trading/raydium_amm_v4/mod.rs @@ -0,0 +1 @@ +pub mod common; diff --git a/src/utils/calc/raydium_amm_v4.rs b/src/utils/calc/raydium_amm_v4.rs index 70b786d..50c705f 100644 --- a/src/utils/calc/raydium_amm_v4.rs +++ b/src/utils/calc/raydium_amm_v4.rs @@ -1 +1,150 @@ -// TODO +use crate::constants::raydium_amm_v4::accounts::{ + SWAP_FEE_DENOMINATOR, SWAP_FEE_NUMERATOR, TRADE_FEE_DENOMINATOR, TRADE_FEE_NUMERATOR, +}; + +/// Computes trading fee using ceiling division. +/// +/// # Arguments +/// * `amount` - The amount to calculate fee for +/// * `fee_rate` - The fee rate to apply +/// +/// # Returns +/// The calculated trading fee +fn compute_trading_fee(amount: u64, fee_rate: u64, fee_denominator: u64) -> u64 { + let numerator = (amount as u128) * (fee_rate as u128); + ((numerator + fee_denominator as u128 - 1) / fee_denominator as u128) as u64 +} + +/// Computes protocol or fund fee using floor division. +/// +/// # Arguments +/// * `amount` - The amount to calculate fee for +/// * `fee_rate` - The fee rate to apply +/// +/// # Returns +/// The calculated protocol or fund fee +fn compute_protocol_fund_fee(amount: u64, fee_rate: u64, fee_denominator: u64) -> u64 { + let numerator = (amount as u128) * (fee_rate as u128); + (numerator / fee_denominator as u128) as u64 +} + +/// Parameters for computing swap amounts and fees. +#[derive(Debug, Clone)] +pub struct ComputeSwapParams { + /// Whether the entire input amount is traded + pub all_trade: bool, + /// The input amount for the swap + pub amount_in: u64, + /// The expected output amount from the swap + pub amount_out: u64, + /// The minimum acceptable output amount (considering slippage_basis_points) + pub min_amount_out: u64, + /// The trading fee amount + pub fee: u64, +} + +/// Result of a swap calculation containing all relevant amounts and fees. +#[derive(Debug, Clone)] +pub struct SwapResult { + /// The new amount in the input vault after the swap + pub new_input_vault_amount: u64, + /// The new amount in the output vault after the swap + pub new_output_vault_amount: u64, + /// The actual input amount used in the swap + pub input_amount: u64, + /// The actual output amount received from the swap + pub output_amount: u64, + /// The trading fee charged + pub trade_fee: u64, + /// The swap fee charged + pub swap_fee: u64, +} + +/// Performs a swap calculation based on input amount. +/// +/// Calculates the output amount and all associated fees when swapping a specific input amount. +/// +/// # Arguments +/// * `input_amount` - The amount of input tokens to swap +/// * `input_vault_amount` - Current amount in the input token vault +/// * `output_vault_amount` - Current amount in the output token vault +/// * `trade_fee_rate` - The trading fee rate +/// * `swap_fee_rate` - The swap fee rate +/// +/// # Returns +/// A `SwapResult` containing all swap calculations and fees +fn swap_base_input( + input_amount: u64, + input_vault_amount: u64, + output_vault_amount: u64, + trade_fee_rate: u64, + swap_fee_rate: u64, +) -> SwapResult { + let trade_fee = compute_trading_fee(input_amount, trade_fee_rate, TRADE_FEE_DENOMINATOR); + + let input_amount_less_fees = input_amount.saturating_sub(trade_fee); + + let swap_fee = compute_protocol_fund_fee(trade_fee, swap_fee_rate, SWAP_FEE_DENOMINATOR); + + let output_amount_swapped = ((output_vault_amount as u128) + .saturating_mul(input_amount_less_fees as u128) + / (input_vault_amount as u128).saturating_add(input_amount_less_fees as u128)) + as u64; + + let output_amount = output_amount_swapped.saturating_sub(swap_fee); + + SwapResult { + new_input_vault_amount: input_vault_amount.saturating_add(input_amount_less_fees), + new_output_vault_amount: output_vault_amount.saturating_sub(output_amount_swapped), + input_amount, + output_amount, + trade_fee, + swap_fee, + } +} + +/// Computes swap parameters including amounts, fees, and slippage protection. +/// +/// This function calculates the expected output amount, minimum output amount (with slippage), +/// and trading fees for a given input amount in a Raydium AMM V4 pool. +/// +/// # Arguments +/// * `base_reserve` - The current reserve amount of the base token in the pool +/// * `quote_reserve` - The current reserve amount of the quote token in the pool +/// * `is_base_in` - Whether the input token is the base token (true) or quote token (false) +/// * `amount_in` - The amount of input tokens to swap +/// * `slippage_basis_points` - The acceptable slippage in basis points (e.g., 100 for 1%) +/// +/// # Returns +/// A `ComputeSwapParams` struct containing all computed swap parameters +pub fn compute_swap_amount( + base_reserve: u64, + quote_reserve: u64, + is_base_in: bool, + amount_in: u64, + slippage_basis_points: u64, +) -> ComputeSwapParams { + let (input_reserve, output_reserve) = + if is_base_in { (base_reserve, quote_reserve) } else { (quote_reserve, base_reserve) }; + + let swap_result = swap_base_input( + amount_in, + input_reserve, + output_reserve, + TRADE_FEE_NUMERATOR, + SWAP_FEE_NUMERATOR, + ); + + let min_amount_out = ((swap_result.output_amount as f64) + * (1.0 - (slippage_basis_points as f64) / 10000.0)) as u64; + + let all_trade = swap_result.input_amount == amount_in; + + ComputeSwapParams { + all_trade, + amount_in, + amount_out: swap_result.output_amount, + min_amount_out, + fee: swap_result.trade_fee, + } +}