feat: Add Raydium AMM V4 support and middleware system
- Add Raydium AMM V4 trading protocol support - Implement instruction middleware system for dynamic processing - Refactor trade executors to support middleware parameters - Add fee calculation and slippage protection mechanisms - Maintain backward compatibility with optional middleware
This commit is contained in:
+2
-2
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "sol-trade-sdk"
|
||||
version = "0.4.0"
|
||||
version = "0.4.1"
|
||||
edition = "2021"
|
||||
authors = ["William <byteblock6@gmail.com>", "sgxiang <sgxiang@gmail.com>", "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"
|
||||
|
||||
@@ -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<dyn std::error::Error>> {
|
||||
}
|
||||
```
|
||||
|
||||
### 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<dyn std::error::Error>> {
|
||||
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<dyn std::error::Error>> {
|
||||
}
|
||||
```
|
||||
|
||||
### 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<Instruction>,
|
||||
protocol_name: String,
|
||||
is_buy: bool,
|
||||
) -> Result<Vec<Instruction>> {
|
||||
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<Instruction>,
|
||||
protocol_name: String,
|
||||
is_buy: bool,
|
||||
) -> Result<Vec<Instruction>> {
|
||||
println!("Custom middleware processing, instruction count: {}", full_instructions.len());
|
||||
Ok(full_instructions)
|
||||
}
|
||||
|
||||
fn clone_box(&self) -> Box<dyn InstructionMiddleware> {
|
||||
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
|
||||
```
|
||||
|
||||
+239
-20
@@ -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<dyn std::error::Error>> {
|
||||
}
|
||||
```
|
||||
|
||||
### 6. Bonk 交易操作
|
||||
### 6. Raydium AMM V4 交易操作
|
||||
|
||||
```rust
|
||||
use sol_trade_sdk::trading::core::params::RaydiumAmmV4Params;
|
||||
|
||||
async fn test_raydium_amm_v4() -> Result<(), Box<dyn std::error::Error>> {
|
||||
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<dyn std::error::Error>> {
|
||||
}
|
||||
```
|
||||
|
||||
### 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<Instruction>,
|
||||
protocol_name: String,
|
||||
is_buy: bool,
|
||||
) -> Result<Vec<Instruction>> {
|
||||
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<Instruction>,
|
||||
protocol_name: String,
|
||||
is_buy: bool,
|
||||
) -> Result<Vec<Instruction>> {
|
||||
println!("自定义中间件处理中,指令数量: {}", full_instructions.len());
|
||||
Ok(full_instructions)
|
||||
}
|
||||
|
||||
fn clone_box(&self) -> Box<dyn InstructionMiddleware> {
|
||||
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 # 示例程序
|
||||
```
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Executable
+33
@@ -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];
|
||||
@@ -1,4 +1,5 @@
|
||||
pub mod pumpfun;
|
||||
pub mod pumpswap;
|
||||
pub mod bonk;
|
||||
pub mod raydium_cpmm;
|
||||
pub mod raydium_cpmm;
|
||||
pub mod raydium_amm_v4;
|
||||
Executable
+248
@@ -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<Vec<Instruction>> {
|
||||
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<Vec<Instruction>> {
|
||||
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<Vec<Instruction>> {
|
||||
let protocol_params = params
|
||||
.protocol_params
|
||||
.as_any()
|
||||
.downcast_ref::<RaydiumAmmV4Params>()
|
||||
.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<Vec<Instruction>> {
|
||||
let protocol_params = params
|
||||
.protocol_params
|
||||
.as_any()
|
||||
.downcast_ref::<RaydiumAmmV4Params>()
|
||||
.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)
|
||||
}
|
||||
}
|
||||
+44
-53
@@ -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<Arc<SwqosClient>>,
|
||||
pub priority_fee: PriorityFee,
|
||||
pub trade_config: TradeConfig,
|
||||
pub middleware_manager: Option<Arc<MiddlewareManager>>,
|
||||
}
|
||||
|
||||
static INSTANCE: Mutex<Option<Arc<SolanaTrade>>> = 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<SolanaRpcClient> {
|
||||
&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::<PumpFunParams>()
|
||||
.is_some(),
|
||||
DexType::PumpSwap => protocol_params
|
||||
.as_any()
|
||||
.downcast_ref::<PumpSwapParams>()
|
||||
.is_some(),
|
||||
DexType::Bonk => protocol_params
|
||||
.as_any()
|
||||
.downcast_ref::<BonkParams>()
|
||||
.is_some(),
|
||||
DexType::RaydiumCpmm => protocol_params
|
||||
.as_any()
|
||||
.downcast_ref::<RaydiumCpmmParams>()
|
||||
.is_some(),
|
||||
DexType::PumpFun => protocol_params.as_any().downcast_ref::<PumpFunParams>().is_some(),
|
||||
DexType::PumpSwap => {
|
||||
protocol_params.as_any().downcast_ref::<PumpSwapParams>().is_some()
|
||||
}
|
||||
DexType::Bonk => protocol_params.as_any().downcast_ref::<BonkParams>().is_some(),
|
||||
DexType::RaydiumCpmm => {
|
||||
protocol_params.as_any().downcast_ref::<RaydiumCpmmParams>().is_some()
|
||||
}
|
||||
DexType::RaydiumAmmV4 => {
|
||||
protocol_params.as_any().downcast_ref::<RaydiumAmmV4Params>().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::<PumpFunParams>()
|
||||
.is_some(),
|
||||
DexType::PumpSwap => protocol_params
|
||||
.as_any()
|
||||
.downcast_ref::<PumpSwapParams>()
|
||||
.is_some(),
|
||||
DexType::Bonk => protocol_params
|
||||
.as_any()
|
||||
.downcast_ref::<BonkParams>()
|
||||
.is_some(),
|
||||
DexType::RaydiumCpmm => protocol_params
|
||||
.as_any()
|
||||
.downcast_ref::<RaydiumCpmmParams>()
|
||||
.is_some(),
|
||||
DexType::PumpFun => protocol_params.as_any().downcast_ref::<PumpFunParams>().is_some(),
|
||||
DexType::PumpSwap => {
|
||||
protocol_params.as_any().downcast_ref::<PumpSwapParams>().is_some()
|
||||
}
|
||||
DexType::Bonk => protocol_params.as_any().downcast_ref::<BonkParams>().is_some(),
|
||||
DexType::RaydiumCpmm => {
|
||||
protocol_params.as_any().downcast_ref::<RaydiumCpmmParams>().is_some()
|
||||
}
|
||||
DexType::RaydiumAmmV4 => {
|
||||
protocol_params.as_any().downcast_ref::<RaydiumAmmV4Params>().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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+103
-18
@@ -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<dyn std::error::Error>> {
|
||||
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<SwqosConfig>) -> 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<dyn std::error::Error>> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn test_raydium_amm_v4() -> Result<(), Box<dyn std::error::Error>> {
|
||||
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<dyn std::error::Error>> {
|
||||
println!("Subscribing to GRPC events...");
|
||||
|
||||
|
||||
@@ -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<Pubkey>,
|
||||
recent_blockhash: Hash,
|
||||
data_size_limit: u32,
|
||||
middleware_manager: Option<Arc<MiddlewareManager>>,
|
||||
protocol_name: String,
|
||||
is_buy: bool,
|
||||
) -> Result<VersionedTransaction, anyhow::Error> {
|
||||
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<Pubkey>,
|
||||
recent_blockhash: Hash,
|
||||
data_size_limit: u32,
|
||||
middleware_manager: Option<Arc<MiddlewareManager>>,
|
||||
protocol_name: String,
|
||||
is_buy: bool,
|
||||
) -> Result<VersionedTransaction, anyhow::Error> {
|
||||
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<Instruction>,
|
||||
address_lookup_table_accounts: Vec<solana_sdk::message::AddressLookupTableAccount>,
|
||||
blockhash: Hash,
|
||||
middleware_manager: Option<Arc<MiddlewareManager>>,
|
||||
protocol_name: String,
|
||||
is_buy: bool,
|
||||
) -> Result<VersionedTransaction, anyhow::Error> {
|
||||
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<Pubkey>,
|
||||
recent_blockhash: Hash,
|
||||
data_size_limit: u32,
|
||||
middleware_manager: Option<Arc<MiddlewareManager>>,
|
||||
protocol_name: String,
|
||||
is_buy: bool,
|
||||
) -> Result<VersionedTransaction, anyhow::Error> {
|
||||
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<Instruction>,
|
||||
lookup_table_key: Option<Pubkey>,
|
||||
recent_blockhash: Hash,
|
||||
middleware_manager: Option<Arc<MiddlewareManager>>,
|
||||
protocol_name: String,
|
||||
is_buy: bool,
|
||||
) -> Result<VersionedTransaction, anyhow::Error> {
|
||||
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<Pubkey>,
|
||||
recent_blockhash: Hash,
|
||||
middleware_manager: Option<Arc<MiddlewareManager>>,
|
||||
protocol_name: String,
|
||||
is_buy: bool,
|
||||
) -> Result<VersionedTransaction, anyhow::Error> {
|
||||
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<Pubkey>,
|
||||
recent_blockhash: Hash,
|
||||
middleware_manager: Option<Arc<MiddlewareManager>>,
|
||||
protocol_name: String,
|
||||
is_buy: bool,
|
||||
) -> Result<VersionedTransaction, anyhow::Error> {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -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::<u64>().map_err(|e| anyhow!("Failed to parse token0 balance: {}", e))?;
|
||||
let token1_amount =
|
||||
token1_balance.amount.parse::<u64>().map_err(|e| anyhow!("Failed to parse token1 balance: {}", e))?;
|
||||
Ok((token0_amount, token1_amount))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn get_token_balance(
|
||||
rpc: &SolanaRpcClient,
|
||||
|
||||
@@ -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<dyn InstructionBuilder>,
|
||||
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<Arc<MiddlewareManager>>,
|
||||
) -> 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<Arc<MiddlewareManager>>,
|
||||
) -> 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<Arc<MiddlewareManager>>,
|
||||
) -> 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<Arc<MiddlewareManager>>,
|
||||
) -> 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?;
|
||||
|
||||
|
||||
@@ -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<Arc<MiddlewareManager>>,
|
||||
protocol_name: String,
|
||||
is_buy: bool,
|
||||
) -> Result<()> {
|
||||
let cores = core_affinity::get_core_ids().unwrap();
|
||||
let mut handles: Vec<JoinHandle<Result<()>>> = 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>(())
|
||||
|
||||
@@ -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<Self, anyhow::Error> {
|
||||
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<dyn ProtocolParams> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl BuyParams {
|
||||
/// Convert to BuyWithTipParams
|
||||
/// Transforms basic buy parameters into MEV-enabled parameters
|
||||
|
||||
@@ -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<Arc<MiddlewareManager>>) -> Result<()>;
|
||||
|
||||
/// 使用MEV服务执行买入交易
|
||||
async fn buy_with_tip(&self, params: BuyWithTipParams) -> Result<()>;
|
||||
async fn buy_with_tip(&self, params: BuyWithTipParams, middleware_manager: Option<Arc<MiddlewareManager>>) -> Result<()>;
|
||||
|
||||
/// 执行卖出交易
|
||||
async fn sell(&self, params: SellParams) -> Result<()>;
|
||||
async fn sell(&self, params: SellParams, middleware_manager: Option<Arc<MiddlewareManager>>) -> Result<()>;
|
||||
|
||||
/// 使用MEV服务执行卖出交易
|
||||
async fn sell_with_tip(&self, params: SellWithTipParams) -> Result<()>;
|
||||
async fn sell_with_tip(&self, params: SellWithTipParams, middleware_manager: Option<Arc<MiddlewareManager>>) -> Result<()>;
|
||||
|
||||
/// 获取协议名称
|
||||
fn protocol_name(&self) -> &'static str;
|
||||
|
||||
+10
-5
@@ -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"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Instruction>,
|
||||
protocol_name: String,
|
||||
is_buy: bool,
|
||||
) -> Result<Vec<Instruction>> {
|
||||
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<Instruction>,
|
||||
protocol_name: String,
|
||||
is_buy: bool,
|
||||
) -> Result<Vec<Instruction>> {
|
||||
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<dyn InstructionMiddleware> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
pub mod traits;
|
||||
pub mod builtin;
|
||||
|
||||
pub use traits::{InstructionMiddleware, MiddlewareManager};
|
||||
@@ -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<Instruction>,
|
||||
protocol_name: String,
|
||||
is_buy: bool,
|
||||
) -> Result<Vec<Instruction>>;
|
||||
|
||||
/// 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<Instruction>,
|
||||
protocol_name: String,
|
||||
is_buy: bool,
|
||||
) -> Result<Vec<Instruction>>;
|
||||
|
||||
/// Clone middleware
|
||||
fn clone_box(&self) -> Box<dyn InstructionMiddleware>;
|
||||
}
|
||||
|
||||
/// Middleware manager
|
||||
pub struct MiddlewareManager {
|
||||
middlewares: Vec<Box<dyn InstructionMiddleware>>,
|
||||
}
|
||||
|
||||
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<dyn InstructionMiddleware>) -> Self {
|
||||
self.middlewares.push(middleware);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn apply_middlewares_process_full_instructions(
|
||||
&self,
|
||||
mut full_instructions: Vec<Instruction>,
|
||||
protocol_name: String,
|
||||
is_buy: bool,
|
||||
) -> Result<Vec<Instruction>> {
|
||||
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<Instruction>,
|
||||
protocol_name: String,
|
||||
is_buy: bool,
|
||||
) -> Result<Vec<Instruction>> {
|
||||
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))
|
||||
}
|
||||
}
|
||||
+4
-1
@@ -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};
|
||||
|
||||
Executable
+14
@@ -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<AmmInfo, anyhow::Error> {
|
||||
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)
|
||||
}
|
||||
Executable
+1
@@ -0,0 +1 @@
|
||||
pub mod common;
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user