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:
ysq
2025-08-19 18:07:21 +08:00
parent 4b74d3cfb4
commit d27a44735a
23 changed files with 1534 additions and 168 deletions
+2 -2
View File
@@ -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"
+239 -20
View File
@@ -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
View File
@@ -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 # 示例程序
```
+1
View File
@@ -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 {
+33
View File
@@ -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];
+2 -1
View File
@@ -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;
+248
View File
@@ -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(
&params.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(
&params.payer.pubkey(),
&params.payer.pubkey(),
&accounts::WSOL_TOKEN_ACCOUNT,
&accounts::TOKEN_PROGRAM,
),
);
instructions.push(
// Transfer SOL to wSOL ATA account
transfer(&params.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(
&params.payer.pubkey(),
&params.payer.pubkey(),
&params.mint,
&accounts::TOKEN_PROGRAM,
));
let user_source_token_account = spl_associated_token_account::get_associated_token_address(
&params.payer.pubkey(),
&accounts::WSOL_TOKEN_ACCOUNT,
);
let user_destination_token_account =
spl_associated_token_account::get_associated_token_address(
&params.payer.pubkey(),
&params.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,
&params.payer.pubkey(),
&params.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(
&params.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(
&params.payer.pubkey(),
&params.payer.pubkey(),
&accounts::WSOL_TOKEN_ACCOUNT,
&accounts::TOKEN_PROGRAM,
),
);
let user_source_token_account = spl_associated_token_account::get_associated_token_address(
&params.payer.pubkey(),
&params.mint,
);
let user_destination_token_account =
spl_associated_token_account::get_associated_token_address(
&params.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(&params.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,
&params.payer.pubkey(),
&params.payer.pubkey(),
&[&params.payer.pubkey()],
)
.unwrap(),
);
}
Ok(instructions)
}
}
+44 -53
View File
@@ -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
View File
@@ -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...");
+62 -5
View File
@@ -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
}
+19
View File
@@ -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,
+81 -29
View File
@@ -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(&params)
.await?;
let instructions = self.instruction_builder.build_buy_instructions(&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.stage("构建rpc交易指令");
// 构建交易
let transaction = build_rpc_transaction(
params.payer.clone(),
&params.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(&params)
.await?;
let instructions = self.instruction_builder.build_sell_instructions(&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.stage("卖出交易指令");
// 构建交易
let transaction = build_sell_transaction(
params.payer.clone(),
&params.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?;
+29 -9
View File
@@ -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>(())
+73
View File
@@ -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
+8 -4
View File
@@ -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
View File
@@ -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"))
}
}
}
+53
View File
@@ -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())
}
}
+4
View File
@@ -0,0 +1,4 @@
pub mod traits;
pub mod builtin;
pub use traits::{InstructionMiddleware, MiddlewareManager};
+115
View File
@@ -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
View File
@@ -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};
+14
View File
@@ -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)
}
+1
View File
@@ -0,0 +1 @@
pub mod common;
+150 -1
View File
@@ -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,
}
}