feat: integrate Raydium CPMM trading protocol support
- Add Raydium CPMM constants, instruction builder and pool implementation - Implement unified buy/sell trading interface and event parsing system - Update documentation and examples to support new protocol
This commit is contained in:
@@ -1,18 +1,19 @@
|
||||
# Sol Trade SDK
|
||||
|
||||
A comprehensive Rust SDK for seamless interaction with Solana DEX trading programs. This SDK provides a robust set of tools and interfaces to integrate PumpFun, PumpSwap, and Bonk functionality into your applications.
|
||||
A comprehensive Rust SDK for seamless interaction with Solana DEX trading programs. This SDK provides a robust set of tools and interfaces to integrate PumpFun, PumpSwap, Bonk, and Raydium CPMM functionality into your applications.
|
||||
|
||||
## Project Features
|
||||
|
||||
1. **PumpFun Trading**: Support for `buy` and `sell` operations
|
||||
2. **PumpSwap Trading**: Support for PumpSwap pool trading operations
|
||||
3. **Bonk Trading**: Support for Bonk trading operations
|
||||
4. **Event Subscription**: Subscribe to PumpFun, PumpSwap, and Bonk program trading events
|
||||
5. **Yellowstone gRPC**: Subscribe to program events using Yellowstone gRPC
|
||||
6. **ShredStream Support**: Subscribe to program events using ShredStream
|
||||
7. **Multiple MEV Protection**: Support for Jito, Nextblock, ZeroSlot, Temporal, Bloxroute, and other services
|
||||
8. **Concurrent Trading**: Send transactions using multiple MEV services simultaneously; the fastest succeeds while others fail
|
||||
9. **Unified Trading Interface**: Use unified trading protocol enums for trading operations
|
||||
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
|
||||
|
||||
## Installation
|
||||
|
||||
@@ -47,6 +48,7 @@ use sol_trade_sdk::{
|
||||
PumpSwapBuyEvent, PumpSwapCreatePoolEvent, PumpSwapDepositEvent,
|
||||
PumpSwapSellEvent, PumpSwapWithdrawEvent,
|
||||
},
|
||||
raydium_cpmm::RaydiumCpmmSwapEvent,
|
||||
},
|
||||
Protocol, UnifiedEvent,
|
||||
},
|
||||
@@ -94,12 +96,15 @@ async fn test_grpc() -> Result<(), Box<dyn std::error::Error>> {
|
||||
PumpSwapWithdrawEvent => |e: PumpSwapWithdrawEvent| {
|
||||
println!("Withdraw event: {:?}", e);
|
||||
},
|
||||
RaydiumCpmmSwapEvent => |e: RaydiumCpmmSwapEvent| {
|
||||
println!("Raydium CPMM Swap event: {:?}", e);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// Subscribe to events from multiple protocols
|
||||
println!("Starting to listen for events, press Ctrl+C to stop...");
|
||||
let protocols = vec![Protocol::PumpFun, Protocol::PumpSwap, Protocol::Bonk];
|
||||
let protocols = vec![Protocol::PumpFun, Protocol::PumpSwap, Protocol::Bonk, Protocol::RaydiumCpmm];
|
||||
grpc.subscribe_events(protocols, None, None, None, callback)
|
||||
.await?;
|
||||
|
||||
@@ -148,12 +153,15 @@ async fn test_shreds() -> Result<(), Box<dyn std::error::Error>> {
|
||||
PumpSwapWithdrawEvent => |e: PumpSwapWithdrawEvent| {
|
||||
println!("Withdraw event: {:?}", e);
|
||||
},
|
||||
RaydiumCpmmSwapEvent => |e: RaydiumCpmmSwapEvent| {
|
||||
println!("Raydium CPMM Swap event: {:?}", e);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// Subscribe to events
|
||||
println!("Starting to listen for events, press Ctrl+C to stop...");
|
||||
let protocols = vec![Protocol::PumpFun, Protocol::PumpSwap, Protocol::Bonk];
|
||||
let protocols = vec![Protocol::PumpFun, Protocol::PumpSwap, Protocol::Bonk, Protocol::RaydiumCpmm];
|
||||
shred_stream
|
||||
.shredstream_subscribe(protocols, None, callback)
|
||||
.await?;
|
||||
@@ -364,7 +372,71 @@ async fn test_pumpswap() -> AnyResult<()> {
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Bonk Trading Operations
|
||||
### 5. Raydium CPMM Trading Operations
|
||||
|
||||
```rust
|
||||
use sol_trade_sdk::{
|
||||
trading::{
|
||||
core::params::RaydiumCpmmParams,
|
||||
factory::DexType,
|
||||
raydium_cpmm::common::{get_buy_token_amount, get_sell_sol_amount}
|
||||
},
|
||||
};
|
||||
|
||||
async fn test_raydium_cpmm() -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("Testing Raydium CPMM trading...");
|
||||
|
||||
let trade_client = test_create_solana_trade_client().await?;
|
||||
|
||||
let mint_pubkey = Pubkey::from_str("xxxxxxxx")?; // 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 pool_state = Pubkey::from_str("xxxxxxx")?; // Pool state address
|
||||
|
||||
// Calculate expected token amount when buying
|
||||
let buy_amount_out = get_buy_token_amount(&trade_client.rpc, &pool_state, buy_sol_cost).await?;
|
||||
|
||||
println!("Buying tokens from Raydium CPMM...");
|
||||
trade_client.buy(
|
||||
DexType::RaydiumCpmm,
|
||||
mint_pubkey,
|
||||
None,
|
||||
buy_sol_cost,
|
||||
slippage_basis_points,
|
||||
recent_blockhash,
|
||||
None,
|
||||
Some(Box::new(RaydiumCpmmParams {
|
||||
pool_state: Some(pool_state), // If not provided, will auto-calculate wsol-mint direction pool
|
||||
minimum_amount_out: Some(buy_amount_out), // If not provided, defaults to 0
|
||||
auto_handle_wsol: true, // Automatically handle wSOL wrapping/unwrapping
|
||||
})),
|
||||
).await?;
|
||||
|
||||
println!("Selling tokens from Raydium CPMM...");
|
||||
let amount_token = 100_000_000; // Token amount to sell
|
||||
let sell_sol_amount = get_sell_sol_amount(&trade_client.rpc, &pool_state, amount_token).await?;
|
||||
|
||||
trade_client.sell(
|
||||
DexType::RaydiumCpmm,
|
||||
mint_pubkey,
|
||||
None,
|
||||
amount_token,
|
||||
slippage_basis_points,
|
||||
recent_blockhash,
|
||||
None,
|
||||
Some(Box::new(RaydiumCpmmParams {
|
||||
pool_state: Some(pool_state), // If not provided, will auto-calculate wsol-mint direction pool
|
||||
minimum_amount_out: Some(sell_sol_amount), // If not provided, defaults to 0
|
||||
auto_handle_wsol: true, // Automatically handle wSOL wrapping/unwrapping
|
||||
})),
|
||||
).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
### 6. Bonk Trading Operations
|
||||
|
||||
```rust
|
||||
|
||||
@@ -496,7 +568,7 @@ async fn test_bonk() -> Result<(), Box<dyn std::error::Error>> {
|
||||
}
|
||||
```
|
||||
|
||||
### 6. Custom Priority Fee Configuration
|
||||
### 7. Custom Priority Fee Configuration
|
||||
|
||||
```rust
|
||||
use sol_trade_sdk::common::PriorityFee;
|
||||
@@ -527,6 +599,7 @@ let trade_config = TradeConfig {
|
||||
- **PumpFun**: Primary meme coin trading platform
|
||||
- **PumpSwap**: PumpFun's swap protocol
|
||||
- **Bonk**: Token launch platform (letsbonk.fun)
|
||||
- **Raydium CPMM**: Raydium's Concentrated Pool Market Maker protocol
|
||||
|
||||
## MEV Protection Services
|
||||
|
||||
@@ -540,9 +613,9 @@ let trade_config = TradeConfig {
|
||||
|
||||
### Unified Trading Interface
|
||||
|
||||
- **TradingProtocol Enum**: Use unified protocol enums (PumpFun, PumpSwap, Bonk)
|
||||
- **TradingProtocol Enum**: Use unified protocol enums (PumpFun, PumpSwap, Bonk, RaydiumCpmm)
|
||||
- **Unified buy/sell Methods**: All protocols use the same trading method signatures
|
||||
- **Protocol-specific Parameters**: Each protocol has its own parameter structure (PumpFunParams, etc.)
|
||||
- **Protocol-specific Parameters**: Each protocol has its own parameter structure (PumpFunParams, RaydiumCpmmParams, etc.)
|
||||
|
||||
### Event Parsing System
|
||||
|
||||
@@ -570,7 +643,8 @@ src/
|
||||
│ │ ├── protocols/# Protocol-specific parsers
|
||||
│ │ │ ├── bonk/ # Bonk event parsing
|
||||
│ │ │ ├── pumpfun/ # PumpFun event parsing
|
||||
│ │ │ └── pumpswap/ # PumpSwap event parsing
|
||||
│ │ │ ├── pumpswap/ # PumpSwap event parsing
|
||||
│ │ │ └── raydium_cpmm/ # Raydium CPMM event parsing
|
||||
│ │ └── factory.rs # Parser factory
|
||||
│ ├── shred_stream.rs # ShredStream client
|
||||
│ └── yellowstone_grpc.rs # Yellowstone gRPC client
|
||||
@@ -581,6 +655,7 @@ src/
|
||||
│ ├── bonk/ # Bonk trading implementation
|
||||
│ ├── pumpfun/ # PumpFun trading implementation
|
||||
│ ├── pumpswap/ # PumpSwap trading implementation
|
||||
│ ├── raydium_cpmm/ # Raydium CPMM trading implementation
|
||||
│ └── factory.rs # Trading factory
|
||||
├── lib.rs # Main library file
|
||||
└── main.rs # Example program
|
||||
|
||||
+88
-13
@@ -7,12 +7,13 @@
|
||||
1. **PumpFun 交易**: 支持`购买`、`卖出`功能
|
||||
2. **PumpSwap 交易**: 支持 PumpSwap 池的交易操作
|
||||
3. **Bonk 交易**: 支持 Bonk 的交易操作
|
||||
4. **事件订阅**: 订阅 PumpFun、PumpSwap 和 Bonk 程序的交易事件
|
||||
5. **Yellowstone gRPC**: 使用 Yellowstone gRPC 订阅程序事件
|
||||
6. **ShredStream 支持**: 使用 ShredStream 订阅程序事件
|
||||
7. **多种 MEV 保护**: 支持 Jito、Nextblock、ZeroSlot、Temporal、Bloxroute 等服务
|
||||
8. **并发交易**: 同时使用多个 MEV 服务发送交易,最快的成功,其他失败
|
||||
9. **统一交易接口**: 使用统一的交易协议枚举进行交易操作
|
||||
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. **统一交易接口**: 使用统一的交易协议枚举进行交易操作
|
||||
|
||||
## 安装
|
||||
|
||||
@@ -47,6 +48,7 @@ use sol_trade_sdk::{
|
||||
PumpSwapBuyEvent, PumpSwapCreatePoolEvent, PumpSwapDepositEvent,
|
||||
PumpSwapSellEvent, PumpSwapWithdrawEvent,
|
||||
},
|
||||
raydium_cpmm::RaydiumCpmmSwapEvent,
|
||||
},
|
||||
Protocol, UnifiedEvent,
|
||||
},
|
||||
@@ -94,12 +96,15 @@ async fn test_grpc() -> Result<(), Box<dyn std::error::Error>> {
|
||||
PumpSwapWithdrawEvent => |e: PumpSwapWithdrawEvent| {
|
||||
println!("Withdraw event: {:?}", e);
|
||||
},
|
||||
RaydiumCpmmSwapEvent => |e: RaydiumCpmmSwapEvent| {
|
||||
println!("Raydium CPMM Swap event: {:?}", e);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// 订阅多个协议的事件
|
||||
println!("开始监听事件,按 Ctrl+C 停止...");
|
||||
let protocols = vec![Protocol::PumpFun, Protocol::PumpSwap, Protocol::Bonk];
|
||||
let protocols = vec![Protocol::PumpFun, Protocol::PumpSwap, Protocol::Bonk, Protocol::RaydiumCpmm];
|
||||
grpc.subscribe_events(protocols, None, None, None, callback)
|
||||
.await?;
|
||||
|
||||
@@ -148,12 +153,15 @@ async fn test_shreds() -> Result<(), Box<dyn std::error::Error>> {
|
||||
PumpSwapWithdrawEvent => |e: PumpSwapWithdrawEvent| {
|
||||
println!("Withdraw event: {:?}", e);
|
||||
},
|
||||
RaydiumCpmmSwapEvent => |e: RaydiumCpmmSwapEvent| {
|
||||
println!("Raydium CPMM Swap event: {:?}", e);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// 订阅事件
|
||||
println!("开始监听事件,按 Ctrl+C 停止...");
|
||||
let protocols = vec![Protocol::PumpFun, Protocol::PumpSwap, Protocol::Bonk];
|
||||
let protocols = vec![Protocol::PumpFun, Protocol::PumpSwap, Protocol::Bonk, Protocol::RaydiumCpmm];
|
||||
shred_stream
|
||||
.shredstream_subscribe(protocols, None, callback)
|
||||
.await?;
|
||||
@@ -362,7 +370,71 @@ async fn test_pumpswap() -> AnyResult<()> {
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Bonk 交易操作
|
||||
### 5. Raydium CPMM 交易操作
|
||||
|
||||
```rust
|
||||
use sol_trade_sdk::{
|
||||
trading::{
|
||||
core::params::RaydiumCpmmParams,
|
||||
factory::DexType,
|
||||
raydium_cpmm::common::{get_buy_token_amount, get_sell_sol_amount}
|
||||
},
|
||||
};
|
||||
|
||||
async fn test_raydium_cpmm() -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("Testing Raydium CPMM trading...");
|
||||
|
||||
let trade_client = test_create_solana_trade_client().await?;
|
||||
|
||||
let mint_pubkey = Pubkey::from_str("xxxxxxxx")?; // 代币地址
|
||||
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 pool_state = Pubkey::from_str("xxxxxxx")?; // 池状态地址
|
||||
|
||||
// 计算买入时预期获得的代币数量
|
||||
let buy_amount_out = get_buy_token_amount(&trade_client.rpc, &pool_state, buy_sol_cost).await?;
|
||||
|
||||
println!("Buying tokens from Raydium CPMM...");
|
||||
trade_client.buy(
|
||||
DexType::RaydiumCpmm,
|
||||
mint_pubkey,
|
||||
None,
|
||||
buy_sol_cost,
|
||||
slippage_basis_points,
|
||||
recent_blockhash,
|
||||
None,
|
||||
Some(Box::new(RaydiumCpmmParams {
|
||||
pool_state: Some(pool_state), // 如果不传,会自动计算 wsol-mint 方向的池子
|
||||
minimum_amount_out: Some(buy_amount_out), // 如果不传,默认为0
|
||||
auto_handle_wsol: true, // 自动处理 wSOL 包装/解包装
|
||||
})),
|
||||
).await?;
|
||||
|
||||
println!("Selling tokens from Raydium CPMM...");
|
||||
let amount_token = 100_000_000; // 卖出代币数量
|
||||
let sell_sol_amount = get_sell_sol_amount(&trade_client.rpc, &pool_state, amount_token).await?;
|
||||
|
||||
trade_client.sell(
|
||||
DexType::RaydiumCpmm,
|
||||
mint_pubkey,
|
||||
None,
|
||||
amount_token,
|
||||
slippage_basis_points,
|
||||
recent_blockhash,
|
||||
None,
|
||||
Some(Box::new(RaydiumCpmmParams {
|
||||
pool_state: Some(pool_state), // 如果不传,会自动计算 wsol-mint 方向的池子
|
||||
minimum_amount_out: Some(sell_sol_amount), // 如果不传,默认为0
|
||||
auto_handle_wsol: true, // 自动处理 wSOL 包装/解包装
|
||||
})),
|
||||
).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
### 6. Bonk 交易操作
|
||||
|
||||
```rust
|
||||
|
||||
@@ -494,7 +566,7 @@ async fn test_bonk() -> Result<(), Box<dyn std::error::Error>> {
|
||||
}
|
||||
```
|
||||
|
||||
### 6. 自定义优先费用配置
|
||||
### 7. 自定义优先费用配置
|
||||
|
||||
```rust
|
||||
use sol_trade_sdk::common::PriorityFee;
|
||||
@@ -525,6 +597,7 @@ let trade_config = TradeConfig {
|
||||
- **PumpFun**: 主要的 meme 币交易平台
|
||||
- **PumpSwap**: PumpFun 的交换协议
|
||||
- **Bonk**: 代币发行平台(letsbonk.fun)
|
||||
- **Raydium CPMM**: Raydium 的集中流动性做市商协议
|
||||
|
||||
## MEV 保护服务
|
||||
|
||||
@@ -538,9 +611,9 @@ let trade_config = TradeConfig {
|
||||
|
||||
### 统一交易接口
|
||||
|
||||
- **TradingProtocol 枚举**: 使用统一的协议枚举(PumpFun、PumpSwap、Bonk)
|
||||
- **TradingProtocol 枚举**: 使用统一的协议枚举(PumpFun、PumpSwap、Bonk、RaydiumCpmm)
|
||||
- **统一的 buy/sell 方法**: 所有协议都使用相同的交易方法签名
|
||||
- **协议特定参数**: 每个协议都有自己的参数结构(PumpFunParams 等)
|
||||
- **协议特定参数**: 每个协议都有自己的参数结构(PumpFunParams、RaydiumCpmmParams 等)
|
||||
|
||||
### 事件解析系统
|
||||
|
||||
@@ -568,7 +641,8 @@ src/
|
||||
│ │ ├── protocols/# 协议特定解析器
|
||||
│ │ │ ├── bonk/ # Bonk事件解析
|
||||
│ │ │ ├── pumpfun/ # PumpFun事件解析
|
||||
│ │ │ └── pumpswap/ # PumpSwap事件解析
|
||||
│ │ │ ├── pumpswap/ # PumpSwap事件解析
|
||||
│ │ │ └── raydium_cpmm/ # Raydium CPMM事件解析
|
||||
│ │ └── factory.rs # 解析器工厂
|
||||
│ ├── shred_stream.rs # ShredStream客户端
|
||||
│ └── yellowstone_grpc.rs # Yellowstone gRPC客户端
|
||||
@@ -579,6 +653,7 @@ src/
|
||||
│ ├── bonk/ # Bonk交易实现
|
||||
│ ├── pumpfun/ # PumpFun交易实现
|
||||
│ ├── pumpswap/ # PumpSwap交易实现
|
||||
│ ├── raydium_cpmm/ # Raydium CPMM交易实现
|
||||
│ └── factory.rs # 交易工厂
|
||||
├── lib.rs # 主库文件
|
||||
└── main.rs # 示例程序
|
||||
|
||||
@@ -3,9 +3,11 @@ pub mod pumpfun;
|
||||
pub mod pumpswap;
|
||||
pub mod swqos;
|
||||
pub mod trade;
|
||||
pub mod raydium_cpmm;
|
||||
|
||||
pub mod trade_platform {
|
||||
pub const PUMPFUN: &'static str = "pumpfun";
|
||||
pub const PUMPFUN_SWAP: &'static str = "pumpswap";
|
||||
pub const BONK: &'static str = "bonk";
|
||||
pub const RAYDIUM_CPMM: &'static str = "raydium_cpmm";
|
||||
}
|
||||
|
||||
Executable
+31
@@ -0,0 +1,31 @@
|
||||
//! 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";
|
||||
pub const POOL_VAULT_SEED: &[u8] = b"pool_vault";
|
||||
pub const OBSERVATION_STATE_SEED: &[u8] = b"observation";
|
||||
}
|
||||
|
||||
/// Constants related to program accounts and authorities
|
||||
pub mod accounts {
|
||||
use solana_sdk::{pubkey, pubkey::Pubkey};
|
||||
pub const AUTHORITY: Pubkey = pubkey!("GpMZbSM2GgvTKHJirzeGfMFoaZ8UR2X7F4v8vHTvxFbL");
|
||||
pub const AMM_CONFIG: Pubkey = pubkey!("D4FPEruKEHrG5TenZ2mpDGEfu1iUvTiqBxvpU8HLBvC2");
|
||||
pub const TOKEN_PROGRAM: Pubkey = pubkey!("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA");
|
||||
pub const WSOL_TOKEN_ACCOUNT: Pubkey = pubkey!("So11111111111111111111111111111111111111112");
|
||||
pub const RAYDIUM_CPMM: Pubkey = pubkey!("CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C");
|
||||
}
|
||||
|
||||
pub const SWAP_BASE_IN_DISCRIMINATOR: &[u8] = &[143, 190, 90, 218, 196, 30, 51, 222];
|
||||
pub const SWAP_BASE_OUT_DISCRIMINATOR: &[u8] = &[55, 217, 98, 86, 163, 74, 180, 173];
|
||||
@@ -27,6 +27,7 @@ use solana_sdk::{
|
||||
pub mod pumpfun;
|
||||
pub mod pumpswap;
|
||||
pub mod bonk;
|
||||
pub mod raydium_cpmm;
|
||||
|
||||
pub struct Create {
|
||||
pub _name: String,
|
||||
|
||||
Executable
+301
@@ -0,0 +1,301 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use solana_sdk::{instruction::Instruction, signer::Signer};
|
||||
use spl_associated_token_account::instruction::create_associated_token_account_idempotent;
|
||||
use spl_token::instruction::close_account;
|
||||
|
||||
use crate::{
|
||||
constants::raydium_cpmm::{accounts, SWAP_BASE_IN_DISCRIMINATOR},
|
||||
constants::trade::trade::DEFAULT_SLIPPAGE,
|
||||
trading::common::utils::get_token_balance,
|
||||
trading::core::{
|
||||
params::{BuyParams, RaydiumCpmmParams, SellParams},
|
||||
traits::InstructionBuilder,
|
||||
},
|
||||
trading::raydium_cpmm::{
|
||||
common::{get_observation_state_pda, get_pool_pda, get_vault_pda},
|
||||
// pool::Pool,
|
||||
},
|
||||
};
|
||||
|
||||
/// RaydiumCpmm协议的指令构建器
|
||||
pub struct RaydiumCpmmInstructionBuilder;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl InstructionBuilder for RaydiumCpmmInstructionBuilder {
|
||||
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 RaydiumCpmmInstructionBuilder {
|
||||
/// 使用提供的账户信息构建买入指令
|
||||
async fn build_buy_instructions_with_accounts(
|
||||
&self,
|
||||
params: &BuyParams,
|
||||
) -> Result<Vec<Instruction>> {
|
||||
let protocol_params = params
|
||||
.protocol_params
|
||||
.as_any()
|
||||
.downcast_ref::<RaydiumCpmmParams>()
|
||||
.ok_or_else(|| anyhow!("Invalid protocol params for RaydiumCpmm"))?;
|
||||
|
||||
let pool_state = if protocol_params.pool_state.is_some() {
|
||||
protocol_params.pool_state.unwrap()
|
||||
} else {
|
||||
get_pool_pda(
|
||||
&accounts::AMM_CONFIG,
|
||||
&accounts::WSOL_TOKEN_ACCOUNT,
|
||||
¶ms.mint,
|
||||
)
|
||||
.unwrap()
|
||||
};
|
||||
|
||||
let wsol_token_account = spl_associated_token_account::get_associated_token_address(
|
||||
¶ms.payer.pubkey(),
|
||||
&accounts::WSOL_TOKEN_ACCOUNT,
|
||||
);
|
||||
let mint_token_account = spl_associated_token_account::get_associated_token_address(
|
||||
¶ms.payer.pubkey(),
|
||||
¶ms.mint,
|
||||
);
|
||||
|
||||
// 获取池的代币账户
|
||||
let wsol_vault_account = get_vault_pda(&pool_state, &accounts::WSOL_TOKEN_ACCOUNT).unwrap();
|
||||
let mint_vault_account = get_vault_pda(&pool_state, ¶ms.mint).unwrap();
|
||||
|
||||
let observation_state_account = get_observation_state_pda(&pool_state).unwrap();
|
||||
|
||||
let amount_in: u64 = params.sol_amount;
|
||||
let mut minimum_amount_out: u64 = if protocol_params.minimum_amount_out.is_some() {
|
||||
protocol_params.minimum_amount_out.unwrap()
|
||||
} else {
|
||||
println!("未提供minimum_amount_out,使用默认值0");
|
||||
0
|
||||
};
|
||||
if minimum_amount_out != 0 {
|
||||
let slippage_basis_points: u64 = if params.slippage_basis_points.is_some() {
|
||||
params.slippage_basis_points.unwrap()
|
||||
} else {
|
||||
DEFAULT_SLIPPAGE
|
||||
} as u64;
|
||||
minimum_amount_out = minimum_amount_out * (10000 - slippage_basis_points) / 10000;
|
||||
println!("slippage_basis_points: {}", slippage_basis_points);
|
||||
}
|
||||
println!("minimum_amount_out: {}", minimum_amount_out);
|
||||
|
||||
let mut instructions = vec![];
|
||||
|
||||
if protocol_params.auto_handle_wsol {
|
||||
// 插入wsol
|
||||
instructions.push(
|
||||
// 创建wSOL ATA账户,如果不存在
|
||||
create_associated_token_account_idempotent(
|
||||
¶ms.payer.pubkey(),
|
||||
¶ms.payer.pubkey(),
|
||||
&accounts::WSOL_TOKEN_ACCOUNT,
|
||||
&accounts::TOKEN_PROGRAM,
|
||||
),
|
||||
);
|
||||
instructions.push(
|
||||
// 将SOL转入wSOL ATA账户
|
||||
solana_sdk::system_instruction::transfer(
|
||||
¶ms.payer.pubkey(),
|
||||
&wsol_token_account,
|
||||
amount_in,
|
||||
),
|
||||
);
|
||||
|
||||
// 同步wSOL余额
|
||||
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 accounts = vec![
|
||||
solana_sdk::instruction::AccountMeta::new(params.payer.pubkey(), true), // Payer (signer)
|
||||
solana_sdk::instruction::AccountMeta::new_readonly(accounts::AUTHORITY, false), // Authority (readonly)
|
||||
solana_sdk::instruction::AccountMeta::new_readonly(accounts::AMM_CONFIG, false), // Amm Config (readonly)
|
||||
solana_sdk::instruction::AccountMeta::new(pool_state, false), // Pool State
|
||||
solana_sdk::instruction::AccountMeta::new(wsol_token_account, false), // Input Token Account
|
||||
solana_sdk::instruction::AccountMeta::new(mint_token_account, false), // Output Token Account
|
||||
solana_sdk::instruction::AccountMeta::new(wsol_vault_account, false), // Input Vault Account
|
||||
solana_sdk::instruction::AccountMeta::new(mint_vault_account, false), // Output Vault Account
|
||||
solana_sdk::instruction::AccountMeta::new_readonly(accounts::TOKEN_PROGRAM, false), // Input Token Program (readonly)
|
||||
solana_sdk::instruction::AccountMeta::new_readonly(accounts::TOKEN_PROGRAM, false), // Output Token Program (readonly)
|
||||
solana_sdk::instruction::AccountMeta::new_readonly(accounts::WSOL_TOKEN_ACCOUNT, false), // Input token mint (readonly)
|
||||
solana_sdk::instruction::AccountMeta::new_readonly(params.mint, false), // Output token mint (readonly)
|
||||
solana_sdk::instruction::AccountMeta::new(observation_state_account, false), // Observation State Account
|
||||
];
|
||||
// 创建指令数据
|
||||
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_CPMM,
|
||||
accounts,
|
||||
data,
|
||||
});
|
||||
|
||||
if protocol_params.auto_handle_wsol {
|
||||
// 关闭wSOL ATA账户,回收租金
|
||||
instructions.push(
|
||||
spl_token::instruction::close_account(
|
||||
&accounts::TOKEN_PROGRAM,
|
||||
&wsol_token_account,
|
||||
¶ms.payer.pubkey(),
|
||||
¶ms.payer.pubkey(),
|
||||
&[],
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
Ok(instructions)
|
||||
}
|
||||
|
||||
/// 使用提供的账户信息构建卖出指令
|
||||
async fn build_sell_instructions_with_accounts(
|
||||
&self,
|
||||
params: &SellParams,
|
||||
) -> Result<Vec<Instruction>> {
|
||||
let protocol_params = params
|
||||
.protocol_params
|
||||
.as_any()
|
||||
.downcast_ref::<RaydiumCpmmParams>()
|
||||
.ok_or_else(|| anyhow!("Invalid protocol params for RaydiumCpmm"))?;
|
||||
|
||||
if params.rpc.is_none() {
|
||||
return Err(anyhow!("RPC is not set"));
|
||||
}
|
||||
let rpc = params.rpc.as_ref().unwrap().clone();
|
||||
|
||||
// 获取代币余额
|
||||
let mut amount = params.token_amount;
|
||||
if params.token_amount.is_none() || params.token_amount.unwrap_or(0) == 0 {
|
||||
let balance_u64 =
|
||||
get_token_balance(rpc.as_ref(), ¶ms.payer.pubkey(), ¶ms.mint).await?;
|
||||
amount = Some(balance_u64);
|
||||
}
|
||||
let amount = amount.unwrap_or(0);
|
||||
|
||||
if amount == 0 {
|
||||
return Err(anyhow!("Amount cannot be zero"));
|
||||
}
|
||||
|
||||
let mut minimum_amount_out: u64 = if protocol_params.minimum_amount_out.is_some() {
|
||||
protocol_params.minimum_amount_out.unwrap()
|
||||
} else {
|
||||
println!("未提供minimum_amount_out,使用默认值0");
|
||||
0
|
||||
};
|
||||
if minimum_amount_out != 0 {
|
||||
let slippage_basis_points: u64 = if params.slippage_basis_points.is_some() {
|
||||
params.slippage_basis_points.unwrap()
|
||||
} else {
|
||||
DEFAULT_SLIPPAGE
|
||||
} as u64;
|
||||
minimum_amount_out = minimum_amount_out * (10000 - slippage_basis_points) / 10000;
|
||||
println!("slippage_basis_points: {}", slippage_basis_points);
|
||||
}
|
||||
println!("minimum_amount_out: {}", minimum_amount_out);
|
||||
|
||||
let pool_state = if protocol_params.pool_state.is_some() {
|
||||
protocol_params.pool_state.unwrap()
|
||||
} else {
|
||||
get_pool_pda(
|
||||
&accounts::AMM_CONFIG,
|
||||
&accounts::WSOL_TOKEN_ACCOUNT,
|
||||
¶ms.mint,
|
||||
)
|
||||
.unwrap()
|
||||
};
|
||||
|
||||
let wsol_token_account = spl_associated_token_account::get_associated_token_address(
|
||||
¶ms.payer.pubkey(),
|
||||
&accounts::WSOL_TOKEN_ACCOUNT,
|
||||
);
|
||||
let mint_token_account = spl_associated_token_account::get_associated_token_address(
|
||||
¶ms.payer.pubkey(),
|
||||
¶ms.mint,
|
||||
);
|
||||
|
||||
// 获取池的代币账户
|
||||
let wsol_vault_account = get_vault_pda(&pool_state, &accounts::WSOL_TOKEN_ACCOUNT).unwrap();
|
||||
let mint_vault_account = get_vault_pda(&pool_state, ¶ms.mint).unwrap();
|
||||
|
||||
let observation_state_account = get_observation_state_pda(&pool_state).unwrap();
|
||||
|
||||
let mut instructions = vec![];
|
||||
|
||||
// 插入wsol
|
||||
instructions.push(
|
||||
// 创建wSOL ATA账户,如果不存在
|
||||
create_associated_token_account_idempotent(
|
||||
¶ms.payer.pubkey(),
|
||||
¶ms.payer.pubkey(),
|
||||
&accounts::WSOL_TOKEN_ACCOUNT,
|
||||
&accounts::TOKEN_PROGRAM,
|
||||
),
|
||||
);
|
||||
|
||||
// 创建卖出指令
|
||||
let accounts = vec![
|
||||
solana_sdk::instruction::AccountMeta::new(params.payer.pubkey(), true), // Payer (signer)
|
||||
solana_sdk::instruction::AccountMeta::new_readonly(accounts::AUTHORITY, false), // Authority (readonly)
|
||||
solana_sdk::instruction::AccountMeta::new_readonly(accounts::AMM_CONFIG, false), // Amm Config (readonly)
|
||||
solana_sdk::instruction::AccountMeta::new(pool_state, false), // Pool State
|
||||
solana_sdk::instruction::AccountMeta::new(mint_token_account, false), // Input Token Account
|
||||
solana_sdk::instruction::AccountMeta::new(wsol_token_account, false), // Output Token Account
|
||||
solana_sdk::instruction::AccountMeta::new(mint_vault_account, false), // Input Vault Account
|
||||
solana_sdk::instruction::AccountMeta::new(wsol_vault_account, false), // Output Vault Account
|
||||
solana_sdk::instruction::AccountMeta::new_readonly(accounts::TOKEN_PROGRAM, false), // Input Token Program (readonly)
|
||||
solana_sdk::instruction::AccountMeta::new_readonly(accounts::TOKEN_PROGRAM, false), // Output Token Program (readonly)
|
||||
solana_sdk::instruction::AccountMeta::new_readonly(params.mint, false), // Input token mint (readonly)
|
||||
solana_sdk::instruction::AccountMeta::new_readonly(accounts::WSOL_TOKEN_ACCOUNT, false), // Output token mint (readonly)
|
||||
solana_sdk::instruction::AccountMeta::new(observation_state_account, false), // Observation State Account
|
||||
];
|
||||
// 创建指令数据
|
||||
let mut data = vec![];
|
||||
data.extend_from_slice(&SWAP_BASE_IN_DISCRIMINATOR);
|
||||
data.extend_from_slice(&amount.to_le_bytes());
|
||||
data.extend_from_slice(&minimum_amount_out.to_le_bytes());
|
||||
|
||||
instructions.push(Instruction {
|
||||
program_id: accounts::RAYDIUM_CPMM,
|
||||
accounts,
|
||||
data,
|
||||
});
|
||||
|
||||
instructions.push(
|
||||
close_account(
|
||||
&accounts::TOKEN_PROGRAM,
|
||||
&wsol_token_account,
|
||||
¶ms.payer.pubkey(),
|
||||
¶ms.payer.pubkey(),
|
||||
&[¶ms.payer.pubkey()],
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
Ok(instructions)
|
||||
}
|
||||
}
|
||||
+19
-12
@@ -11,6 +11,7 @@ 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::RaydiumCpmmParams;
|
||||
use crate::trading::core::traits::ProtocolParams;
|
||||
use crate::trading::factory::DexType;
|
||||
use crate::trading::BuyParams;
|
||||
@@ -179,13 +180,12 @@ impl SolanaTrade {
|
||||
params
|
||||
} else {
|
||||
match dex_type {
|
||||
DexType::PumpFun => {
|
||||
Box::new(PumpFunParams::default()) as Box<dyn ProtocolParams>
|
||||
}
|
||||
DexType::PumpSwap => {
|
||||
Box::new(PumpSwapParams::default()) as Box<dyn ProtocolParams>
|
||||
}
|
||||
DexType::PumpFun => Box::new(PumpFunParams::default()) as Box<dyn ProtocolParams>,
|
||||
DexType::PumpSwap => Box::new(PumpSwapParams::default()) as Box<dyn ProtocolParams>,
|
||||
DexType::Bonk => Box::new(BonkParams::default()) as Box<dyn ProtocolParams>,
|
||||
DexType::RaydiumCpmm => {
|
||||
Box::new(RaydiumCpmmParams::default()) as Box<dyn ProtocolParams>
|
||||
}
|
||||
}
|
||||
};
|
||||
let buy_params = BuyParams {
|
||||
@@ -227,6 +227,10 @@ impl SolanaTrade {
|
||||
.as_any()
|
||||
.downcast_ref::<BonkParams>()
|
||||
.is_some(),
|
||||
DexType::RaydiumCpmm => protocol_params
|
||||
.as_any()
|
||||
.downcast_ref::<RaydiumCpmmParams>()
|
||||
.is_some(),
|
||||
};
|
||||
|
||||
if !is_valid_params {
|
||||
@@ -301,13 +305,12 @@ impl SolanaTrade {
|
||||
params
|
||||
} else {
|
||||
match dex_type {
|
||||
DexType::PumpFun => {
|
||||
Box::new(PumpFunParams::default()) as Box<dyn ProtocolParams>
|
||||
}
|
||||
DexType::PumpSwap => {
|
||||
Box::new(PumpSwapParams::default()) as Box<dyn ProtocolParams>
|
||||
}
|
||||
DexType::PumpFun => Box::new(PumpFunParams::default()) as Box<dyn ProtocolParams>,
|
||||
DexType::PumpSwap => Box::new(PumpSwapParams::default()) as Box<dyn ProtocolParams>,
|
||||
DexType::Bonk => Box::new(BonkParams::default()) as Box<dyn ProtocolParams>,
|
||||
DexType::RaydiumCpmm => {
|
||||
Box::new(RaydiumCpmmParams::default()) as Box<dyn ProtocolParams>
|
||||
}
|
||||
}
|
||||
};
|
||||
let sell_params = SellParams {
|
||||
@@ -348,6 +351,10 @@ impl SolanaTrade {
|
||||
.as_any()
|
||||
.downcast_ref::<BonkParams>()
|
||||
.is_some(),
|
||||
DexType::RaydiumCpmm => protocol_params
|
||||
.as_any()
|
||||
.downcast_ref::<RaydiumCpmmParams>()
|
||||
.is_some(),
|
||||
};
|
||||
|
||||
if !is_valid_params {
|
||||
|
||||
+57
-3
@@ -11,14 +11,14 @@ use sol_trade_sdk::{
|
||||
pumpswap::{
|
||||
PumpSwapBuyEvent, PumpSwapCreatePoolEvent, PumpSwapDepositEvent,
|
||||
PumpSwapSellEvent, PumpSwapWithdrawEvent,
|
||||
},
|
||||
}, raydium_cpmm::RaydiumCpmmSwapEvent,
|
||||
},
|
||||
Protocol, UnifiedEvent,
|
||||
},
|
||||
ShredStreamGrpc, YellowstoneGrpc,
|
||||
},
|
||||
swqos::{SwqosConfig, SwqosRegion},
|
||||
trading::{core::params::{BonkParams, PumpFunParams}, factory::DexType},
|
||||
trading::{core::params::{BonkParams, PumpFunParams, RaydiumCpmmParams}, factory::DexType, raydium_cpmm::{common::{get_buy_token_amount, get_sell_sol_amount}}},
|
||||
SolanaTrade,
|
||||
};
|
||||
use solana_sdk::{commitment_config::CommitmentConfig, pubkey::Pubkey, signature::Keypair};
|
||||
@@ -28,6 +28,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
test_create_solana_trade_client().await?;
|
||||
test_pumpswap().await?;
|
||||
test_bonk().await?;
|
||||
test_raydium_cpmm().await?;
|
||||
test_grpc().await?;
|
||||
test_shreds().await?;
|
||||
Ok(())
|
||||
@@ -330,6 +331,56 @@ async fn test_bonk() -> Result<(), Box<dyn std::error::Error>> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
async fn test_raydium_cpmm() -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("Testing Raydium Cpmm trading...");
|
||||
|
||||
let client = test_create_solana_trade_client().await?;
|
||||
let mint_pubkey = Pubkey::from_str("xxxxxxxx")?;
|
||||
let buy_sol_cost = 100_000;
|
||||
let slippage_basis_points = Some(100);
|
||||
let recent_blockhash = client.rpc.get_latest_blockhash().await?;
|
||||
let pool_state = Pubkey::from_str("xxxxxxx")?;
|
||||
let buy_amount_out = get_buy_token_amount(&client.rpc, &pool_state, buy_sol_cost).await?;
|
||||
// Buy tokens
|
||||
println!("Buying tokens from Raydium Cpmm...");
|
||||
client.buy(
|
||||
DexType::RaydiumCpmm,
|
||||
mint_pubkey,
|
||||
None,
|
||||
buy_sol_cost,
|
||||
slippage_basis_points,
|
||||
recent_blockhash,
|
||||
None,
|
||||
Some(Box::new(RaydiumCpmmParams {
|
||||
pool_state: Some(pool_state), // 如果不传,会自动计算 wsol-mint 方向的池子
|
||||
minimum_amount_out: Some(buy_amount_out), // 如果不传、默认为0
|
||||
auto_handle_wsol: true,
|
||||
})),
|
||||
).await?;
|
||||
|
||||
// Sell tokens
|
||||
println!("Selling tokens from Raydium Cpmm...");
|
||||
let amount_token = 0;
|
||||
let sell_sol_amount = get_sell_sol_amount(&client.rpc, &pool_state, amount_token).await?;
|
||||
client.sell(
|
||||
DexType::RaydiumCpmm,
|
||||
mint_pubkey,
|
||||
None,
|
||||
amount_token,
|
||||
slippage_basis_points,
|
||||
recent_blockhash,
|
||||
None,
|
||||
Some(Box::new(RaydiumCpmmParams {
|
||||
pool_state: Some(pool_state), // 如果不传,会自动计算 wsol-mint 方向的池子
|
||||
minimum_amount_out: Some(sell_sol_amount), // 如果不传、默认为0
|
||||
auto_handle_wsol: true,
|
||||
})),
|
||||
).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn test_grpc() -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("正在订阅 GRPC 事件...");
|
||||
|
||||
@@ -339,7 +390,7 @@ async fn test_grpc() -> Result<(), Box<dyn std::error::Error>> {
|
||||
)?;
|
||||
|
||||
let callback = create_event_callback();
|
||||
let protocols = vec![Protocol::PumpFun, Protocol::PumpSwap, Protocol::Bonk];
|
||||
let protocols = vec![Protocol::PumpFun, Protocol::PumpSwap, Protocol::Bonk, Protocol::RaydiumCpmm];
|
||||
|
||||
println!("开始监听事件,按 Ctrl+C 停止...");
|
||||
grpc.subscribe_events(protocols, None, None, None, callback).await?;
|
||||
@@ -390,6 +441,9 @@ fn create_event_callback() -> impl Fn(Box<dyn UnifiedEvent>) {
|
||||
PumpSwapWithdrawEvent => |e: PumpSwapWithdrawEvent| {
|
||||
println!("Withdraw event: {:?}", e);
|
||||
},
|
||||
RaydiumCpmmSwapEvent => |e: RaydiumCpmmSwapEvent| {
|
||||
println!("RaydiumCpmmSwapEvent: {:?}", e);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ pub enum ProtocolType {
|
||||
PumpSwap,
|
||||
PumpFun,
|
||||
Bonk,
|
||||
RaydiumCpmm,
|
||||
}
|
||||
|
||||
/// 事件类型枚举
|
||||
@@ -39,6 +40,10 @@ pub enum EventType {
|
||||
BonkSellExactOut,
|
||||
BonkInitialize,
|
||||
|
||||
// Raydium CPMM 事件
|
||||
RaydiumCpmmSwapBaseInput,
|
||||
RaydiumCpmmSwapBaseOutput,
|
||||
|
||||
// 通用事件
|
||||
Unknown,
|
||||
}
|
||||
|
||||
@@ -3,7 +3,9 @@ use solana_sdk::pubkey::Pubkey;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::streaming::event_parser::protocols::{
|
||||
pumpfun::parser::PUMPFUN_PROGRAM_ID, pumpswap::parser::PUMPSWAP_PROGRAM_ID, bonk::parser::BONK_PROGRAM_ID, BonkEventParser,
|
||||
bonk::parser::BONK_PROGRAM_ID, pumpfun::parser::PUMPFUN_PROGRAM_ID,
|
||||
pumpswap::parser::PUMPSWAP_PROGRAM_ID, raydium_cpmm::parser::RAYDIUM_CPMM_PROGRAM_ID,
|
||||
BonkEventParser, RaydiumCpmmEventParser,
|
||||
};
|
||||
|
||||
use super::{
|
||||
@@ -17,6 +19,7 @@ pub enum Protocol {
|
||||
PumpSwap,
|
||||
PumpFun,
|
||||
Bonk,
|
||||
RaydiumCpmm,
|
||||
}
|
||||
|
||||
impl Protocol {
|
||||
@@ -25,6 +28,7 @@ impl Protocol {
|
||||
Protocol::PumpSwap => vec![PUMPSWAP_PROGRAM_ID],
|
||||
Protocol::PumpFun => vec![PUMPFUN_PROGRAM_ID],
|
||||
Protocol::Bonk => vec![BONK_PROGRAM_ID],
|
||||
Protocol::RaydiumCpmm => vec![RAYDIUM_CPMM_PROGRAM_ID],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,6 +39,7 @@ impl std::fmt::Display for Protocol {
|
||||
Protocol::PumpSwap => write!(f, "PumpSwap"),
|
||||
Protocol::PumpFun => write!(f, "PumpFun"),
|
||||
Protocol::Bonk => write!(f, "Bonk"),
|
||||
Protocol::RaydiumCpmm => write!(f, "RaydiumCpmm"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -47,6 +52,7 @@ impl std::str::FromStr for Protocol {
|
||||
"pumpswap" => Ok(Protocol::PumpSwap),
|
||||
"pumpfun" => Ok(Protocol::PumpFun),
|
||||
"bonk" => Ok(Protocol::Bonk),
|
||||
"raydiumcpmm" => Ok(Protocol::RaydiumCpmm),
|
||||
_ => Err(anyhow!("Unsupported protocol: {}", s)),
|
||||
}
|
||||
}
|
||||
@@ -62,6 +68,7 @@ impl EventParserFactory {
|
||||
Protocol::PumpSwap => Arc::new(PumpSwapEventParser::new()),
|
||||
Protocol::PumpFun => Arc::new(PumpFunEventParser::new()),
|
||||
Protocol::Bonk => Arc::new(BonkEventParser::new()),
|
||||
Protocol::RaydiumCpmm => Arc::new(RaydiumCpmmEventParser::new()),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
pub mod pumpfun;
|
||||
pub mod pumpswap;
|
||||
pub mod bonk;
|
||||
pub mod raydium_cpmm;
|
||||
|
||||
pub use pumpfun::PumpFunEventParser;
|
||||
pub use pumpswap::PumpSwapEventParser;
|
||||
pub use bonk::BonkEventParser;
|
||||
pub use bonk::BonkEventParser;
|
||||
pub use raydium_cpmm::RaydiumCpmmEventParser;
|
||||
@@ -0,0 +1,35 @@
|
||||
use crate::impl_unified_event;
|
||||
use crate::streaming::event_parser::common::EventMetadata;
|
||||
use borsh::BorshDeserialize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
|
||||
/// 交易
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)]
|
||||
pub struct RaydiumCpmmSwapEvent {
|
||||
pub metadata: EventMetadata,
|
||||
pub amount_in: u64,
|
||||
pub minimum_amount_out: u64,
|
||||
pub max_amount_in: u64,
|
||||
pub amount_out: u64,
|
||||
pub payer: Pubkey,
|
||||
pub authority: Pubkey,
|
||||
pub amm_config: Pubkey,
|
||||
pub pool_state: Pubkey,
|
||||
pub input_token_account: Pubkey,
|
||||
pub output_token_account: Pubkey,
|
||||
pub input_vault: Pubkey,
|
||||
pub output_vault: Pubkey,
|
||||
pub input_token_mint: Pubkey,
|
||||
pub output_token_mint: Pubkey,
|
||||
pub observation_state: Pubkey,
|
||||
}
|
||||
|
||||
impl_unified_event!(RaydiumCpmmSwapEvent,);
|
||||
|
||||
/// 事件鉴别器常量
|
||||
pub mod discriminators {
|
||||
// 指令鉴别器
|
||||
pub const SWAP_BASE_IN: &[u8] = &[143, 190, 90, 218, 196, 30, 51, 222];
|
||||
pub const SWAP_BASE_OUT: &[u8] = &[55, 217, 98, 86, 163, 74, 180, 173];
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
pub mod events;
|
||||
pub mod parser;
|
||||
|
||||
pub use events::*;
|
||||
pub use parser::RaydiumCpmmEventParser;
|
||||
+159
@@ -0,0 +1,159 @@
|
||||
use solana_sdk::{instruction::CompiledInstruction, pubkey::Pubkey};
|
||||
use solana_transaction_status::UiCompiledInstruction;
|
||||
|
||||
use crate::streaming::event_parser::{
|
||||
common::{read_u64_le, EventMetadata, EventType, ProtocolType},
|
||||
core::traits::{EventParser, GenericEventParseConfig, GenericEventParser, UnifiedEvent},
|
||||
protocols::raydium_cpmm::{discriminators, RaydiumCpmmSwapEvent},
|
||||
};
|
||||
|
||||
/// Raydium CPMM程序ID
|
||||
pub const RAYDIUM_CPMM_PROGRAM_ID: Pubkey =
|
||||
solana_sdk::pubkey!("CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C");
|
||||
|
||||
/// Raydium CPMM事件解析器
|
||||
pub struct RaydiumCpmmEventParser {
|
||||
inner: GenericEventParser,
|
||||
}
|
||||
|
||||
impl RaydiumCpmmEventParser {
|
||||
pub fn new() -> Self {
|
||||
// 配置所有事件类型
|
||||
let configs = vec![
|
||||
GenericEventParseConfig {
|
||||
inner_instruction_discriminator: "",
|
||||
instruction_discriminator: discriminators::SWAP_BASE_IN,
|
||||
event_type: EventType::RaydiumCpmmSwapBaseInput,
|
||||
inner_instruction_parser: Self::parse_trade_inner_instruction,
|
||||
instruction_parser: Self::parse_swap_base_input_instruction,
|
||||
},
|
||||
GenericEventParseConfig {
|
||||
inner_instruction_discriminator: "",
|
||||
instruction_discriminator: discriminators::SWAP_BASE_OUT,
|
||||
event_type: EventType::RaydiumCpmmSwapBaseOutput,
|
||||
inner_instruction_parser: Self::parse_trade_inner_instruction,
|
||||
instruction_parser: Self::parse_swap_base_output_instruction,
|
||||
},
|
||||
];
|
||||
|
||||
let inner =
|
||||
GenericEventParser::new(RAYDIUM_CPMM_PROGRAM_ID, ProtocolType::RaydiumCpmm, configs);
|
||||
|
||||
Self { inner }
|
||||
}
|
||||
|
||||
/// 解析交易事件
|
||||
fn parse_trade_inner_instruction(
|
||||
_data: &[u8],
|
||||
_metadata: EventMetadata,
|
||||
) -> Option<Box<dyn UnifiedEvent>> {
|
||||
None
|
||||
}
|
||||
|
||||
/// 解析买入指令事件
|
||||
fn parse_swap_base_input_instruction(
|
||||
data: &[u8],
|
||||
accounts: &[Pubkey],
|
||||
metadata: EventMetadata,
|
||||
) -> Option<Box<dyn UnifiedEvent>> {
|
||||
if data.len() < 16 || accounts.len() < 13 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let amount_in = read_u64_le(data, 0)?;
|
||||
let minimum_amount_out = read_u64_le(data, 8)?;
|
||||
|
||||
let mut metadata = metadata;
|
||||
metadata.set_id(format!(
|
||||
"{}-{}-{}-{}",
|
||||
metadata.signature, accounts[3], accounts[10], accounts[11]
|
||||
));
|
||||
|
||||
Some(Box::new(RaydiumCpmmSwapEvent {
|
||||
metadata,
|
||||
amount_in,
|
||||
minimum_amount_out,
|
||||
payer: accounts[0],
|
||||
authority: accounts[1],
|
||||
amm_config: accounts[2],
|
||||
pool_state: accounts[3],
|
||||
input_token_account: accounts[4],
|
||||
output_token_account: accounts[5],
|
||||
input_vault: accounts[6],
|
||||
output_vault: accounts[7],
|
||||
input_token_mint: accounts[10],
|
||||
output_token_mint: accounts[11],
|
||||
observation_state: accounts[12],
|
||||
..Default::default()
|
||||
}))
|
||||
}
|
||||
|
||||
fn parse_swap_base_output_instruction(
|
||||
data: &[u8],
|
||||
accounts: &[Pubkey],
|
||||
metadata: EventMetadata,
|
||||
) -> Option<Box<dyn UnifiedEvent>> {
|
||||
if data.len() < 16 || accounts.len() < 13 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let max_amount_in = read_u64_le(data, 0)?;
|
||||
let amount_out = read_u64_le(data, 8)?;
|
||||
|
||||
let mut metadata = metadata;
|
||||
metadata.set_id(format!(
|
||||
"{}-{}-{}-{}",
|
||||
metadata.signature, accounts[3], accounts[10], accounts[11]
|
||||
));
|
||||
|
||||
Some(Box::new(RaydiumCpmmSwapEvent {
|
||||
metadata,
|
||||
max_amount_in,
|
||||
amount_out,
|
||||
payer: accounts[0],
|
||||
authority: accounts[1],
|
||||
amm_config: accounts[2],
|
||||
pool_state: accounts[3],
|
||||
input_token_account: accounts[4],
|
||||
output_token_account: accounts[5],
|
||||
input_vault: accounts[6],
|
||||
output_vault: accounts[7],
|
||||
input_token_mint: accounts[10],
|
||||
output_token_mint: accounts[11],
|
||||
observation_state: accounts[12],
|
||||
..Default::default()
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl EventParser for RaydiumCpmmEventParser {
|
||||
fn parse_events_from_inner_instruction(
|
||||
&self,
|
||||
inner_instruction: &UiCompiledInstruction,
|
||||
signature: &str,
|
||||
slot: u64,
|
||||
) -> Vec<Box<dyn UnifiedEvent>> {
|
||||
self.inner
|
||||
.parse_events_from_inner_instruction(inner_instruction, signature, slot)
|
||||
}
|
||||
|
||||
fn parse_events_from_instruction(
|
||||
&self,
|
||||
instruction: &CompiledInstruction,
|
||||
accounts: &[Pubkey],
|
||||
signature: &str,
|
||||
slot: u64,
|
||||
) -> Vec<Box<dyn UnifiedEvent>> {
|
||||
self.inner
|
||||
.parse_events_from_instruction(instruction, accounts, signature, slot)
|
||||
}
|
||||
|
||||
fn should_handle(&self, program_id: &Pubkey) -> bool {
|
||||
self.inner.should_handle(program_id)
|
||||
}
|
||||
|
||||
fn supported_program_ids(&self) -> Vec<Pubkey> {
|
||||
self.inner.supported_program_ids()
|
||||
}
|
||||
}
|
||||
@@ -215,6 +215,34 @@ impl ProtocolParams for BonkParams {
|
||||
}
|
||||
}
|
||||
|
||||
/// RaydiumCpmm协议特定参数
|
||||
#[derive(Clone)]
|
||||
pub struct RaydiumCpmmParams {
|
||||
pub pool_state: Option<Pubkey>,
|
||||
pub minimum_amount_out: Option<u64>,
|
||||
pub auto_handle_wsol: bool,
|
||||
}
|
||||
|
||||
impl RaydiumCpmmParams {
|
||||
pub fn default() -> Self {
|
||||
Self {
|
||||
pool_state: None,
|
||||
minimum_amount_out: None,
|
||||
auto_handle_wsol: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ProtocolParams for RaydiumCpmmParams {
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn clone_box(&self) -> Box<dyn ProtocolParams> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl BuyParams {
|
||||
/// 转换为BuyWithTipParams
|
||||
pub fn with_tip(self, swqos_clients: Vec<Arc<SwqosClient>>) -> BuyWithTipParams {
|
||||
|
||||
+14
-6
@@ -1,18 +1,20 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::instruction::{bonk::BonkInstructionBuilder, pumpfun::PumpFunInstructionBuilder, pumpswap::PumpSwapInstructionBuilder};
|
||||
|
||||
use super::{
|
||||
core::{executor::GenericTradeExecutor, traits::TradeExecutor},
|
||||
use crate::instruction::{
|
||||
bonk::BonkInstructionBuilder, pumpfun::PumpFunInstructionBuilder,
|
||||
pumpswap::PumpSwapInstructionBuilder, raydium_cpmm::RaydiumCpmmInstructionBuilder,
|
||||
};
|
||||
|
||||
use super::core::{executor::GenericTradeExecutor, traits::TradeExecutor};
|
||||
|
||||
/// 支持的交易协议
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum DexType {
|
||||
PumpFun,
|
||||
PumpSwap,
|
||||
Bonk,
|
||||
RaydiumCpmm,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for DexType {
|
||||
@@ -21,6 +23,7 @@ impl std::fmt::Display for DexType {
|
||||
DexType::PumpFun => write!(f, "PumpFun"),
|
||||
DexType::PumpSwap => write!(f, "PumpSwap"),
|
||||
DexType::Bonk => write!(f, "Bonk"),
|
||||
DexType::RaydiumCpmm => write!(f, "RaydiumCpmm"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,6 +36,7 @@ impl std::str::FromStr for DexType {
|
||||
"pumpfun" => Ok(DexType::PumpFun),
|
||||
"pumpswap" => Ok(DexType::PumpSwap),
|
||||
"bonk" => Ok(DexType::Bonk),
|
||||
"raydiumcpmm" => Ok(DexType::RaydiumCpmm),
|
||||
_ => Err(anyhow!("Unsupported protocol: {}", s)),
|
||||
}
|
||||
}
|
||||
@@ -55,9 +59,13 @@ impl TradeFactory {
|
||||
}
|
||||
DexType::Bonk => {
|
||||
let instruction_builder = Arc::new(BonkInstructionBuilder);
|
||||
Arc::new(GenericTradeExecutor::new(instruction_builder, "Bonk"))
|
||||
}
|
||||
DexType::RaydiumCpmm => {
|
||||
let instruction_builder = Arc::new(RaydiumCpmmInstructionBuilder);
|
||||
Arc::new(GenericTradeExecutor::new(
|
||||
instruction_builder,
|
||||
"Bonk",
|
||||
"RaydiumCpmm",
|
||||
))
|
||||
}
|
||||
}
|
||||
@@ -65,7 +73,7 @@ impl TradeFactory {
|
||||
|
||||
/// 获取所有支持的协议
|
||||
pub fn supported_dex_types() -> Vec<DexType> {
|
||||
vec![DexType::PumpFun, DexType::PumpSwap, DexType::Bonk]
|
||||
vec![DexType::PumpFun, DexType::PumpSwap, DexType::Bonk, DexType::RaydiumCpmm]
|
||||
}
|
||||
|
||||
/// 检查协议是否支持
|
||||
|
||||
@@ -4,6 +4,7 @@ pub mod factory;
|
||||
pub mod bonk;
|
||||
pub mod pumpfun;
|
||||
pub mod pumpswap;
|
||||
pub mod raydium_cpmm;
|
||||
|
||||
pub use core::params::{BuyParams, BuyWithTipParams, SellParams, SellWithTipParams};
|
||||
pub use core::traits::{InstructionBuilder, TradeExecutor};
|
||||
|
||||
Executable
+220
@@ -0,0 +1,220 @@
|
||||
use crate::{
|
||||
common::SolanaRpcClient,
|
||||
constants::{self, raydium_cpmm::accounts::WSOL_TOKEN_ACCOUNT},
|
||||
trading::raydium_cpmm::pool::Pool,
|
||||
};
|
||||
use anyhow::anyhow;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
|
||||
pub fn get_pool_pda(amm_config: &Pubkey, mint1: &Pubkey, mint2: &Pubkey) -> Option<Pubkey> {
|
||||
let seeds: &[&[u8]; 4] = &[
|
||||
constants::raydium_cpmm::seeds::POOL_SEED,
|
||||
amm_config.as_ref(),
|
||||
mint1.as_ref(),
|
||||
mint2.as_ref(),
|
||||
];
|
||||
let program_id: &Pubkey = &constants::raydium_cpmm::accounts::RAYDIUM_CPMM;
|
||||
let pda: Option<(Pubkey, u8)> = Pubkey::try_find_program_address(seeds, program_id);
|
||||
pda.map(|pubkey| pubkey.0)
|
||||
}
|
||||
|
||||
pub fn get_vault_pda(pool_state: &Pubkey, mint: &Pubkey) -> Option<Pubkey> {
|
||||
let seeds: &[&[u8]; 3] = &[
|
||||
constants::raydium_cpmm::seeds::POOL_VAULT_SEED,
|
||||
pool_state.as_ref(),
|
||||
mint.as_ref(),
|
||||
];
|
||||
let program_id: &Pubkey = &constants::raydium_cpmm::accounts::RAYDIUM_CPMM;
|
||||
let pda: Option<(Pubkey, u8)> = Pubkey::try_find_program_address(seeds, program_id);
|
||||
pda.map(|pubkey| pubkey.0)
|
||||
}
|
||||
|
||||
pub fn get_observation_state_pda(pool_state: &Pubkey) -> Option<Pubkey> {
|
||||
let seeds: &[&[u8]; 2] = &[
|
||||
constants::raydium_cpmm::seeds::OBSERVATION_STATE_SEED,
|
||||
pool_state.as_ref(),
|
||||
];
|
||||
let program_id: &Pubkey = &constants::raydium_cpmm::accounts::RAYDIUM_CPMM;
|
||||
let pda: Option<(Pubkey, u8)> = Pubkey::try_find_program_address(seeds, program_id);
|
||||
pda.map(|pubkey| pubkey.0)
|
||||
}
|
||||
|
||||
pub async fn get_buy_token_amount(
|
||||
rpc: &SolanaRpcClient,
|
||||
pool_state: &Pubkey,
|
||||
sol_amount: u64,
|
||||
) -> Result<u64, anyhow::Error> {
|
||||
let pool = Pool::fetch(rpc, pool_state).await?;
|
||||
let is_token0_input = if pool.token0_mint == WSOL_TOKEN_ACCOUNT {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
};
|
||||
let (token0_balance, token1_balance) =
|
||||
get_pool_token_balances(rpc, pool_state, &pool.token0_mint, &pool.token1_mint).await?;
|
||||
|
||||
// 使用恒定乘积公式计算
|
||||
|
||||
let (reserve_in, reserve_out) = if is_token0_input {
|
||||
(token0_balance, token1_balance)
|
||||
} else {
|
||||
(token1_balance, token0_balance)
|
||||
};
|
||||
|
||||
if reserve_in == 0 || reserve_out == 0 {
|
||||
return Err(anyhow!("池子储备金为零,无法进行交换"));
|
||||
}
|
||||
|
||||
// 使用 u128 防止溢出
|
||||
let amount_in_128 = sol_amount as u128;
|
||||
let reserve_in_128 = reserve_in as u128;
|
||||
let reserve_out_128 = reserve_out as u128;
|
||||
|
||||
// 恒定乘积公式: amount_out = (amount_in * reserve_out) / (reserve_in + amount_in)
|
||||
let numerator = amount_in_128 * reserve_out_128;
|
||||
let denominator = reserve_in_128 + amount_in_128;
|
||||
|
||||
if denominator == 0 {
|
||||
return Err(anyhow!("分母为零,计算错误"));
|
||||
}
|
||||
|
||||
let amount_out = numerator / denominator;
|
||||
|
||||
// 检查是否超出储备金
|
||||
if amount_out >= reserve_out_128 {
|
||||
return Err(anyhow!("输出数量超过池子储备金"));
|
||||
}
|
||||
|
||||
Ok(amount_out as u64)
|
||||
}
|
||||
|
||||
pub async fn get_sell_sol_amount(
|
||||
rpc: &SolanaRpcClient,
|
||||
pool_state: &Pubkey,
|
||||
token_amount: u64,
|
||||
) -> Result<u64, anyhow::Error> {
|
||||
let pool = Pool::fetch(rpc, pool_state).await?;
|
||||
let is_token0_sol = if pool.token0_mint == WSOL_TOKEN_ACCOUNT {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
};
|
||||
let (token0_balance, token1_balance) =
|
||||
get_pool_token_balances(rpc, pool_state, &pool.token0_mint, &pool.token1_mint).await?;
|
||||
|
||||
let (reserve_in, reserve_out) = if is_token0_sol {
|
||||
(token1_balance, token0_balance)
|
||||
} else {
|
||||
(token0_balance, token1_balance)
|
||||
};
|
||||
|
||||
if reserve_in == 0 || reserve_out == 0 {
|
||||
return Err(anyhow!("池子储备金为零,无法进行交换"));
|
||||
}
|
||||
|
||||
// 使用 u128 防止溢出
|
||||
let amount_in_128 = token_amount as u128;
|
||||
let reserve_in_128 = reserve_in as u128;
|
||||
let reserve_out_128 = reserve_out as u128;
|
||||
|
||||
// 恒定乘积公式: amount_out = (amount_in * reserve_out) / (reserve_in + amount_in)
|
||||
let numerator = amount_in_128 * reserve_out_128;
|
||||
let denominator = reserve_in_128 + amount_in_128;
|
||||
|
||||
if denominator == 0 {
|
||||
return Err(anyhow!("分母为零,计算错误"));
|
||||
}
|
||||
|
||||
let amount_out = numerator / denominator;
|
||||
|
||||
// 检查是否超出储备金
|
||||
if amount_out >= reserve_out_128 {
|
||||
return Err(anyhow!("输出数量超过池子储备金"));
|
||||
}
|
||||
|
||||
Ok(amount_out as u64)
|
||||
}
|
||||
|
||||
/// 获取池子中两个代币的余额
|
||||
///
|
||||
/// # 返回值
|
||||
/// 返回 token0_balance, token1_balance
|
||||
pub async fn get_pool_token_balances(
|
||||
rpc: &SolanaRpcClient,
|
||||
pool_state: &Pubkey,
|
||||
token0_mint: &Pubkey,
|
||||
token1_mint: &Pubkey,
|
||||
) -> Result<(u64, u64), anyhow::Error> {
|
||||
let token0_vault = get_vault_pda(pool_state, token0_mint).unwrap();
|
||||
let token0_balance = rpc.get_token_account_balance(&token0_vault).await?;
|
||||
let token1_vault = get_vault_pda(pool_state, token1_mint).unwrap();
|
||||
let token1_balance = rpc.get_token_account_balance(&token1_vault).await?;
|
||||
|
||||
// 解析余额字符串为 u64
|
||||
let token0_amount = token0_balance
|
||||
.amount
|
||||
.parse::<u64>()
|
||||
.map_err(|e| anyhow!("解析 token0 余额失败: {}", e))?;
|
||||
|
||||
let token1_amount = token1_balance
|
||||
.amount
|
||||
.parse::<u64>()
|
||||
.map_err(|e| anyhow!("解析 token1 余额失败: {}", e))?;
|
||||
|
||||
Ok((token0_amount, token1_amount))
|
||||
}
|
||||
|
||||
/// 计算代币价格 (token1/token0)
|
||||
///
|
||||
/// # 返回值
|
||||
/// 返回 token1 相对于 token0 的价格
|
||||
pub async fn calculate_price(
|
||||
token0_amount: u64,
|
||||
token1_amount: u64,
|
||||
mint0_decimals: u8,
|
||||
mint1_decimals: u8,
|
||||
) -> Result<f64, anyhow::Error> {
|
||||
if token0_amount == 0 {
|
||||
return Err(anyhow!("Token0 余额为零,无法计算价格"));
|
||||
}
|
||||
// 考虑小数位精度
|
||||
let token0_adjusted = token0_amount as f64 / 10_f64.powi(mint0_decimals as i32);
|
||||
let token1_adjusted = token1_amount as f64 / 10_f64.powi(mint1_decimals as i32);
|
||||
let price = token1_adjusted / token0_adjusted;
|
||||
Ok(price)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use solana_sdk::pubkey;
|
||||
|
||||
#[test]
|
||||
fn test_get_pool_pda() {
|
||||
// 测试get_pool_pda函数
|
||||
let amm_config = constants::raydium_cpmm::accounts::AMM_CONFIG;
|
||||
let input_mint = pubkey!("So11111111111111111111111111111111111111112"); // WSOL
|
||||
let output_mint = pubkey!("BnwbwoqPm5ZNx7YTJ8g9jR2qCpYeHBC7xxpU8zEtbonk"); // USDC
|
||||
let pool_state = pubkey!("E9rRRpcdsKAseeLFbwC1Ewxd3aYG27meqwTTrMfCTbSG");
|
||||
let result = get_pool_pda(&amm_config, &input_mint, &output_mint);
|
||||
assert_eq!(result, Some(pool_state));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_vault_pda() {
|
||||
// 测试get_vault_pda函数
|
||||
let pool_state = pubkey!("HBMkgQvt4NAFx6XzNav23bNcv6K3oiC5UfY3JsE22scY");
|
||||
let mint = pubkey!("DeESECsL3cLXno1LFquss98kNQSno1xpQC2ERCqSbonk"); // WSOL
|
||||
let vault_pda = pubkey!("7rkgNG3A8z636DuzhchKeqAJTaH3H5ZFWmBQeStydovA");
|
||||
let result = get_vault_pda(&pool_state, &mint);
|
||||
assert_eq!(result, Some(vault_pda));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_observation_state_pda() {
|
||||
let pool_state = pubkey!("HBMkgQvt4NAFx6XzNav23bNcv6K3oiC5UfY3JsE22scY");
|
||||
let observation_state_pda = pubkey!("Gq8u9N18ASjq3AK2gCk6RtGSNyjXZf9EZDb6vTtB9JRs");
|
||||
let result = get_observation_state_pda(&pool_state);
|
||||
assert_eq!(result, Some(observation_state_pda));
|
||||
}
|
||||
}
|
||||
Executable
+2
@@ -0,0 +1,2 @@
|
||||
pub mod common;
|
||||
pub mod pool;
|
||||
Executable
+51
@@ -0,0 +1,51 @@
|
||||
use crate::{common::SolanaRpcClient, constants::raydium_cpmm::accounts};
|
||||
use anyhow::anyhow;
|
||||
use borsh::BorshDeserialize;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
|
||||
#[derive(Debug, Clone, BorshDeserialize)]
|
||||
pub struct Pool {
|
||||
pub amm_config: Pubkey,
|
||||
pub pool_creator: Pubkey,
|
||||
pub token0_vault: Pubkey,
|
||||
pub token1_vault: Pubkey,
|
||||
pub lp_mint: Pubkey,
|
||||
pub token0_mint: Pubkey,
|
||||
pub token1_mint: Pubkey,
|
||||
pub token0_program: Pubkey,
|
||||
pub token1_program: Pubkey,
|
||||
pub observation_key: Pubkey,
|
||||
pub auth_bump: u8,
|
||||
pub status: u8,
|
||||
pub lp_mint_decimals: u8,
|
||||
pub mint0_decimals: u8,
|
||||
pub mint1_decimals: u8,
|
||||
pub lp_supply: u64,
|
||||
pub protocol_fees_token0: u64,
|
||||
pub protocol_fees_token1: u64,
|
||||
pub fund_fees_token0: u64,
|
||||
pub fund_fees_token1: u64,
|
||||
pub open_time: u64,
|
||||
pub recent_epoch: u64,
|
||||
pub padding: [u64; 31],
|
||||
}
|
||||
|
||||
impl Pool {
|
||||
pub fn from_bytes(data: &[u8]) -> Result<Self, anyhow::Error> {
|
||||
let pool = Pool::try_from_slice(&data[8..])?;
|
||||
Ok(pool)
|
||||
}
|
||||
|
||||
pub async fn fetch(
|
||||
rpc: &SolanaRpcClient,
|
||||
pool_address: &Pubkey,
|
||||
) -> Result<Self, anyhow::Error> {
|
||||
let account = rpc.get_account(pool_address).await?;
|
||||
|
||||
if account.owner != accounts::RAYDIUM_CPMM {
|
||||
return Err(anyhow!("Account is not owned by Raydium Cpmm program"));
|
||||
}
|
||||
|
||||
Self::from_bytes(&account.data)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user