refactor: Major SDK architecture refactoring and API consolidation

- Consolidate separate buy/sell modules into unified trading interface
- Remove protocol-specific buy/sell files (bonk, pumpfun, pumpswap)
- Add new trading constants and utility functions
- Simplify API with unified buy/sell methods supporting multiple protocols
- Enhance documentation with comprehensive examples and usage guides
- Add balance checking and token account management utilities
- Improve code organization and maintainability
This commit is contained in:
ysq
2025-07-10 18:14:21 +08:00
parent 57c2848a57
commit b891b2bc27
41 changed files with 1297 additions and 2744 deletions
+2 -3
View File
@@ -2,11 +2,11 @@
name = "sol-trade-sdk" name = "sol-trade-sdk"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2021"
authors = ["William <byteblock6@gmail.com>"] authors = ["William <byteblock6@gmail.com>", "sgxiang <sgxiang@gmail.com>", "wei <1415121722@qq.com>"]
repository = "https://github.com/0xfnzero/sol-trade-sdk" repository = "https://github.com/0xfnzero/sol-trade-sdk"
description = "Rust SDK to interact with the dex trade Solana program." description = "Rust SDK to interact with the dex trade Solana program."
license = "MIT" license = "MIT"
keywords = ["solana", "memecoins", "pumpfun", "pumpswap", "raydium"] keywords = ["solana", "memecoins", "pumpfun", "pumpswap", "raydium", "bonk", "shreds", "yellowstone"]
readme = "README.md" readme = "README.md"
[lib] [lib]
@@ -65,7 +65,6 @@ lazy_static = "1.5.0"
once_cell = "1.20.3" once_cell = "1.20.3"
prost = "0.13.5" prost = "0.13.5"
prost-types = "0.13.5" prost-types = "0.13.5"
arrform = { git = "https://github.com/raydium-io/arrform" }
num_enum = "0.7.3" num_enum = "0.7.3"
num-derive = "0.4.2" num-derive = "0.4.2"
num-traits = "0.2.19" num-traits = "0.2.19"
+174 -234
View File
@@ -12,7 +12,7 @@ A comprehensive Rust SDK for seamless interaction with Solana DEX trading progra
6. **ShredStream Support**: Subscribe to program events using ShredStream 6. **ShredStream Support**: Subscribe to program events using ShredStream
7. **Multiple MEV Protection**: Support for Jito, Nextblock, ZeroSlot, Temporal, Bloxroute, and other services 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 8. **Concurrent Trading**: Send transactions using multiple MEV services simultaneously; the fastest succeeds while others fail
9. **Unified Trading Interface**: Use unified parameter structures for trading operations 9. **Unified Trading Interface**: Use unified trading protocol enums for trading operations
## Installation ## Installation
@@ -38,18 +38,20 @@ sol-trade-sdk = { path = "./sol-trade-sdk", version = "0.1.0" }
```rust ```rust
use sol_trade_sdk::{ use sol_trade_sdk::{
event_parser::{ streaming::{
protocols::{ event_parser::{
pumpfun::{PumpFunCreateTokenEvent, PumpFunTradeEvent}, protocols::{
pumpswap::{ bonk::{BonkPoolCreateEvent, BonkTradeEvent},
PumpSwapBuyEvent, PumpSwapCreatePoolEvent, PumpSwapDepositEvent, pumpfun::{PumpFunCreateTokenEvent, PumpFunTradeEvent},
PumpSwapSellEvent, PumpSwapWithdrawEvent, pumpswap::{
PumpSwapBuyEvent, PumpSwapCreatePoolEvent, PumpSwapDepositEvent,
PumpSwapSellEvent, PumpSwapWithdrawEvent,
},
}, },
bonk::{BonkPoolCreateEvent, BonkTradeEvent}, Protocol, UnifiedEvent,
}, },
Protocol, UnifiedEvent, YellowstoneGrpc,
}, },
grpc::YellowstoneGrpc,
match_event, match_event,
}; };
@@ -97,11 +99,7 @@ async fn test_grpc() -> Result<(), Box<dyn std::error::Error>> {
// Subscribe to events from multiple protocols // Subscribe to events from multiple protocols
println!("Starting to listen for events, press Ctrl+C to stop..."); println!("Starting to listen for events, press Ctrl+C to stop...");
let protocols = vec![ let protocols = vec![Protocol::PumpFun, Protocol::PumpSwap, Protocol::Bonk];
Protocol::PumpFun,
Protocol::PumpSwap,
Protocol::Bonk,
];
grpc.subscribe_events(protocols, None, None, None, callback) grpc.subscribe_events(protocols, None, None, None, callback)
.await?; .await?;
@@ -112,7 +110,7 @@ async fn test_grpc() -> Result<(), Box<dyn std::error::Error>> {
#### 1.2 Subscribe to Events Using ShredStream #### 1.2 Subscribe to Events Using ShredStream
```rust ```rust
use sol_trade_sdk::grpc::ShredStreamGrpc; use sol_trade_sdk::streaming::ShredStreamGrpc;
async fn test_shreds() -> Result<(), Box<dyn std::error::Error>> { async fn test_shreds() -> Result<(), Box<dyn std::error::Error>> {
// Subscribe to events using ShredStream client // Subscribe to events using ShredStream client
@@ -155,11 +153,7 @@ async fn test_shreds() -> Result<(), Box<dyn std::error::Error>> {
// Subscribe to events // Subscribe to events
println!("Starting to listen for events, press Ctrl+C to stop..."); println!("Starting to listen for events, press Ctrl+C to stop...");
let protocols = vec![ let protocols = vec![Protocol::PumpFun, Protocol::PumpSwap, Protocol::Bonk];
Protocol::PumpFun,
Protocol::PumpSwap,
Protocol::Bonk,
];
shred_stream shred_stream
.shredstream_subscribe(protocols, None, callback) .shredstream_subscribe(protocols, None, callback)
.await?; .await?;
@@ -177,127 +171,120 @@ use sol_trade_sdk::{
swqos::{SwqosConfig, SwqosRegion}, swqos::{SwqosConfig, SwqosRegion},
SolanaTrade SolanaTrade
}; };
use solana_client::rpc_client::RpcClient;
use solana_sdk::{commitment_config::CommitmentConfig, pubkey::Pubkey, signature::Keypair}; use solana_sdk::{commitment_config::CommitmentConfig, pubkey::Pubkey, signature::Keypair};
// Create trader account /// Example of creating a SolanaTrade client
let payer = Keypair::new(); async fn test_create_solana_trade_client() -> AnyResult<SolanaTrade> {
let rpc_url = "https://mainnet.helius-rpc.com/?api-key=xxxxxx".to_string(); println!("Creating SolanaTrade client...");
// Configure multiple MEV services to support concurrent trading let payer = Keypair::new();
let swqos_configs = vec![ let rpc_url = "https://mainnet.helius-rpc.com/?api-key=xxxxxx".to_string();
SwqosConfig::Jito(SwqosRegion::Frankfurt),
SwqosConfig::NextBlock("your api_token".to_string(), SwqosRegion::Frankfurt),
SwqosConfig::Bloxroute("your api_token".to_string(), SwqosRegion::Frankfurt),
SwqosConfig::ZeroSlot("your api_token".to_string(), SwqosRegion::Frankfurt),
SwqosConfig::Temporal("your api_token".to_string(), SwqosRegion::Frankfurt),
SwqosConfig::Default(rpc_url.clone()),
];
// Define trading configuration // Configure various SWQOS services
let trade_config = TradeConfig { let swqos_configs = vec![
rpc_url: rpc_url.clone(), SwqosConfig::Jito(SwqosRegion::Frankfurt),
commitment: CommitmentConfig::confirmed(), SwqosConfig::NextBlock("your api_token".to_string(), SwqosRegion::Frankfurt),
priority_fee: PriorityFee::default(), SwqosConfig::Bloxroute("your api_token".to_string(), SwqosRegion::Frankfurt),
swqos_configs, SwqosConfig::ZeroSlot("your api_token".to_string(), SwqosRegion::Frankfurt),
lookup_table_key: None, SwqosConfig::Temporal("your api_token".to_string(), SwqosRegion::Frankfurt),
}; SwqosConfig::Default(rpc_url.clone()),
];
// Create SolanaTrade instance // Define trading configuration
let solana_trade_client = SolanaTrade::new(Arc::new(payer), trade_config).await; let trade_config = TradeConfig {
rpc_url: rpc_url.clone(),
commitment: CommitmentConfig::confirmed(),
priority_fee: PriorityFee::default(),
swqos_configs,
lookup_table_key: None,
};
let solana_trade_client = SolanaTrade::new(Arc::new(payer), trade_config).await;
println!("SolanaTrade client created successfully!");
Ok(solana_trade_client)
}
``` ```
### 3. PumpFun Trading Operations ### 3. PumpFun Trading Operations
```rust ```rust
use sol_trade_sdk::{ use sol_trade_sdk::{
accounts::BondingCurveAccount, common::bonding_curve::BondingCurveAccount,
constants::{pumpfun::global_constants::TOKEN_TOTAL_SUPPLY, trade_type}, constants::pumpfun::global_constants::TOKEN_TOTAL_SUPPLY,
pumpfun::common::get_bonding_curve_account_v2,
trading::{ trading::{
core::params::{PumpFunParams, PumpFunSellParams}, core::params::PumpFunParams,
BuyParams, SellParams, factory::TradingProtocol,
pumpfun::common::get_bonding_curve_account_v2,
}, },
}; };
async fn test_pumpfun() -> AnyResult<()> { async fn test_pumpfun() -> AnyResult<()> {
// Basic parameter setup println!("Testing PumpFun trading...");
let creator = Pubkey::from_str("xxxxxx")?; // Developer account
let mint_pubkey = Pubkey::from_str("xxxxxx")?; // Token address let solana_trade_client = test_create_solana_trade_client().await?;
let creator = Pubkey::from_str("xxxxxx")?; // dev account
let buy_sol_cost = 100_000; // 0.0001 SOL let buy_sol_cost = 100_000; // 0.0001 SOL
let slippage_basis_points = Some(100); let slippage_basis_points = Some(100);
let rpc = RpcClient::new(rpc_url); let recent_blockhash = solana_trade_client.rpc.get_latest_blockhash().await?;
let recent_blockhash = rpc.get_latest_blockhash().unwrap(); let mint_pubkey = Pubkey::from_str("xxxxxx")?; // token mint
println!("Buying tokens from PumpFun..."); println!("Buying tokens from PumpFun...");
// get bonding curve
// Get bonding curve information
let (bonding_curve, bonding_curve_pda) = let (bonding_curve, bonding_curve_pda) =
get_bonding_curve_account_v2(&solana_trade_client.rpc, &mint_pubkey).await?; get_bonding_curve_account_v2(&solana_trade_client.rpc, &mint_pubkey).await?;
let virtual_token_reserves = bonding_curve.virtual_token_reserves;
let virtual_sol_reserves = bonding_curve.virtual_sol_reserves;
let real_token_reserves = bonding_curve.real_token_reserves;
let real_sol_reserves = bonding_curve.real_sol_reserves;
let bonding_curve = BondingCurveAccount { let bonding_curve = BondingCurveAccount {
discriminator: bonding_curve.discriminator, discriminator: bonding_curve.discriminator,
account: bonding_curve_pda, account: bonding_curve_pda,
virtual_token_reserves: bonding_curve.virtual_token_reserves, virtual_token_reserves: virtual_token_reserves,
virtual_sol_reserves: bonding_curve.virtual_sol_reserves, virtual_sol_reserves: virtual_sol_reserves,
real_token_reserves: bonding_curve.real_token_reserves, real_token_reserves: real_token_reserves,
real_sol_reserves: bonding_curve.real_sol_reserves, real_sol_reserves: real_sol_reserves,
token_total_supply: TOKEN_TOTAL_SUPPLY, token_total_supply: TOKEN_TOTAL_SUPPLY,
complete: false, complete: false,
creator: creator, creator: creator,
}; };
// For sniping developers
// let bonding_curve =
// BondingCurveAccount::new(&mint_pubkey, dev_buy_token, dev_cost_sol, creator);
// Buy operation // buy
let buy_protocol_params = PumpFunParams {
trade_type: trade_type::COPY_BUY.to_string(),
bonding_curve: Some(Arc::new(bonding_curve)),
};
let buy_params = BuyParams {
rpc: Some(solana_trade_client.rpc.clone()),
payer: solana_trade_client.payer.clone(),
mint: mint_pubkey,
creator: creator,
amount_sol: buy_sol_cost,
slippage_basis_points: slippage_basis_points,
priority_fee: solana_trade_client.trade_config.clone().priority_fee,
lookup_table_key: solana_trade_client.trade_config.clone().lookup_table_key,
recent_blockhash,
data_size_limit: 0,
protocol_params: Box::new(buy_protocol_params.clone()),
};
// Buy with MEV protection
let buy_with_tip_params = buy_params
.clone()
.with_tip(solana_trade_client.swqos_clients.clone());
solana_trade_client solana_trade_client
.buy_use_buy_params(buy_with_tip_params, None) .buy(
mint_pubkey,
Some(creator),
buy_sol_cost,
slippage_basis_points,
recent_blockhash,
None,
false,
TradingProtocol::PumpFun,
Some(Box::new(PumpFunParams {
bonding_curve: Some(Arc::new(bonding_curve.clone())),
})),
)
.await?; .await?;
// Sell operation // sell
println!("Selling tokens from PumpFun..."); println!("Selling tokens from PumpFun...");
let sell_protocol_params = PumpFunSellParams {}; let amount_token = 0; // Enter the actual amount_token
let amount_token = 1000000; // Enter the actual token amount
let sell_params = SellParams {
rpc: Some(solana_trade_client.rpc.clone()),
payer: solana_trade_client.payer.clone(),
mint: mint_pubkey,
creator: creator,
amount_token: Some(amount_token),
slippage_basis_points: None,
priority_fee: solana_trade_client.trade_config.clone().priority_fee,
lookup_table_key: solana_trade_client.trade_config.clone().lookup_table_key,
recent_blockhash,
protocol_params: Box::new(sell_protocol_params.clone()),
};
solana_trade_client solana_trade_client
.sell_by_amount_use_sell_params(sell_params) .sell(
mint_pubkey,
Some(creator),
amount_token,
slippage_basis_points,
recent_blockhash,
None,
false,
TradingProtocol::PumpFun,
None,
)
.await?; .await?;
Ok(()) Ok(())
} }
``` ```
@@ -305,73 +292,48 @@ async fn test_pumpfun() -> AnyResult<()> {
### 4. PumpSwap Trading Operations ### 4. PumpSwap Trading Operations
```rust ```rust
use sol_trade_sdk::trading::core::params::PumpSwapParams;
async fn test_pumpswap() -> AnyResult<()> { async fn test_pumpswap() -> AnyResult<()> {
// Basic parameter setup println!("Testing PumpSwap trading...");
let creator = Pubkey::from_str("11111111111111111111111111111111")?; // Developer account
let mint_pubkey = Pubkey::from_str("2zMMhcVQEXDtdE6vsFS7S7D5oUodfJHE8vd1gnBouauv")?; // Token address let solana_trade_client = test_create_solana_trade_client().await?;
let creator = Pubkey::from_str("11111111111111111111111111111111")?; // dev account
let buy_sol_cost = 100_000; // 0.0001 SOL let buy_sol_cost = 100_000; // 0.0001 SOL
let slippage_basis_points = Some(100); let slippage_basis_points = Some(100);
let rpc = RpcClient::new(rpc_url); let recent_blockhash = solana_trade_client.rpc.get_latest_blockhash().await?;
let recent_blockhash = rpc.get_latest_blockhash().unwrap(); let mint_pubkey = Pubkey::from_str("2zMMhcVQEXDtdE6vsFS7S7D5oUodfJHE8vd1gnBouauv")?; // token mint
println!("Buying tokens from PumpSwap..."); println!("Buying tokens from PumpSwap...");
// buy
// PumpSwap parameter configuration
let protocol_params = PumpSwapParams {
pool: None,
pool_base_token_account: None,
pool_quote_token_account: None,
user_base_token_account: None,
user_quote_token_account: None,
auto_handle_wsol: true,
};
// Buy operation
let buy_params = BuyParams {
rpc: Some(solana_trade_client.rpc.clone()),
payer: solana_trade_client.payer.clone(),
mint: mint_pubkey,
creator: creator,
amount_sol: buy_sol_cost,
slippage_basis_points: slippage_basis_points,
priority_fee: solana_trade_client.trade_config.clone().priority_fee,
lookup_table_key: solana_trade_client.trade_config.clone().lookup_table_key,
recent_blockhash,
data_size_limit: 0,
protocol_params: Box::new(protocol_params.clone()),
};
let buy_with_tip_params = buy_params
.clone()
.with_tip(solana_trade_client.swqos_clients.clone());
solana_trade_client solana_trade_client
.buy_use_buy_params(buy_with_tip_params, None) .buy(
mint_pubkey,
Some(creator),
buy_sol_cost,
slippage_basis_points,
recent_blockhash,
None,
false,
TradingProtocol::PumpSwap,
None,
)
.await?; .await?;
// Sell operation // sell
println!("Selling tokens from PumpSwap..."); println!("Selling tokens from PumpSwap...");
let amount_token = 1000000; // Enter the actual token amount let amount_token = 0; // Enter the actual amount_token
let sell_params = SellParams {
rpc: Some(solana_trade_client.rpc.clone()),
payer: solana_trade_client.payer.clone(),
mint: mint_pubkey,
creator: creator,
amount_token: Some(amount_token),
slippage_basis_points: None,
priority_fee: solana_trade_client.trade_config.clone().priority_fee,
lookup_table_key: solana_trade_client.trade_config.clone().lookup_table_key,
recent_blockhash,
protocol_params: Box::new(protocol_params.clone()),
};
solana_trade_client solana_trade_client
.sell_by_amount_use_sell_params(sell_params) .sell(
mint_pubkey,
Some(creator),
amount_token,
slippage_basis_points,
recent_blockhash,
None,
false,
TradingProtocol::PumpSwap,
None,
)
.await?; .await?;
Ok(()) Ok(())
} }
``` ```
@@ -379,68 +341,47 @@ async fn test_pumpswap() -> AnyResult<()> {
### 5. Bonk Trading Operations ### 5. Bonk Trading Operations
```rust ```rust
use sol_trade_sdk::trading::core::params::BonkParams;
async fn test_bonk() -> Result<(), Box<dyn std::error::Error>> { async fn test_bonk() -> Result<(), Box<dyn std::error::Error>> {
// Basic parameter setup println!("Testing Bonk trading...");
let amount = 100_000; // 0.0001 SOL
let mint = Pubkey::from_str("xxxxxxx")?;
let recent_blockhash = solana_trade_client.rpc.get_latest_blockhash().await?;
// Bonk parameter configuration let solana_trade_client = test_create_solana_trade_client().await?;
let bonk_params = BonkParams { let buy_sol_cost = 100_000; // 0.0001 SOL
virtual_base: None, let slippage_basis_points = Some(100); // 1%
virtual_quote: None, let recent_blockhash = solana_trade_client.rpc.get_latest_blockhash().await?;
real_base_before: None, let mint_pubkey = Pubkey::from_str("xxxxxxx")?;
real_quote_before: None,
auto_handle_wsol: true,
};
println!("Buying tokens from letsbonk.fun..."); println!("Buying tokens from letsbonk.fun...");
// buy
// Buy operation
let buy_params = BuyParams {
rpc: Some(solana_trade_client.rpc.clone()),
payer: solana_trade_client.payer.clone(),
mint: mint,
creator: Pubkey::default(),
amount_sol: amount,
slippage_basis_points: None,
priority_fee: solana_trade_client.trade_config.clone().priority_fee,
lookup_table_key: solana_trade_client.trade_config.clone().lookup_table_key,
recent_blockhash,
data_size_limit: 0,
protocol_params: Box::new(bonk_params.clone()),
};
let buy_with_tip_params = buy_params
.clone()
.with_tip(solana_trade_client.swqos_clients.clone());
solana_trade_client solana_trade_client
.buy_use_buy_params(buy_with_tip_params, None) .buy(
mint_pubkey,
None,
buy_sol_cost,
slippage_basis_points,
recent_blockhash,
None,
false,
TradingProtocol::Bonk,
None,
)
.await?; .await?;
// Sell operation // sell
println!("Selling tokens from letsbonk.fun..."); println!("Selling tokens from letsbonk.fun...");
let amount_token = 0; // Enter the actual amount_token
let sell_params = SellParams {
rpc: Some(solana_trade_client.rpc.clone()),
payer: solana_trade_client.payer.clone(),
mint: mint,
creator: Pubkey::default(),
amount_token: None,
slippage_basis_points: None,
priority_fee: solana_trade_client.trade_config.clone().priority_fee,
lookup_table_key: solana_trade_client.trade_config.clone().lookup_table_key,
recent_blockhash,
protocol_params: Box::new(bonk_params.clone()),
};
solana_trade_client solana_trade_client
.sell_by_amount_use_sell_params(sell_params) .sell(
mint_pubkey,
None,
amount_token,
slippage_basis_points,
recent_blockhash,
None,
false,
TradingProtocol::Bonk,
None,
)
.await?; .await?;
Ok(()) Ok(())
} }
``` ```
@@ -487,11 +428,11 @@ let trade_config = TradeConfig {
## New Architecture Features ## New Architecture Features
### Unified Parameter Structure ### Unified Trading Interface
- **BuyParams**: Unified buy parameter structure - **TradingProtocol Enum**: Use unified protocol enums (PumpFun, PumpSwap, Bonk)
- **SellParams**: Unified sell parameter structure - **Unified buy/sell Methods**: All protocols use the same trading method signatures
- **Protocol-specific Parameters**: Each protocol has its own parameter structure (PumpFunParams, PumpSwapParams, BonkParams) - **Protocol-specific Parameters**: Each protocol has its own parameter structure (PumpFunParams, etc.)
### Event Parsing System ### Event Parsing System
@@ -509,29 +450,28 @@ let trade_config = TradeConfig {
``` ```
src/ src/
├── accounts/ # Account-related definitions
├── common/ # Common functionality and tools ├── common/ # Common functionality and tools
├── constants/ # Constant definitions ├── constants/ # Constant definitions
├── error/ # Error handling
├── event_parser/ # Event parsing system
│ ├── common/ # Common event parsing tools
│ ├── core/ # Core parsing traits and interfaces
│ ├── protocols/ # Protocol-specific parsers
│ │ ├── pumpfun/ # PumpFun event parsing
│ │ ├── pumpswap/ # PumpSwap event parsing
│ │ └── bonk/ # Bonk event parsing
│ └── factory.rs # Parser factory
├── grpc/ # gRPC clients
├── instruction/ # Instruction building ├── instruction/ # Instruction building
├── protos/ # Protocol buffer definitions ├── streaming/ # Event stream processing
├── pumpfun/ # PumpFun trading functionality │ ├── event_parser/ # Event parsing system
├── pumpswap/ # PumpSwap trading functionality │ │ ├── common/ # Common event parsing tools
├── bonk/ # Bonk trading functionality │ │ ├── core/ # Core parsing traits and interfaces
│ │ ├── protocols/# Protocol-specific parsers
│ │ │ ├── bonk/ # Bonk event parsing
│ │ │ ├── pumpfun/ # PumpFun event parsing
│ │ │ └── pumpswap/ # PumpSwap event parsing
│ │ └── factory.rs # Parser factory
│ ├── shred_stream.rs # ShredStream client
│ └── yellowstone_grpc.rs # Yellowstone gRPC client
├── swqos/ # MEV service clients ├── swqos/ # MEV service clients
├── trading/ # Unified trading engine ├── trading/ # Unified trading engine
│ ├── common/ # Common trading tools │ ├── common/ # Common trading tools
│ ├── core/ # Core trading engine │ ├── core/ # Core trading engine
── protocols/ # Protocol-specific trading implementations ── bonk/ # Bonk trading implementation
│ ├── pumpfun/ # PumpFun trading implementation
│ ├── pumpswap/ # PumpSwap trading implementation
│ └── factory.rs # Trading factory
├── lib.rs # Main library file ├── lib.rs # Main library file
└── main.rs # Example program └── main.rs # Example program
``` ```
+174 -234
View File
@@ -12,7 +12,7 @@
6. **ShredStream 支持**: 使用 ShredStream 订阅程序事件 6. **ShredStream 支持**: 使用 ShredStream 订阅程序事件
7. **多种 MEV 保护**: 支持 Jito、Nextblock、ZeroSlot、Temporal、Bloxroute 等服务 7. **多种 MEV 保护**: 支持 Jito、Nextblock、ZeroSlot、Temporal、Bloxroute 等服务
8. **并发交易**: 同时使用多个 MEV 服务发送交易,最快的成功,其他失败 8. **并发交易**: 同时使用多个 MEV 服务发送交易,最快的成功,其他失败
9. **统一交易接口**: 使用统一的参数结构进行交易操作 9. **统一交易接口**: 使用统一的交易协议枚举进行交易操作
## 安装 ## 安装
@@ -38,18 +38,20 @@ sol-trade-sdk = { path = "./sol-trade-sdk", version = "0.1.0" }
```rust ```rust
use sol_trade_sdk::{ use sol_trade_sdk::{
event_parser::{ streaming::{
protocols::{ event_parser::{
pumpfun::{PumpFunCreateTokenEvent, PumpFunTradeEvent}, protocols::{
pumpswap::{ bonk::{BonkPoolCreateEvent, BonkTradeEvent},
PumpSwapBuyEvent, PumpSwapCreatePoolEvent, PumpSwapDepositEvent, pumpfun::{PumpFunCreateTokenEvent, PumpFunTradeEvent},
PumpSwapSellEvent, PumpSwapWithdrawEvent, pumpswap::{
PumpSwapBuyEvent, PumpSwapCreatePoolEvent, PumpSwapDepositEvent,
PumpSwapSellEvent, PumpSwapWithdrawEvent,
},
}, },
bonk::{BonkPoolCreateEvent, BonkTradeEvent}, Protocol, UnifiedEvent,
}, },
Protocol, UnifiedEvent, YellowstoneGrpc,
}, },
grpc::YellowstoneGrpc,
match_event, match_event,
}; };
@@ -97,11 +99,7 @@ async fn test_grpc() -> Result<(), Box<dyn std::error::Error>> {
// 订阅多个协议的事件 // 订阅多个协议的事件
println!("开始监听事件,按 Ctrl+C 停止..."); println!("开始监听事件,按 Ctrl+C 停止...");
let protocols = vec![ let protocols = vec![Protocol::PumpFun, Protocol::PumpSwap, Protocol::Bonk];
Protocol::PumpFun,
Protocol::PumpSwap,
Protocol::Bonk,
];
grpc.subscribe_events(protocols, None, None, None, callback) grpc.subscribe_events(protocols, None, None, None, callback)
.await?; .await?;
@@ -112,7 +110,7 @@ async fn test_grpc() -> Result<(), Box<dyn std::error::Error>> {
#### 1.2 使用 ShredStream 订阅事件 #### 1.2 使用 ShredStream 订阅事件
```rust ```rust
use sol_trade_sdk::grpc::ShredStreamGrpc; use sol_trade_sdk::streaming::ShredStreamGrpc;
async fn test_shreds() -> Result<(), Box<dyn std::error::Error>> { async fn test_shreds() -> Result<(), Box<dyn std::error::Error>> {
// 使用 ShredStream 客户端订阅事件 // 使用 ShredStream 客户端订阅事件
@@ -155,11 +153,7 @@ async fn test_shreds() -> Result<(), Box<dyn std::error::Error>> {
// 订阅事件 // 订阅事件
println!("开始监听事件,按 Ctrl+C 停止..."); println!("开始监听事件,按 Ctrl+C 停止...");
let protocols = vec![ let protocols = vec![Protocol::PumpFun, Protocol::PumpSwap, Protocol::Bonk];
Protocol::PumpFun,
Protocol::PumpSwap,
Protocol::Bonk,
];
shred_stream shred_stream
.shredstream_subscribe(protocols, None, callback) .shredstream_subscribe(protocols, None, callback)
.await?; .await?;
@@ -177,127 +171,120 @@ use sol_trade_sdk::{
swqos::{SwqosConfig, SwqosRegion}, swqos::{SwqosConfig, SwqosRegion},
SolanaTrade SolanaTrade
}; };
use solana_client::rpc_client::RpcClient;
use solana_sdk::{commitment_config::CommitmentConfig, pubkey::Pubkey, signature::Keypair}; use solana_sdk::{commitment_config::CommitmentConfig, pubkey::Pubkey, signature::Keypair};
// 创建交易者账户 /// 创建 SolanaTrade 客户端的示例
let payer = Keypair::new(); async fn test_create_solana_trade_client() -> AnyResult<SolanaTrade> {
let rpc_url = "https://mainnet.helius-rpc.com/?api-key=xxxxxx".to_string(); println!("Creating SolanaTrade client...");
// 配置多个MEV服务,支持并发交易 let payer = Keypair::new();
let swqos_configs = vec![ let rpc_url = "https://mainnet.helius-rpc.com/?api-key=xxxxxx".to_string();
SwqosConfig::Jito(SwqosRegion::Frankfurt),
SwqosConfig::NextBlock("your api_token".to_string(), SwqosRegion::Frankfurt),
SwqosConfig::Bloxroute("your api_token".to_string(), SwqosRegion::Frankfurt),
SwqosConfig::ZeroSlot("your api_token".to_string(), SwqosRegion::Frankfurt),
SwqosConfig::Temporal("your api_token".to_string(), SwqosRegion::Frankfurt),
SwqosConfig::Default(rpc_url.clone()),
];
// 定义交易配置 // 配置各种 SWQOS 服务
let trade_config = TradeConfig { let swqos_configs = vec![
rpc_url: rpc_url.clone(), SwqosConfig::Jito(SwqosRegion::Frankfurt),
commitment: CommitmentConfig::confirmed(), SwqosConfig::NextBlock("your api_token".to_string(), SwqosRegion::Frankfurt),
priority_fee: PriorityFee::default(), SwqosConfig::Bloxroute("your api_token".to_string(), SwqosRegion::Frankfurt),
swqos_configs, SwqosConfig::ZeroSlot("your api_token".to_string(), SwqosRegion::Frankfurt),
lookup_table_key: None, SwqosConfig::Temporal("your api_token".to_string(), SwqosRegion::Frankfurt),
}; SwqosConfig::Default(rpc_url.clone()),
];
// 创建SolanaTrade实例 // 定义交易配置
let solana_trade_client = SolanaTrade::new(Arc::new(payer), trade_config).await; let trade_config = TradeConfig {
rpc_url: rpc_url.clone(),
commitment: CommitmentConfig::confirmed(),
priority_fee: PriorityFee::default(),
swqos_configs,
lookup_table_key: None,
};
let solana_trade_client = SolanaTrade::new(Arc::new(payer), trade_config).await;
println!("SolanaTrade client created successfully!");
Ok(solana_trade_client)
}
``` ```
### 3. PumpFun 交易操作 ### 3. PumpFun 交易操作
```rust ```rust
use sol_trade_sdk::{ use sol_trade_sdk::{
accounts::BondingCurveAccount, common::bonding_curve::BondingCurveAccount,
constants::{pumpfun::global_constants::TOKEN_TOTAL_SUPPLY, trade_type}, constants::pumpfun::global_constants::TOKEN_TOTAL_SUPPLY,
pumpfun::common::get_bonding_curve_account_v2,
trading::{ trading::{
core::params::{PumpFunParams, PumpFunSellParams}, core::params::PumpFunParams,
BuyParams, SellParams, factory::TradingProtocol,
pumpfun::common::get_bonding_curve_account_v2,
}, },
}; };
async fn test_pumpfun() -> AnyResult<()> { async fn test_pumpfun() -> AnyResult<()> {
// 基本参数设置 println!("Testing PumpFun trading...");
let creator = Pubkey::from_str("xxxxxx")?; // 开发者账户
let mint_pubkey = Pubkey::from_str("xxxxxx")?; // 代币地址 let solana_trade_client = test_create_solana_trade_client().await?;
let creator = Pubkey::from_str("xxxxxx")?; // dev account
let buy_sol_cost = 100_000; // 0.0001 SOL let buy_sol_cost = 100_000; // 0.0001 SOL
let slippage_basis_points = Some(100); let slippage_basis_points = Some(100);
let rpc = RpcClient::new(rpc_url); let recent_blockhash = solana_trade_client.rpc.get_latest_blockhash().await?;
let recent_blockhash = rpc.get_latest_blockhash().unwrap(); let mint_pubkey = Pubkey::from_str("xxxxxx")?; // token mint
println!("Buying tokens from PumpFun..."); println!("Buying tokens from PumpFun...");
// get bonding curve
// 获取bonding curve信息
let (bonding_curve, bonding_curve_pda) = let (bonding_curve, bonding_curve_pda) =
get_bonding_curve_account_v2(&solana_trade_client.rpc, &mint_pubkey).await?; get_bonding_curve_account_v2(&solana_trade_client.rpc, &mint_pubkey).await?;
let virtual_token_reserves = bonding_curve.virtual_token_reserves;
let virtual_sol_reserves = bonding_curve.virtual_sol_reserves;
let real_token_reserves = bonding_curve.real_token_reserves;
let real_sol_reserves = bonding_curve.real_sol_reserves;
let bonding_curve = BondingCurveAccount { let bonding_curve = BondingCurveAccount {
discriminator: bonding_curve.discriminator, discriminator: bonding_curve.discriminator,
account: bonding_curve_pda, account: bonding_curve_pda,
virtual_token_reserves: bonding_curve.virtual_token_reserves, virtual_token_reserves: virtual_token_reserves,
virtual_sol_reserves: bonding_curve.virtual_sol_reserves, virtual_sol_reserves: virtual_sol_reserves,
real_token_reserves: bonding_curve.real_token_reserves, real_token_reserves: real_token_reserves,
real_sol_reserves: bonding_curve.real_sol_reserves, real_sol_reserves: real_sol_reserves,
token_total_supply: TOKEN_TOTAL_SUPPLY, token_total_supply: TOKEN_TOTAL_SUPPLY,
complete: false, complete: false,
creator: creator, creator: creator,
}; };
// 如果是狙击开发者
// let bonding_curve =
// BondingCurveAccount::new(&mint_pubkey, dev_buy_token, dev_cost_sol, creator);
// 购买操作 // buy
let buy_protocol_params = PumpFunParams {
trade_type: trade_type::COPY_BUY.to_string(),
bonding_curve: Some(Arc::new(bonding_curve)),
};
let buy_params = BuyParams {
rpc: Some(solana_trade_client.rpc.clone()),
payer: solana_trade_client.payer.clone(),
mint: mint_pubkey,
creator: creator,
amount_sol: buy_sol_cost,
slippage_basis_points: slippage_basis_points,
priority_fee: solana_trade_client.trade_config.clone().priority_fee,
lookup_table_key: solana_trade_client.trade_config.clone().lookup_table_key,
recent_blockhash,
data_size_limit: 0,
protocol_params: Box::new(buy_protocol_params.clone()),
};
// 使用MEV保护的购买
let buy_with_tip_params = buy_params
.clone()
.with_tip(solana_trade_client.swqos_clients.clone());
solana_trade_client solana_trade_client
.buy_use_buy_params(buy_with_tip_params, None) .buy(
mint_pubkey,
Some(creator),
buy_sol_cost,
slippage_basis_points,
recent_blockhash,
None,
false,
TradingProtocol::PumpFun,
Some(Box::new(PumpFunParams {
bonding_curve: Some(Arc::new(bonding_curve.clone())),
})),
)
.await?; .await?;
// 卖出操作 // sell
println!("Selling tokens from PumpFun..."); println!("Selling tokens from PumpFun...");
let sell_protocol_params = PumpFunSellParams {}; let amount_token = 0; // 写上真实的amount_token
let amount_token = 1000000; // 写上真实的代币数量
let sell_params = SellParams {
rpc: Some(solana_trade_client.rpc.clone()),
payer: solana_trade_client.payer.clone(),
mint: mint_pubkey,
creator: creator,
amount_token: Some(amount_token),
slippage_basis_points: None,
priority_fee: solana_trade_client.trade_config.clone().priority_fee,
lookup_table_key: solana_trade_client.trade_config.clone().lookup_table_key,
recent_blockhash,
protocol_params: Box::new(sell_protocol_params.clone()),
};
solana_trade_client solana_trade_client
.sell_by_amount_use_sell_params(sell_params) .sell(
mint_pubkey,
Some(creator),
amount_token,
slippage_basis_points,
recent_blockhash,
None,
false,
TradingProtocol::PumpFun,
None,
)
.await?; .await?;
Ok(()) Ok(())
} }
``` ```
@@ -305,73 +292,48 @@ async fn test_pumpfun() -> AnyResult<()> {
### 4. PumpSwap 交易操作 ### 4. PumpSwap 交易操作
```rust ```rust
use sol_trade_sdk::trading::core::params::PumpSwapParams;
async fn test_pumpswap() -> AnyResult<()> { async fn test_pumpswap() -> AnyResult<()> {
// 基本参数设置 println!("Testing PumpSwap trading...");
let creator = Pubkey::from_str("11111111111111111111111111111111")?; // 开发者账户
let mint_pubkey = Pubkey::from_str("2zMMhcVQEXDtdE6vsFS7S7D5oUodfJHE8vd1gnBouauv")?; // 代币地址 let solana_trade_client = test_create_solana_trade_client().await?;
let creator = Pubkey::from_str("11111111111111111111111111111111")?; // dev account
let buy_sol_cost = 100_000; // 0.0001 SOL let buy_sol_cost = 100_000; // 0.0001 SOL
let slippage_basis_points = Some(100); let slippage_basis_points = Some(100);
let rpc = RpcClient::new(rpc_url); let recent_blockhash = solana_trade_client.rpc.get_latest_blockhash().await?;
let recent_blockhash = rpc.get_latest_blockhash().unwrap(); let mint_pubkey = Pubkey::from_str("2zMMhcVQEXDtdE6vsFS7S7D5oUodfJHE8vd1gnBouauv")?; // token mint
println!("Buying tokens from PumpSwap..."); println!("Buying tokens from PumpSwap...");
// buy
// PumpSwap参数配置
let protocol_params = PumpSwapParams {
pool: None,
pool_base_token_account: None,
pool_quote_token_account: None,
user_base_token_account: None,
user_quote_token_account: None,
auto_handle_wsol: true,
};
// 购买操作
let buy_params = BuyParams {
rpc: Some(solana_trade_client.rpc.clone()),
payer: solana_trade_client.payer.clone(),
mint: mint_pubkey,
creator: creator,
amount_sol: buy_sol_cost,
slippage_basis_points: slippage_basis_points,
priority_fee: solana_trade_client.trade_config.clone().priority_fee,
lookup_table_key: solana_trade_client.trade_config.clone().lookup_table_key,
recent_blockhash,
data_size_limit: 0,
protocol_params: Box::new(protocol_params.clone()),
};
let buy_with_tip_params = buy_params
.clone()
.with_tip(solana_trade_client.swqos_clients.clone());
solana_trade_client solana_trade_client
.buy_use_buy_params(buy_with_tip_params, None) .buy(
mint_pubkey,
Some(creator),
buy_sol_cost,
slippage_basis_points,
recent_blockhash,
None,
false,
TradingProtocol::PumpSwap,
None,
)
.await?; .await?;
// 卖出操作 // sell
println!("Selling tokens from PumpSwap..."); println!("Selling tokens from PumpSwap...");
let amount_token = 1000000; // 写上真实的代币数量 let amount_token = 0; // 写上真实的amount_token
let sell_params = SellParams {
rpc: Some(solana_trade_client.rpc.clone()),
payer: solana_trade_client.payer.clone(),
mint: mint_pubkey,
creator: creator,
amount_token: Some(amount_token),
slippage_basis_points: None,
priority_fee: solana_trade_client.trade_config.clone().priority_fee,
lookup_table_key: solana_trade_client.trade_config.clone().lookup_table_key,
recent_blockhash,
protocol_params: Box::new(protocol_params.clone()),
};
solana_trade_client solana_trade_client
.sell_by_amount_use_sell_params(sell_params) .sell(
mint_pubkey,
Some(creator),
amount_token,
slippage_basis_points,
recent_blockhash,
None,
false,
TradingProtocol::PumpSwap,
None,
)
.await?; .await?;
Ok(()) Ok(())
} }
``` ```
@@ -379,68 +341,47 @@ async fn test_pumpswap() -> AnyResult<()> {
### 5. Bonk 交易操作 ### 5. Bonk 交易操作
```rust ```rust
use sol_trade_sdk::trading::core::params::BonkParams;
async fn test_bonk() -> Result<(), Box<dyn std::error::Error>> { async fn test_bonk() -> Result<(), Box<dyn std::error::Error>> {
// 基本参数设置 println!("Testing Bonk trading...");
let amount = 100_000; // 0.0001 SOL
let mint = Pubkey::from_str("xxxxxxx")?;
let recent_blockhash = solana_trade_client.rpc.get_latest_blockhash().await?;
// Bonk参数配置 let solana_trade_client = test_create_solana_trade_client().await?;
let bonk_params = BonkParams { let buy_sol_cost = 100_000; // 0.0001 SOL
virtual_base: None, let slippage_basis_points = Some(100); // 1%
virtual_quote: None, let recent_blockhash = solana_trade_client.rpc.get_latest_blockhash().await?;
real_base_before: None, let mint_pubkey = Pubkey::from_str("xxxxxxx")?;
real_quote_before: None,
auto_handle_wsol: true,
};
println!("Buying tokens from letsbonk.fun..."); println!("Buying tokens from letsbonk.fun...");
// buy
// 购买操作
let buy_params = BuyParams {
rpc: Some(solana_trade_client.rpc.clone()),
payer: solana_trade_client.payer.clone(),
mint: mint,
creator: Pubkey::default(),
amount_sol: amount,
slippage_basis_points: None,
priority_fee: solana_trade_client.trade_config.clone().priority_fee,
lookup_table_key: solana_trade_client.trade_config.clone().lookup_table_key,
recent_blockhash,
data_size_limit: 0,
protocol_params: Box::new(bonk_params.clone()),
};
let buy_with_tip_params = buy_params
.clone()
.with_tip(solana_trade_client.swqos_clients.clone());
solana_trade_client solana_trade_client
.buy_use_buy_params(buy_with_tip_params, None) .buy(
mint_pubkey,
None,
buy_sol_cost,
slippage_basis_points,
recent_blockhash,
None,
false,
TradingProtocol::Bonk,
None,
)
.await?; .await?;
// 卖出操作 // sell
println!("Selling tokens from letsbonk.fun..."); println!("Selling tokens from letsbonk.fun...");
let amount_token = 0; // 写上真实的amount_token
let sell_params = SellParams {
rpc: Some(solana_trade_client.rpc.clone()),
payer: solana_trade_client.payer.clone(),
mint: mint,
creator: Pubkey::default(),
amount_token: None,
slippage_basis_points: None,
priority_fee: solana_trade_client.trade_config.clone().priority_fee,
lookup_table_key: solana_trade_client.trade_config.clone().lookup_table_key,
recent_blockhash,
protocol_params: Box::new(bonk_params.clone()),
};
solana_trade_client solana_trade_client
.sell_by_amount_use_sell_params(sell_params) .sell(
mint_pubkey,
None,
amount_token,
slippage_basis_points,
recent_blockhash,
None,
false,
TradingProtocol::Bonk,
None,
)
.await?; .await?;
Ok(()) Ok(())
} }
``` ```
@@ -487,11 +428,11 @@ let trade_config = TradeConfig {
## 新架构特性 ## 新架构特性
### 统一参数结构 ### 统一交易接口
- **BuyParams**: 统一的购买参数结构 - **TradingProtocol 枚举**: 使用统一的协议枚举(PumpFun、PumpSwap、Bonk
- **SellParams**: 统一的卖出参数结构 - **统一的 buy/sell 方法**: 所有协议都使用相同的交易方法签名
- **协议特定参数**: 每个协议都有自己的参数结构(PumpFunParams、PumpSwapParams、BonkParams - **协议特定参数**: 每个协议都有自己的参数结构(PumpFunParams
### 事件解析系统 ### 事件解析系统
@@ -509,29 +450,28 @@ let trade_config = TradeConfig {
``` ```
src/ src/
├── accounts/ # 账户相关定义
├── common/ # 通用功能和工具 ├── common/ # 通用功能和工具
├── constants/ # 常量定义 ├── constants/ # 常量定义
├── error/ # 错误处理
├── event_parser/ # 事件解析系统
│ ├── common/ # 通用事件解析工具
│ ├── core/ # 核心解析特征和接口
│ ├── protocols/ # 协议特定解析器
│ │ ├── pumpfun/ # PumpFun事件解析
│ │ ├── pumpswap/ # PumpSwap事件解析
│ │ └── bonk/ # Bonk事件解析
│ └── factory.rs # 解析器工厂
├── grpc/ # gRPC客户端
├── instruction/ # 指令构建 ├── instruction/ # 指令构建
├── protos/ # 协议缓冲区定义 ├── streaming/ # 事件流处理
├── pumpfun/ # PumpFun交易功能 │ ├── event_parser/ # 事件解析系统
├── pumpswap/ # PumpSwap交易功能 │ │ ├── common/ # 通用事件解析工具
├── bonk/ # Bonk交易功能 ├── core/ # 核心解析特征和接口
│ │ ├── protocols/# 协议特定解析器
│ │ │ ├── bonk/ # Bonk事件解析
│ │ │ ├── pumpfun/ # PumpFun事件解析
│ │ │ └── pumpswap/ # PumpSwap事件解析
│ │ └── factory.rs # 解析器工厂
│ ├── shred_stream.rs # ShredStream客户端
│ └── yellowstone_grpc.rs # Yellowstone gRPC客户端
├── swqos/ # MEV服务客户端 ├── swqos/ # MEV服务客户端
├── trading/ # 统一交易引擎 ├── trading/ # 统一交易引擎
│ ├── common/ # 通用交易工具 │ ├── common/ # 通用交易工具
│ ├── core/ # 核心交易引擎 │ ├── core/ # 核心交易引擎
── protocols/ # 协议特定交易实现 ── bonk/ # Bonk交易实现
│ ├── pumpfun/ # PumpFun交易实现
│ ├── pumpswap/ # PumpSwap交易实现
│ └── factory.rs # 交易工厂
├── lib.rs # 主库文件 ├── lib.rs # 主库文件
└── main.rs # 示例程序 └── main.rs # 示例程序
``` ```
+1 -1
View File
@@ -3,7 +3,7 @@ use std::sync::Arc;
use solana_client::rpc_client::RpcClient; use solana_client::rpc_client::RpcClient;
use solana_sdk::{commitment_config::CommitmentConfig, pubkey::Pubkey, signature::Keypair}; use solana_sdk::{commitment_config::CommitmentConfig, pubkey::Pubkey, signature::Keypair};
use serde::Deserialize; use serde::Deserialize;
use crate::{constants::pumpfun::trade::{DEFAULT_BUY_TIP_FEE, DEFAULT_COMPUTE_UNIT_LIMIT, DEFAULT_COMPUTE_UNIT_PRICE, DEFAULT_RPC_UNIT_LIMIT, DEFAULT_RPC_UNIT_PRICE, DEFAULT_SELL_TIP_FEE}, swqos::{SwqosClient, SwqosConfig, SwqosRegion}}; use crate::{constants::trade::trade::{DEFAULT_BUY_TIP_FEE, DEFAULT_COMPUTE_UNIT_LIMIT, DEFAULT_COMPUTE_UNIT_PRICE, DEFAULT_RPC_UNIT_LIMIT, DEFAULT_RPC_UNIT_PRICE, DEFAULT_SELL_TIP_FEE}, swqos::{SwqosClient, SwqosConfig}};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct TradeConfig { pub struct TradeConfig {
-9
View File
@@ -35,12 +35,3 @@ pub mod accounts {
pub const BUY_EXECT_IN_DISCRIMINATOR: [u8; 8] = [250, 234, 13, 123, 213, 156, 19, 236]; pub const BUY_EXECT_IN_DISCRIMINATOR: [u8; 8] = [250, 234, 13, 123, 213, 156, 19, 236];
pub const SELL_EXECT_IN_DISCRIMINATOR: [u8; 8] = [149, 39, 222, 155, 211, 124, 152, 26]; pub const SELL_EXECT_IN_DISCRIMINATOR: [u8; 8] = [149, 39, 222, 155, 211, 124, 152, 26];
pub mod trade {
pub const TRADER_TIP_AMOUNT: u64 = 100000; // 0.0001 SOL in lamports
pub const DEFAULT_SLIPPAGE: u64 = 100; // 1%
pub const DEFAULT_COMPUTE_UNIT_LIMIT: u32 = 78000;
pub const DEFAULT_COMPUTE_UNIT_PRICE: u64 = 500000;
pub const DEFAULT_BUY_TIP_FEE: u64 = 600000; // 0.0006 SOL in lamports
pub const DEFAULT_SELL_TIP_FEE: u64 = 100000; // 0.0001 SOL in lamports
}
+5 -11
View File
@@ -1,17 +1,11 @@
pub mod bonk;
pub mod pumpfun; pub mod pumpfun;
pub mod pumpswap; pub mod pumpswap;
pub mod bonk;
pub mod swqos; pub mod swqos;
pub mod trade;
pub mod trade_type {
pub const COPY_BUY: &'static str = "copy_buy";
pub const COPY_SELL: &'static str = "copy_sell";
pub const SNIPER_BUY: &'static str = "sniper_buy";
pub const SNIPER_SELL: &'static str = "sniper_sell";
}
pub mod trade_platform { pub mod trade_platform {
pub const PUMPFUN: &'static str = "pumpfun"; pub const PUMPFUN: &'static str = "pumpfun";
pub const PUMPFUN_SWAP: &'static str = "pumpswap"; pub const PUMPFUN_SWAP: &'static str = "pumpswap";
pub const BONK: &'static str = "bonk"; pub const BONK: &'static str = "bonk";
} }
+4 -15
View File
@@ -75,8 +75,8 @@ pub mod global_constants {
pub const PUMPFUN_AMM_FEE_4: Pubkey = pubkey!("AVmoTthdrX6tKt4nDjco2D775W2YK3sDhxPcMmzUAmTY"); // Pump.fun AMM: Protocol Fee 4 pub const PUMPFUN_AMM_FEE_4: Pubkey = pubkey!("AVmoTthdrX6tKt4nDjco2D775W2YK3sDhxPcMmzUAmTY"); // Pump.fun AMM: Protocol Fee 4
pub const PUMPFUN_AMM_FEE_5: Pubkey = pubkey!("CebN5WGQ4jvEPvsVU4EoHEpgzq1VV7AbicfhtW4xC9iM"); // Pump.fun AMM: Protocol Fee 5 pub const PUMPFUN_AMM_FEE_5: Pubkey = pubkey!("CebN5WGQ4jvEPvsVU4EoHEpgzq1VV7AbicfhtW4xC9iM"); // Pump.fun AMM: Protocol Fee 5
pub const PUMPFUN_AMM_FEE_6: Pubkey = pubkey!("FWsW1xNtWscwNmKv6wVsU1iTzRN6wmmk3MjxRP5tT7hz"); // Pump.fun AMM: Protocol Fee 6 pub const PUMPFUN_AMM_FEE_6: Pubkey = pubkey!("FWsW1xNtWscwNmKv6wVsU1iTzRN6wmmk3MjxRP5tT7hz"); // Pump.fun AMM: Protocol Fee 6
pub const PUMPFUN_AMM_FEE_7: Pubkey = pubkey!("G5UZAVbAf46s7cKWoyKu8kYTip9DGTpbLZ2qa9Aq69dP"); // Pump.fun AMM: Protocol Fee 7 pub const PUMPFUN_AMM_FEE_7: Pubkey = pubkey!("G5UZAVbAf46s7cKWoyKu8kYTip9DGTpbLZ2qa9Aq69dP");
// Pump.fun AMM: Protocol Fee 7
} }
/// Constants related to program accounts and authorities /// Constants related to program accounts and authorities
@@ -99,7 +99,8 @@ pub mod accounts {
pub const TOKEN_PROGRAM: Pubkey = pubkey!("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"); pub const TOKEN_PROGRAM: Pubkey = pubkey!("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA");
/// Associated Token Program ID /// Associated Token Program ID
pub const ASSOCIATED_TOKEN_PROGRAM: Pubkey = pubkey!("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"); pub const ASSOCIATED_TOKEN_PROGRAM: Pubkey =
pubkey!("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL");
/// Rent Sysvar ID /// Rent Sysvar ID
pub const RENT: Pubkey = pubkey!("SysvarRent111111111111111111111111111111111"); pub const RENT: Pubkey = pubkey!("SysvarRent111111111111111111111111111111111");
@@ -107,20 +108,8 @@ pub mod accounts {
pub const AMM_PROGRAM: Pubkey = pubkey!("675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8"); pub const AMM_PROGRAM: Pubkey = pubkey!("675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8");
} }
pub mod trade {
pub const TRADER_TIP_AMOUNT: f64 = 0.0001;
pub const DEFAULT_SLIPPAGE: u64 = 1000; // 10%
pub const DEFAULT_COMPUTE_UNIT_LIMIT: u32 = 78000;
pub const DEFAULT_COMPUTE_UNIT_PRICE: u64 = 500000;
pub const DEFAULT_BUY_TIP_FEE: f64 = 0.0006;
pub const DEFAULT_SELL_TIP_FEE: f64 = 0.0001;
pub const DEFAULT_RPC_UNIT_LIMIT: u32 = 1000000;
pub const DEFAULT_RPC_UNIT_PRICE: u64 = 500000;
}
pub struct Symbol; pub struct Symbol;
impl Symbol { impl Symbol {
pub const SOLANA: &'static str = "solana"; pub const SOLANA: &'static str = "solana";
} }
+5 -13
View File
@@ -32,7 +32,7 @@ pub mod accounts {
/// Public key for the fee recipient /// Public key for the fee recipient
pub const FEE_RECIPIENT: Pubkey = pubkey!("62qc2CNXwrYqQScmEdiZFFAnJR262PxWEuNQtxfafNgV"); pub const FEE_RECIPIENT: Pubkey = pubkey!("62qc2CNXwrYqQScmEdiZFFAnJR262PxWEuNQtxfafNgV");
pub const FEE_RECIPIENT_ATA:Pubkey = pubkey!("94qWNrtmfn42h3ZjUZwWvK1MEo9uVmmrBPd2hpNjYDjb"); pub const FEE_RECIPIENT_ATA: Pubkey = pubkey!("94qWNrtmfn42h3ZjUZwWvK1MEo9uVmmrBPd2hpNjYDjb");
/// Public key for the global PDA /// Public key for the global PDA
pub const GLOBAL_ACCOUNT: Pubkey = pubkey!("ADyA8hdefvWN2dbGGWFotbzWxrAvLW83WG6QCVXvJKqw"); pub const GLOBAL_ACCOUNT: Pubkey = pubkey!("ADyA8hdefvWN2dbGGWFotbzWxrAvLW83WG6QCVXvJKqw");
@@ -49,26 +49,18 @@ pub mod accounts {
pub const TOKEN_PROGRAM: Pubkey = pubkey!("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"); pub const TOKEN_PROGRAM: Pubkey = pubkey!("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA");
/// Associated Token Program ID /// Associated Token Program ID
pub const ASSOCIATED_TOKEN_PROGRAM: Pubkey = pubkey!("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"); pub const ASSOCIATED_TOKEN_PROGRAM: Pubkey =
pubkey!("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL");
// PumpSwap 协议费用接收者 // PumpSwap 协议费用接收者
pub const PROTOCOL_FEE_RECIPIENT: Pubkey = pubkey!("62qc2CNXwrYqQScmEdiZFFAnJR262PxWEuNQtxfafNgV"); pub const PROTOCOL_FEE_RECIPIENT: Pubkey =
pubkey!("62qc2CNXwrYqQScmEdiZFFAnJR262PxWEuNQtxfafNgV");
/// Rent Sysvar ID /// Rent Sysvar ID
pub const RENT: Pubkey = pubkey!("SysvarRent111111111111111111111111111111111"); pub const RENT: Pubkey = pubkey!("SysvarRent111111111111111111111111111111111");
pub const AMM_PROGRAM: Pubkey = pubkey!("pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA"); pub const AMM_PROGRAM: Pubkey = pubkey!("pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA");
} }
pub const BUY_DISCRIMINATOR: [u8; 8] = [102, 6, 61, 18, 1, 218, 235, 234]; pub const BUY_DISCRIMINATOR: [u8; 8] = [102, 6, 61, 18, 1, 218, 235, 234];
pub const SELL_DISCRIMINATOR: [u8; 8] = [51, 230, 133, 164, 1, 127, 131, 173]; pub const SELL_DISCRIMINATOR: [u8; 8] = [51, 230, 133, 164, 1, 127, 131, 173];
pub mod trade {
pub const TRADER_TIP_AMOUNT: u64 = 100000; // 0.0001 SOL in lamports
pub const DEFAULT_SLIPPAGE: u64 = 1000; // 10%
pub const DEFAULT_COMPUTE_UNIT_LIMIT: u32 = 78000;
pub const DEFAULT_COMPUTE_UNIT_PRICE: u64 = 500000;
pub const DEFAULT_BUY_TIP_FEE: u64 = 600000; // 0.0006 SOL in lamports
pub const DEFAULT_SELL_TIP_FEE: u64 = 100000; // 0.0001 SOL in lamports
}
+9
View File
@@ -0,0 +1,9 @@
pub mod trade {
pub const DEFAULT_SLIPPAGE: u64 = 1000; // 10%
pub const DEFAULT_COMPUTE_UNIT_LIMIT: u32 = 78000;
pub const DEFAULT_COMPUTE_UNIT_PRICE: u64 = 500000;
pub const DEFAULT_BUY_TIP_FEE: f64 = 0.0006;
pub const DEFAULT_SELL_TIP_FEE: f64 = 0.0001;
pub const DEFAULT_RPC_UNIT_LIMIT: u32 = 1000000;
pub const DEFAULT_RPC_UNIT_PRICE: u64 = 500000;
}
+5 -3
View File
@@ -1,15 +1,17 @@
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use solana_sdk::{instruction::Instruction, pubkey::Pubkey, signer::Signer}; use solana_sdk::{instruction::Instruction, signer::Signer};
use spl_associated_token_account::instruction::create_associated_token_account_idempotent; use spl_associated_token_account::instruction::create_associated_token_account_idempotent;
use crate::{ use crate::{
constants::bonk::{ constants::bonk::{
accounts, trade::DEFAULT_SLIPPAGE, BUY_EXECT_IN_DISCRIMINATOR, SELL_EXECT_IN_DISCRIMINATOR, accounts, BUY_EXECT_IN_DISCRIMINATOR, SELL_EXECT_IN_DISCRIMINATOR,
}, },
constants::trade::trade::DEFAULT_SLIPPAGE,
trading::bonk::{ trading::bonk::{
common::{get_amount_out, get_pool_pda, get_token_balance, get_vault_pda}, common::{get_amount_out, get_pool_pda, get_vault_pda},
pool::Pool, pool::Pool,
}, },
trading::common::utils::get_token_balance,
trading::core::{ trading::core::{
params::{BuyParams, BonkParams, SellParams}, params::{BuyParams, BonkParams, SellParams},
traits::InstructionBuilder, traits::InstructionBuilder,
+24 -27
View File
@@ -1,34 +1,28 @@
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use solana_sdk::{ use solana_sdk::{instruction::Instruction, native_token::sol_to_lamports};
instruction::Instruction, native_token::sol_to_lamports,
};
use spl_associated_token_account::{ use spl_associated_token_account::{
get_associated_token_address, instruction::create_associated_token_account, get_associated_token_address, instruction::create_associated_token_account,
}; };
use spl_token::instruction::close_account; use spl_token::instruction::close_account;
use crate::{ use crate::{
constants, trading::pumpfun::common::{ constants,
get_bonding_curve_pda, get_global_pda, get_metadata_pda, get_mint_authority_pda trading::pumpfun::common::{
} get_bonding_curve_pda, get_global_pda, get_metadata_pda, get_mint_authority_pda,
},
}; };
use solana_sdk::{ use solana_sdk::{instruction::AccountMeta, pubkey::Pubkey, signature::Keypair, signer::Signer};
instruction::AccountMeta,
pubkey::Pubkey,
signature::Keypair,
signer::Signer,
};
use crate::{ use crate::{
constants::pumpfun::{global_constants::FEE_RECIPIENT, trade::DEFAULT_SLIPPAGE}, constants::pumpfun::global_constants::FEE_RECIPIENT,
trading::pumpfun::common::{ constants::trade::trade::DEFAULT_SLIPPAGE,
calculate_with_slippage_buy, get_buy_token_amount_from_sol_amount, get_creator_vault_pda, trading::common::utils::calculate_with_slippage_buy,
},
trading::core::{ trading::core::{
params::{BuyParams, PumpFunParams, SellParams}, params::{BuyParams, PumpFunParams, SellParams},
traits::InstructionBuilder, traits::InstructionBuilder,
}, },
trading::pumpfun::common::{get_buy_token_amount_from_sol_amount, get_creator_vault_pda},
}; };
/// PumpFun协议的指令构建器 /// PumpFun协议的指令构建器
@@ -56,9 +50,7 @@ impl InstructionBuilder for PumpFunInstructionBuilder {
let max_sol_cost = calculate_with_slippage_buy( let max_sol_cost = calculate_with_slippage_buy(
params.amount_sol, params.amount_sol,
params params.slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE),
.slippage_basis_points
.unwrap_or(DEFAULT_SLIPPAGE),
); );
let creator_vault_pda = bonding_curve.get_creator_vault_pda(); let creator_vault_pda = bonding_curve.get_creator_vault_pda();
@@ -161,22 +153,24 @@ pub struct Create {
impl Create { impl Create {
pub fn data(&self) -> Vec<u8> { pub fn data(&self) -> Vec<u8> {
let mut data = Vec::with_capacity(8 + 4 + self._name.len() + 4 + self._symbol.len() + 4 + self._uri.len() + 32); let mut data = Vec::with_capacity(
8 + 4 + self._name.len() + 4 + self._symbol.len() + 4 + self._uri.len() + 32,
);
// 追加 discriminator // 追加 discriminator
data.extend_from_slice(&[24, 30, 200, 40, 5, 28, 7, 119]); // discriminator data.extend_from_slice(&[24, 30, 200, 40, 5, 28, 7, 119]); // discriminator
// 添加 name 字符串长度和内容 // 添加 name 字符串长度和内容
data.extend_from_slice(&(self._name.len() as u32).to_le_bytes()); // 添加 name 长度 data.extend_from_slice(&(self._name.len() as u32).to_le_bytes()); // 添加 name 长度
data.extend_from_slice(self._name.as_bytes()); // 添加 name 内容 data.extend_from_slice(self._name.as_bytes()); // 添加 name 内容
// 添加 symbol 字符串长度和内容 // 添加 symbol 字符串长度和内容
data.extend_from_slice(&(self._symbol.len() as u32).to_le_bytes()); // 添加 symbol 长度 data.extend_from_slice(&(self._symbol.len() as u32).to_le_bytes()); // 添加 symbol 长度
data.extend_from_slice(self._symbol.as_bytes()); // 添加 symbol 内容 data.extend_from_slice(self._symbol.as_bytes()); // 添加 symbol 内容
// 添加 uri 字符串长度和内容 // 添加 uri 字符串长度和内容
data.extend_from_slice(&(self._uri.len() as u32).to_le_bytes()); // 添加 uri 长度 data.extend_from_slice(&(self._uri.len() as u32).to_le_bytes()); // 添加 uri 长度
data.extend_from_slice(self._uri.as_bytes()); // 添加 uri 内容 data.extend_from_slice(self._uri.as_bytes()); // 添加 uri 内容
data.extend_from_slice(&self._creator.to_bytes()); data.extend_from_slice(&self._creator.to_bytes());
@@ -233,7 +227,10 @@ pub fn create(payer: &Keypair, mint: &Keypair, args: Create) -> Instruction {
AccountMeta::new(payer.pubkey(), true), AccountMeta::new(payer.pubkey(), true),
AccountMeta::new_readonly(constants::pumpfun::accounts::SYSTEM_PROGRAM, false), AccountMeta::new_readonly(constants::pumpfun::accounts::SYSTEM_PROGRAM, false),
AccountMeta::new_readonly(constants::pumpfun::accounts::TOKEN_PROGRAM, false), AccountMeta::new_readonly(constants::pumpfun::accounts::TOKEN_PROGRAM, false),
AccountMeta::new_readonly(constants::pumpfun::accounts::ASSOCIATED_TOKEN_PROGRAM, false), AccountMeta::new_readonly(
constants::pumpfun::accounts::ASSOCIATED_TOKEN_PROGRAM,
false,
),
AccountMeta::new_readonly(constants::pumpfun::accounts::RENT, false), AccountMeta::new_readonly(constants::pumpfun::accounts::RENT, false),
AccountMeta::new_readonly(constants::pumpfun::accounts::EVENT_AUTHORITY, false), AccountMeta::new_readonly(constants::pumpfun::accounts::EVENT_AUTHORITY, false),
AccountMeta::new_readonly(constants::pumpfun::accounts::PUMPFUN, false), AccountMeta::new_readonly(constants::pumpfun::accounts::PUMPFUN, false),
+63 -124
View File
@@ -3,18 +3,19 @@ use solana_sdk::{instruction::Instruction, pubkey::Pubkey, signer::Signer};
use spl_associated_token_account::instruction::create_associated_token_account_idempotent; use spl_associated_token_account::instruction::create_associated_token_account_idempotent;
use crate::{ use crate::{
constants::pumpswap::{ constants::pumpswap::{accounts, BUY_DISCRIMINATOR, SELL_DISCRIMINATOR},
accounts, trade::DEFAULT_SLIPPAGE, BUY_DISCRIMINATOR, SELL_DISCRIMINATOR, constants::trade::trade::DEFAULT_SLIPPAGE,
}, trading::common::utils::{
trading::pumpswap::common::{ calculate_with_slippage_buy, calculate_with_slippage_sell, get_token_balance,
calculate_with_slippage_buy, calculate_with_slippage_sell, coin_creator_vault_ata,
coin_creator_vault_authority, find_pool, get_buy_token_amount, get_sell_sol_amount,
get_token_balance,
}, },
trading::core::{ trading::core::{
params::{BuyParams, PumpSwapParams, SellParams}, params::{BuyParams, PumpSwapParams, SellParams},
traits::InstructionBuilder, traits::InstructionBuilder,
}, },
trading::pumpswap::common::{
coin_creator_vault_ata, coin_creator_vault_authority, find_pool, get_buy_token_amount,
get_sell_sol_amount,
},
}; };
/// PumpSwap协议的指令构建器 /// PumpSwap协议的指令构建器
@@ -35,27 +36,11 @@ impl InstructionBuilder for PumpSwapInstructionBuilder {
} }
// 根据是否提供了账户信息来构建指令 // 根据是否提供了账户信息来构建指令
match ( match (&protocol_params.pool,) {
&protocol_params.pool, (Some(pool),) => {
&protocol_params.pool_base_token_account,
&protocol_params.pool_quote_token_account,
&protocol_params.user_base_token_account,
&protocol_params.user_quote_token_account,
) {
(
Some(pool),
Some(pool_base_token_account),
Some(pool_quote_token_account),
Some(user_base_token_account),
Some(user_quote_token_account),
) => {
self.build_buy_instructions_with_accounts( self.build_buy_instructions_with_accounts(
params, params,
*pool, *pool,
*pool_base_token_account,
*pool_quote_token_account,
*user_base_token_account,
*user_quote_token_account,
protocol_params.auto_handle_wsol, protocol_params.auto_handle_wsol,
) )
.await .await
@@ -73,29 +58,10 @@ impl InstructionBuilder for PumpSwapInstructionBuilder {
.ok_or_else(|| anyhow!("Invalid protocol params for PumpSwap"))?; .ok_or_else(|| anyhow!("Invalid protocol params for PumpSwap"))?;
// 根据是否提供了账户信息来构建指令 // 根据是否提供了账户信息来构建指令
match ( match (&protocol_params.pool,) {
&protocol_params.pool, (Some(pool),) => {
&protocol_params.pool_base_token_account, self.build_sell_instructions_with_accounts(params, *pool)
&protocol_params.pool_quote_token_account, .await
&protocol_params.user_base_token_account,
&protocol_params.user_quote_token_account,
) {
(
Some(pool),
Some(pool_base_token_account),
Some(pool_quote_token_account),
Some(user_base_token_account),
Some(user_quote_token_account),
) => {
self.build_sell_instructions_with_accounts(
params,
*pool,
*pool_base_token_account,
*pool_quote_token_account,
*user_base_token_account,
*user_quote_token_account,
)
.await
} }
_ => self.build_sell_instructions_auto_discover(params).await, _ => self.build_sell_instructions_auto_discover(params).await,
} }
@@ -115,41 +81,8 @@ impl PumpSwapInstructionBuilder {
// 查找池 // 查找池
let pool = find_pool(rpc.as_ref(), &params.mint).await?; let pool = find_pool(rpc.as_ref(), &params.mint).await?;
// 创建用户代币账户 self.build_buy_instructions_with_accounts(params, pool, true)
let user_base_token_account = spl_associated_token_account::get_associated_token_address( .await
&params.payer.pubkey(),
&params.mint,
);
let user_quote_token_account = spl_associated_token_account::get_associated_token_address(
&params.payer.pubkey(),
&accounts::WSOL_TOKEN_ACCOUNT,
);
// 获取池的代币账户
let pool_base_token_account =
spl_associated_token_account::get_associated_token_address_with_program_id(
&pool,
&params.mint,
&accounts::TOKEN_PROGRAM,
);
let pool_quote_token_account =
spl_associated_token_account::get_associated_token_address_with_program_id(
&pool,
&accounts::WSOL_TOKEN_ACCOUNT,
&accounts::TOKEN_PROGRAM,
);
self.build_buy_instructions_with_accounts(
params,
pool,
pool_base_token_account,
pool_quote_token_account,
user_base_token_account,
user_quote_token_account,
true,
)
.await
} }
/// 自动发现池和账户信息并构建卖出指令 /// 自动发现池和账户信息并构建卖出指令
@@ -165,6 +98,30 @@ impl PumpSwapInstructionBuilder {
// 查找池 // 查找池
let pool = find_pool(rpc.as_ref(), &params.mint).await?; let pool = find_pool(rpc.as_ref(), &params.mint).await?;
self.build_sell_instructions_with_accounts(params, pool)
.await
}
/// 使用提供的账户信息构建买入指令
async fn build_buy_instructions_with_accounts(
&self,
params: &BuyParams,
pool: Pubkey,
auto_handle_wsol: bool,
) -> Result<Vec<Instruction>> {
if params.rpc.is_none() {
return Err(anyhow!("RPC is not set"));
}
let rpc = params.rpc.as_ref().unwrap().clone();
// 计算预期的代币数量
let token_amount = get_buy_token_amount(rpc.as_ref(), &pool, params.amount_sol).await?;
// 计算滑点后的最大SOL数量
let max_sol_amount = calculate_with_slippage_buy(
params.amount_sol,
params.slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE),
);
// 创建用户代币账户 // 创建用户代币账户
let user_base_token_account = spl_associated_token_account::get_associated_token_address( let user_base_token_account = spl_associated_token_account::get_associated_token_address(
&params.payer.pubkey(), &params.payer.pubkey(),
@@ -190,41 +147,6 @@ impl PumpSwapInstructionBuilder {
&accounts::TOKEN_PROGRAM, &accounts::TOKEN_PROGRAM,
); );
self.build_sell_instructions_with_accounts(
params,
pool,
pool_base_token_account,
pool_quote_token_account,
user_base_token_account,
user_quote_token_account,
)
.await
}
/// 使用提供的账户信息构建买入指令
async fn build_buy_instructions_with_accounts(
&self,
params: &BuyParams,
pool: Pubkey,
pool_base_token_account: Pubkey,
pool_quote_token_account: Pubkey,
user_base_token_account: Pubkey,
user_quote_token_account: Pubkey,
auto_handle_wsol: bool,
) -> Result<Vec<Instruction>> {
if params.rpc.is_none() {
return Err(anyhow!("RPC is not set"));
}
let rpc = params.rpc.as_ref().unwrap().clone();
// 计算预期的代币数量
let token_amount = get_buy_token_amount(rpc.as_ref(), &pool, params.amount_sol).await?;
// 计算滑点后的最大SOL数量
let max_sol_amount = calculate_with_slippage_buy(
params.amount_sol,
params.slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE),
);
let mut instructions = vec![]; let mut instructions = vec![];
if auto_handle_wsol { if auto_handle_wsol {
@@ -328,10 +250,6 @@ impl PumpSwapInstructionBuilder {
&self, &self,
params: &SellParams, params: &SellParams,
pool: Pubkey, pool: Pubkey,
pool_base_token_account: Pubkey,
pool_quote_token_account: Pubkey,
user_base_token_account: Pubkey,
user_quote_token_account: Pubkey,
) -> Result<Vec<Instruction>> { ) -> Result<Vec<Instruction>> {
if params.rpc.is_none() { if params.rpc.is_none() {
return Err(anyhow!("RPC is not set")); return Err(anyhow!("RPC is not set"));
@@ -341,8 +259,8 @@ impl PumpSwapInstructionBuilder {
// 获取代币余额 // 获取代币余额
let mut amount = params.amount_token; let mut amount = params.amount_token;
if params.amount_token.is_none() { if params.amount_token.is_none() {
let (balance_u64, _) = let balance_u64 =
get_token_balance(rpc.as_ref(), params.payer.as_ref(), &params.mint).await?; get_token_balance(rpc.as_ref(), &params.payer.pubkey(), &params.mint).await?;
amount = Some(balance_u64); amount = Some(balance_u64);
} }
let amount = amount.unwrap_or(0); let amount = amount.unwrap_or(0);
@@ -363,6 +281,27 @@ impl PumpSwapInstructionBuilder {
let coin_creator_vault_ata = coin_creator_vault_ata(params.creator); let coin_creator_vault_ata = coin_creator_vault_ata(params.creator);
let coin_creator_vault_authority = coin_creator_vault_authority(params.creator); let coin_creator_vault_authority = coin_creator_vault_authority(params.creator);
let user_base_token_account = spl_associated_token_account::get_associated_token_address(
&params.payer.pubkey(),
&params.mint,
);
let user_quote_token_account = spl_associated_token_account::get_associated_token_address(
&params.payer.pubkey(),
&accounts::WSOL_TOKEN_ACCOUNT,
);
let pool_base_token_account =
spl_associated_token_account::get_associated_token_address_with_program_id(
&pool,
&params.mint,
&accounts::TOKEN_PROGRAM,
);
let pool_quote_token_account =
spl_associated_token_account::get_associated_token_address_with_program_id(
&pool,
&accounts::WSOL_TOKEN_ACCOUNT,
&accounts::TOKEN_PROGRAM,
);
let mut instructions = vec![]; let mut instructions = vec![];
// 插入wsol // 插入wsol
+324 -598
View File
@@ -2,34 +2,28 @@ pub mod common;
pub mod constants; pub mod constants;
pub mod instruction; pub mod instruction;
pub mod protos; pub mod protos;
pub mod swqos;
pub mod streaming; pub mod streaming;
pub mod swqos;
pub mod trading; pub mod trading;
pub mod utils;
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::traits::ProtocolParams;
use crate::trading::factory::TradingProtocol;
use crate::trading::BuyParams;
use crate::trading::SellParams;
use crate::trading::TradeFactory;
use common::{PriorityFee, SolanaRpcClient, TradeConfig};
use rustls::crypto::{ring::default_provider, CryptoProvider};
use solana_sdk::hash::Hash;
use solana_sdk::{pubkey::Pubkey, signature::Keypair};
use std::sync::Arc; use std::sync::Arc;
use std::sync::Mutex; use std::sync::Mutex;
use rustls::crypto::{ring::default_provider, CryptoProvider};
use solana_sdk::{
pubkey::Pubkey,
signature::{Keypair, Signer},
};
use swqos::SwqosClient; use swqos::SwqosClient;
use common::{PriorityFee, SolanaRpcClient, TradeConfig};
use constants::trade_type::COPY_BUY;
use crate::streaming::event_parser::protocols::pumpfun::PumpFunTradeEvent;
use crate::swqos::SwqosConfig;
use crate::trading::core::params::PumpFunParams;
use crate::trading::core::params::PumpFunSellParams;
use crate::trading::core::params::PumpSwapParams;
use crate::trading::core::params::BonkParams;
use crate::trading::BuyWithTipParams;
use crate::trading::SellParams;
use crate::trading::SellWithTipParams;
pub struct SolanaTrade { pub struct SolanaTrade {
pub payer: Arc<Keypair>, pub payer: Arc<Keypair>,
pub rpc: Arc<SolanaRpcClient>, pub rpc: Arc<SolanaRpcClient>,
@@ -107,11 +101,95 @@ impl SolanaTrade {
.clone() .clone()
} }
pub async fn buy_use_buy_params( /// Execute a buy order for a specified token
///
/// # Arguments
///
/// * `mint` - The public key of the token mint to buy
/// * `creator` - Optional creator public key for the token (defaults to Pubkey::default() if None)
/// * `amount_sol` - Amount of SOL to spend on the purchase (in lamports)
/// * `slippage_basis_points` - Optional slippage tolerance in basis points (e.g., 100 = 1%)
/// * `recent_blockhash` - Recent blockhash for transaction validity
/// * `custom_buy_tip_fee` - Optional custom tip fee for priority processing (in SOL)
/// * `with_tip` - Whether to include tip for MEV protection and priority processing
/// * `protocol` - Trading protocol to use (PumpFun, PumpSwap, or Bonk)
/// * `protocol_params` - Optional protocol-specific parameters (uses defaults if None)
///
/// # Returns
///
/// Returns `Ok(())` if the buy order is successfully executed, or an error if the transaction fails.
///
/// # Errors
///
/// This function will return an error if:
/// - Invalid protocol parameters are provided
/// - The transaction fails to execute
/// - Network or RPC errors occur
/// - Insufficient SOL balance for the purchase
///
/// # Example
///
/// ```rust
/// use solana_sdk::pubkey::Pubkey;
/// use solana_sdk::hash::Hash;
/// use crate::trading::factory::TradingProtocol;
///
/// let mint = Pubkey::new_unique();
/// let amount_sol = 1_000_000_000; // 1 SOL in lamports
/// let slippage = Some(500); // 5% slippage
/// let recent_blockhash = Hash::default();
///
/// solana_trade.buy(
/// mint,
/// None,
/// amount_sol,
/// slippage,
/// recent_blockhash,
/// None,
/// true,
/// TradingProtocol::PumpFun,
/// None,
/// ).await?;
/// ```
pub async fn buy(
&self, &self,
buy_params: BuyWithTipParams, mint: Pubkey,
creator: Option<Pubkey>,
amount_sol: u64,
slippage_basis_points: Option<u64>,
recent_blockhash: Hash,
custom_buy_tip_fee: Option<f64>, custom_buy_tip_fee: Option<f64>,
with_tip: bool,
protocol: TradingProtocol,
protocol_params: Option<Box<dyn ProtocolParams>>,
) -> Result<(), anyhow::Error> { ) -> Result<(), anyhow::Error> {
let executor = TradeFactory::create_executor(protocol.clone());
let protocol_params = if let Some(params) = protocol_params {
params
} else {
match protocol {
TradingProtocol::PumpFun => {
Box::new(PumpFunParams::default()) as Box<dyn ProtocolParams>
}
TradingProtocol::PumpSwap => {
Box::new(PumpSwapParams::default()) as Box<dyn ProtocolParams>
}
TradingProtocol::Bonk => Box::new(BonkParams::default()) as Box<dyn ProtocolParams>,
}
};
let buy_params = BuyParams {
rpc: Some(self.rpc.clone()),
payer: self.payer.clone(),
mint: mint,
creator: creator.unwrap_or(Pubkey::default()),
amount_sol: amount_sol,
slippage_basis_points: slippage_basis_points,
priority_fee: self.trade_config.priority_fee.clone(),
lookup_table_key: self.trade_config.lookup_table_key,
recent_blockhash,
data_size_limit: 0,
protocol_params: protocol_params.clone(),
};
let mut priority_fee = buy_params.priority_fee.clone(); let mut priority_fee = buy_params.priority_fee.clone();
if custom_buy_tip_fee.is_some() { if custom_buy_tip_fee.is_some() {
priority_fee.buy_tip_fee = custom_buy_tip_fee.unwrap(); priority_fee.buy_tip_fee = custom_buy_tip_fee.unwrap();
@@ -122,85 +200,126 @@ impl SolanaTrade {
custom_buy_tip_fee.unwrap(), custom_buy_tip_fee.unwrap(),
]; ];
} }
let mint = buy_params.mint; let buy_with_tip_params = buy_params.clone().with_tip(self.swqos_clients.clone());
let creator = buy_params.creator;
let buy_sol_cost = buy_params.amount_sol; // Validate protocol params
let slippage_basis_points = buy_params.slippage_basis_points; let is_valid_params = match protocol {
let recent_blockhash = buy_params.recent_blockhash; TradingProtocol::PumpFun => protocol_params
if let Some(protocol_params) = buy_params .as_any()
.protocol_params .downcast_ref::<PumpFunParams>()
.as_any() .is_some(),
.downcast_ref::<PumpFunParams>() TradingProtocol::PumpSwap => protocol_params
{ .as_any()
trading::pumpfun::buy::buy( .downcast_ref::<PumpSwapParams>()
self.rpc.clone(), .is_some(),
self.payer.clone(), TradingProtocol::Bonk => protocol_params
mint, .as_any()
creator, .downcast_ref::<BonkParams>()
buy_sol_cost, .is_some(),
slippage_basis_points, };
self.priority_fee.clone(),
self.trade_config.lookup_table_key, if !is_valid_params {
recent_blockhash,
protocol_params.bonding_curve.clone(),
COPY_BUY.to_string(),
)
.await
} else if let Some(protocol_params) = buy_params
.protocol_params
.as_any()
.downcast_ref::<PumpSwapParams>()
{
trading::pumpswap::buy::buy(
self.rpc.clone(),
self.payer.clone(),
mint,
creator,
buy_sol_cost,
slippage_basis_points,
self.priority_fee.clone(),
self.trade_config.lookup_table_key,
recent_blockhash,
protocol_params.pool.clone(),
protocol_params.pool_base_token_account.clone(),
protocol_params.pool_quote_token_account.clone(),
protocol_params.user_base_token_account.clone(),
protocol_params.user_quote_token_account.clone(),
protocol_params.auto_handle_wsol,
)
.await
} else if let Some(protocol_params) = buy_params
.protocol_params
.as_any()
.downcast_ref::<BonkParams>()
{
trading::bonk::buy::buy(
self.rpc.clone(),
self.payer.clone(),
mint,
protocol_params.virtual_base.unwrap_or(0),
protocol_params.virtual_quote.unwrap_or(0),
protocol_params.real_base_before.unwrap_or(0),
protocol_params.real_quote_before.unwrap_or(0),
buy_sol_cost,
slippage_basis_points,
priority_fee.clone(),
self.trade_config.lookup_table_key,
recent_blockhash,
protocol_params.auto_handle_wsol,
)
.await
} else {
return Err(anyhow::anyhow!("Invalid protocol params for Trade")); return Err(anyhow::anyhow!("Invalid protocol params for Trade"));
} }
// Execute buy based on tip preference
if with_tip {
executor.buy_with_tip(buy_with_tip_params).await
} else {
executor.buy(buy_params).await
}
} }
pub async fn buy_with_tip_use_buy_params( /// Execute a sell order for a specified token
///
/// # Arguments
///
/// * `mint` - The public key of the token mint to sell
/// * `creator` - Optional creator public key for the token (defaults to Pubkey::default() if None)
/// * `amount_token` - Amount of tokens to sell (in smallest token units)
/// * `slippage_basis_points` - Optional slippage tolerance in basis points (e.g., 100 = 1%)
/// * `recent_blockhash` - Recent blockhash for transaction validity
/// * `custom_buy_tip_fee` - Optional custom tip fee for priority processing (in SOL)
/// * `with_tip` - Whether to include tip for MEV protection and priority processing
/// * `protocol` - Trading protocol to use (PumpFun, PumpSwap, or Bonk)
/// * `protocol_params` - Optional protocol-specific parameters (uses defaults if None)
///
/// # Returns
///
/// Returns `Ok(())` if the sell order is successfully executed, or an error if the transaction fails.
///
/// # Errors
///
/// This function will return an error if:
/// - Invalid protocol parameters are provided
/// - The transaction fails to execute
/// - Network or RPC errors occur
/// - Insufficient token balance for the sale
/// - Token account doesn't exist or is not properly initialized
///
/// # Example
///
/// ```rust
/// use solana_sdk::pubkey::Pubkey;
/// use solana_sdk::hash::Hash;
/// use crate::trading::factory::TradingProtocol;
///
/// let mint = Pubkey::new_unique();
/// let amount_token = 1_000_000; // Amount of tokens to sell
/// let slippage = Some(500); // 5% slippage
/// let recent_blockhash = Hash::default();
///
/// solana_trade.sell(
/// mint,
/// None,
/// amount_token,
/// slippage,
/// recent_blockhash,
/// None,
/// true,
/// TradingProtocol::PumpFun,
/// None,
/// ).await?;
/// ```
pub async fn sell(
&self, &self,
buy_params: BuyWithTipParams, mint: Pubkey,
creator: Option<Pubkey>,
amount_token: u64,
slippage_basis_points: Option<u64>,
recent_blockhash: Hash,
custom_buy_tip_fee: Option<f64>, custom_buy_tip_fee: Option<f64>,
with_tip: bool,
protocol: TradingProtocol,
protocol_params: Option<Box<dyn ProtocolParams>>,
) -> Result<(), anyhow::Error> { ) -> Result<(), anyhow::Error> {
let mut priority_fee = buy_params.priority_fee.clone(); let executor = TradeFactory::create_executor(protocol.clone());
let protocol_params = if let Some(params) = protocol_params {
params
} else {
match protocol {
TradingProtocol::PumpFun => {
Box::new(PumpFunParams::default()) as Box<dyn ProtocolParams>
}
TradingProtocol::PumpSwap => {
Box::new(PumpSwapParams::default()) as Box<dyn ProtocolParams>
}
TradingProtocol::Bonk => Box::new(BonkParams::default()) as Box<dyn ProtocolParams>,
}
};
let sell_params = SellParams {
rpc: Some(self.rpc.clone()),
payer: self.payer.clone(),
mint: mint,
creator: creator.unwrap_or(Pubkey::default()),
amount_token: Some(amount_token),
slippage_basis_points: slippage_basis_points,
priority_fee: self.trade_config.priority_fee.clone(),
lookup_table_key: self.trade_config.lookup_table_key,
recent_blockhash,
protocol_params: protocol_params.clone(),
};
let mut priority_fee = sell_params.priority_fee.clone();
if custom_buy_tip_fee.is_some() { if custom_buy_tip_fee.is_some() {
priority_fee.buy_tip_fee = custom_buy_tip_fee.unwrap(); priority_fee.buy_tip_fee = custom_buy_tip_fee.unwrap();
priority_fee.buy_tip_fees = vec![ priority_fee.buy_tip_fees = vec![
@@ -210,516 +329,123 @@ impl SolanaTrade {
custom_buy_tip_fee.unwrap(), custom_buy_tip_fee.unwrap(),
]; ];
} }
let mint = buy_params.mint; let sell_with_tip_params = sell_params.clone().with_tip(self.swqos_clients.clone());
let creator = buy_params.creator;
let buy_sol_cost = buy_params.amount_sol; // Validate protocol params
let slippage_basis_points = buy_params.slippage_basis_points; let is_valid_params = match protocol {
let recent_blockhash = buy_params.recent_blockhash; TradingProtocol::PumpFun => protocol_params
if let Some(protocol_params) = buy_params .as_any()
.protocol_params .downcast_ref::<PumpFunParams>()
.as_any() .is_some(),
.downcast_ref::<PumpFunParams>() TradingProtocol::PumpSwap => protocol_params
{ .as_any()
trading::pumpfun::buy::buy_with_tip( .downcast_ref::<PumpSwapParams>()
self.swqos_clients.clone(), .is_some(),
self.payer.clone(), TradingProtocol::Bonk => protocol_params
mint, .as_any()
creator, .downcast_ref::<BonkParams>()
buy_sol_cost, .is_some(),
slippage_basis_points, };
priority_fee.clone(),
self.trade_config.lookup_table_key, if !is_valid_params {
recent_blockhash,
protocol_params.bonding_curve.clone(),
COPY_BUY.to_string(),
)
.await
} else if let Some(protocol_params) = buy_params
.protocol_params
.as_any()
.downcast_ref::<PumpSwapParams>()
{
trading::pumpswap::buy::buy_with_tip(
self.rpc.clone(),
self.swqos_clients.clone(),
self.payer.clone(),
mint,
creator,
buy_sol_cost,
slippage_basis_points,
priority_fee.clone(),
self.trade_config.lookup_table_key,
recent_blockhash,
protocol_params.pool.clone(),
protocol_params.pool_base_token_account.clone(),
protocol_params.pool_quote_token_account.clone(),
protocol_params.user_base_token_account.clone(),
protocol_params.user_quote_token_account.clone(),
protocol_params.auto_handle_wsol,
)
.await
} else if let Some(protocol_params) = buy_params
.protocol_params
.as_any()
.downcast_ref::<BonkParams>()
{
trading::bonk::buy::buy(
self.rpc.clone(),
self.payer.clone(),
mint,
protocol_params.virtual_base.unwrap_or(0),
protocol_params.virtual_quote.unwrap_or(0),
protocol_params.real_base_before.unwrap_or(0),
protocol_params.real_quote_before.unwrap_or(0),
buy_sol_cost,
slippage_basis_points,
priority_fee.clone(),
self.trade_config.lookup_table_key,
recent_blockhash,
protocol_params.auto_handle_wsol,
)
.await
} else {
return Err(anyhow::anyhow!("Invalid protocol params for Trade")); return Err(anyhow::anyhow!("Invalid protocol params for Trade"));
} }
// Execute sell based on tip preference
if with_tip {
executor.sell_with_tip(sell_with_tip_params).await
} else {
executor.sell(sell_params).await
}
} }
/// Sell tokens by percentage /// Execute a sell order for a percentage of the specified token amount
pub async fn sell_by_percent_use_sell_params( ///
/// This is a convenience function that calculates the exact amount to sell based on
/// a percentage of the total token amount and then calls the `sell` function.
///
/// # Arguments
///
/// * `mint` - The public key of the token mint to sell
/// * `creator` - Optional creator public key for the token (defaults to Pubkey::default() if None)
/// * `amount_token` - Total amount of tokens available (in smallest token units)
/// * `percent` - Percentage of tokens to sell (1-100, where 100 = 100%)
/// * `slippage_basis_points` - Optional slippage tolerance in basis points (e.g., 100 = 1%)
/// * `recent_blockhash` - Recent blockhash for transaction validity
/// * `custom_buy_tip_fee` - Optional custom tip fee for priority processing (in SOL)
/// * `with_tip` - Whether to include tip for MEV protection and priority processing
/// * `protocol` - Trading protocol to use (PumpFun, PumpSwap, or Bonk)
/// * `protocol_params` - Optional protocol-specific parameters (uses defaults if None)
///
/// # Returns
///
/// Returns `Ok(())` if the sell order is successfully executed, or an error if the transaction fails.
///
/// # Errors
///
/// This function will return an error if:
/// - `percent` is 0 or greater than 100
/// - Invalid protocol parameters are provided
/// - The transaction fails to execute
/// - Network or RPC errors occur
/// - Insufficient token balance for the calculated sale amount
/// - Token account doesn't exist or is not properly initialized
///
/// # Example
///
/// ```rust
/// use solana_sdk::pubkey::Pubkey;
/// use solana_sdk::hash::Hash;
/// use crate::trading::factory::TradingProtocol;
///
/// let mint = Pubkey::new_unique();
/// let total_tokens = 10_000_000; // Total tokens available
/// let percent = 50; // Sell 50% of tokens
/// let slippage = Some(500); // 5% slippage
/// let recent_blockhash = Hash::default();
///
/// // This will sell 5_000_000 tokens (50% of 10_000_000)
/// solana_trade.sell_by_percent(
/// mint,
/// None,
/// total_tokens,
/// percent,
/// slippage,
/// recent_blockhash,
/// None,
/// true,
/// TradingProtocol::PumpFun,
/// None,
/// ).await?;
/// ```
pub async fn sell_by_percent(
&self, &self,
sell_params: SellParams, mint: Pubkey,
creator: Option<Pubkey>,
amount_token: u64,
percent: u64, percent: u64,
slippage_basis_points: Option<u64>,
recent_blockhash: Hash,
custom_buy_tip_fee: Option<f64>,
with_tip: bool,
protocol: TradingProtocol,
protocol_params: Option<Box<dyn ProtocolParams>>,
) -> Result<(), anyhow::Error> { ) -> Result<(), anyhow::Error> {
let mint = sell_params.mint; if percent == 0 || percent > 100 {
let creator = sell_params.creator; return Err(anyhow::anyhow!("Percentage must be between 1 and 100"));
let amount_token = sell_params.amount_token;
let recent_blockhash = sell_params.recent_blockhash;
if let Some(_) = sell_params
.protocol_params
.as_any()
.downcast_ref::<PumpFunSellParams>()
{
trading::pumpfun::sell::sell_by_percent(
self.rpc.clone(),
self.payer.clone(),
mint.clone(),
creator,
percent,
amount_token.unwrap_or(0),
self.priority_fee.clone(),
self.trade_config.lookup_table_key,
recent_blockhash,
)
.await
} else if let Some(protocol_params) = sell_params
.protocol_params
.as_any()
.downcast_ref::<PumpSwapParams>()
{
trading::pumpswap::sell::sell_by_percent(
self.rpc.clone(),
self.payer.clone(),
mint.clone(),
creator,
percent,
None,
self.priority_fee.clone(),
self.trade_config.lookup_table_key,
recent_blockhash,
protocol_params.pool.clone(),
protocol_params.pool_base_token_account.clone(),
protocol_params.pool_quote_token_account.clone(),
protocol_params.user_base_token_account.clone(),
protocol_params.user_quote_token_account.clone(),
)
.await
} else if let Some(protocol_params) = sell_params
.protocol_params
.as_any()
.downcast_ref::<BonkParams>()
{
trading::bonk::sell::sell_by_percent(
self.rpc.clone(),
self.payer.clone(),
mint.clone(),
protocol_params.virtual_base.unwrap_or(0),
protocol_params.virtual_quote.unwrap_or(0),
protocol_params.real_base_before.unwrap_or(0),
protocol_params.real_quote_before.unwrap_or(0),
percent,
None,
self.priority_fee.clone(),
self.trade_config.lookup_table_key,
recent_blockhash,
)
.await
} else {
return Err(anyhow::anyhow!("Invalid protocol params for Trade"));
} }
} let amount = amount_token * percent / 100;
self.sell(
/// Sell tokens by amount mint,
pub async fn sell_by_amount_use_sell_params( creator,
&self, amount,
sell_params: SellParams, slippage_basis_points,
) -> Result<(), anyhow::Error> { recent_blockhash,
let mint = sell_params.mint; custom_buy_tip_fee,
let creator = sell_params.creator; with_tip,
let amount = sell_params.amount_token; protocol,
let recent_blockhash = sell_params.recent_blockhash; protocol_params,
if let Some(_) = sell_params )
.protocol_params .await
.as_any()
.downcast_ref::<PumpFunSellParams>()
{
trading::pumpfun::sell::sell_by_amount(
self.rpc.clone(),
self.payer.clone(),
mint.clone(),
creator,
amount.unwrap_or(0),
self.priority_fee.clone(),
self.trade_config.lookup_table_key,
recent_blockhash,
)
.await
} else if let Some(protocol_params) = sell_params
.protocol_params
.as_any()
.downcast_ref::<PumpSwapParams>()
{
trading::pumpswap::sell::sell_by_amount(
self.rpc.clone(),
self.payer.clone(),
mint.clone(),
creator,
amount.unwrap_or(0),
None,
self.priority_fee.clone(),
self.trade_config.lookup_table_key,
recent_blockhash,
protocol_params.pool.clone(),
protocol_params.pool_base_token_account.clone(),
protocol_params.pool_quote_token_account.clone(),
protocol_params.user_base_token_account.clone(),
protocol_params.user_quote_token_account.clone(),
)
.await
} else if let Some(protocol_params) = sell_params
.protocol_params
.as_any()
.downcast_ref::<BonkParams>()
{
trading::bonk::sell::sell_by_amount(
self.rpc.clone(),
self.payer.clone(),
mint.clone(),
protocol_params.virtual_base.unwrap_or(0),
protocol_params.virtual_quote.unwrap_or(0),
protocol_params.real_base_before.unwrap_or(0),
protocol_params.real_quote_before.unwrap_or(0),
amount.unwrap_or(0),
None,
self.priority_fee.clone(),
self.trade_config.lookup_table_key,
recent_blockhash,
)
.await
} else {
Err(anyhow::anyhow!("Invalid protocol params for Trade"))
}
}
pub async fn sell_by_percent_with_tip_use_sell_params(
&self,
sell_params: SellWithTipParams,
percent: u64,
) -> Result<(), anyhow::Error> {
let mint = sell_params.mint;
let creator = sell_params.creator;
let amount_token = sell_params.amount_token;
let recent_blockhash = sell_params.recent_blockhash;
if let Some(_) = sell_params
.protocol_params
.as_any()
.downcast_ref::<PumpFunSellParams>()
{
trading::pumpfun::sell::sell_by_percent_with_tip(
self.rpc.clone(),
self.swqos_clients.clone(),
self.payer.clone(),
mint,
creator,
percent,
amount_token.unwrap_or(0),
self.priority_fee.clone(),
self.trade_config.lookup_table_key,
recent_blockhash,
)
.await
} else if let Some(protocol_params) = sell_params
.protocol_params
.as_any()
.downcast_ref::<PumpSwapParams>()
{
trading::pumpswap::sell::sell_by_percent_with_tip(
self.rpc.clone(),
self.swqos_clients.clone(),
self.payer.clone(),
mint,
creator,
percent,
sell_params.slippage_basis_points,
self.priority_fee.clone(),
self.trade_config.lookup_table_key,
recent_blockhash,
protocol_params.pool.clone(),
protocol_params.pool_base_token_account.clone(),
protocol_params.pool_quote_token_account.clone(),
protocol_params.user_base_token_account.clone(),
protocol_params.user_quote_token_account.clone(),
)
.await
} else if let Some(protocol_params) = sell_params
.protocol_params
.as_any()
.downcast_ref::<BonkParams>()
{
trading::bonk::sell::sell_by_percent_with_tip(
self.rpc.clone(),
self.swqos_clients.clone(),
self.payer.clone(),
mint,
protocol_params.virtual_base.unwrap_or(0),
protocol_params.virtual_quote.unwrap_or(0),
protocol_params.real_base_before.unwrap_or(0),
protocol_params.real_quote_before.unwrap_or(0),
percent,
sell_params.slippage_basis_points,
self.priority_fee.clone(),
self.trade_config.lookup_table_key,
recent_blockhash,
)
.await
} else {
Err(anyhow::anyhow!("Invalid protocol params for Trade"))
}
}
pub async fn sell_by_amount_with_tip_use_sell_params(
&self,
sell_params: SellWithTipParams,
) -> Result<(), anyhow::Error> {
let mint = sell_params.mint;
let creator = sell_params.creator;
let amount = sell_params.amount_token;
let recent_blockhash = sell_params.recent_blockhash;
if let Some(_) = sell_params
.protocol_params
.as_any()
.downcast_ref::<PumpFunSellParams>()
{
trading::pumpfun::sell::sell_by_amount_with_tip(
self.rpc.clone(),
self.swqos_clients.clone(),
self.payer.clone(),
mint,
creator,
amount.unwrap_or(0),
self.priority_fee.clone(),
self.trade_config.lookup_table_key,
recent_blockhash,
)
.await
} else if let Some(protocol_params) = sell_params
.protocol_params
.as_any()
.downcast_ref::<PumpSwapParams>()
{
trading::pumpswap::sell::sell_by_amount_with_tip(
self.rpc.clone(),
self.swqos_clients.clone(),
self.payer.clone(),
mint,
creator,
amount.unwrap_or(0),
sell_params.slippage_basis_points,
self.priority_fee.clone(),
self.trade_config.lookup_table_key,
recent_blockhash,
protocol_params.pool.clone(),
protocol_params.pool_base_token_account.clone(),
protocol_params.pool_quote_token_account.clone(),
protocol_params.user_base_token_account.clone(),
protocol_params.user_quote_token_account.clone(),
)
.await
} else if let Some(protocol_params) = sell_params
.protocol_params
.as_any()
.downcast_ref::<BonkParams>()
{
trading::bonk::sell::sell_by_amount_with_tip(
self.rpc.clone(),
self.swqos_clients.clone(),
self.payer.clone(),
mint,
protocol_params.virtual_base.unwrap_or(0),
protocol_params.virtual_quote.unwrap_or(0),
protocol_params.real_base_before.unwrap_or(0),
protocol_params.real_quote_before.unwrap_or(0),
amount.unwrap_or(0),
sell_params.slippage_basis_points,
self.priority_fee.clone(),
self.trade_config.lookup_table_key,
recent_blockhash,
)
.await
} else {
Err(anyhow::anyhow!("Invalid protocol params for Trade"))
}
}
#[inline]
pub async fn get_sol_balance(&self, payer: &Pubkey) -> Result<u64, anyhow::Error> {
trading::pumpfun::common::get_sol_balance(&self.rpc, payer).await
}
#[inline]
pub async fn get_payer_sol_balance(&self) -> Result<u64, anyhow::Error> {
trading::pumpfun::common::get_sol_balance(&self.rpc, &self.payer.pubkey()).await
}
#[inline]
pub async fn get_token_balance(
&self,
payer: &Pubkey,
mint: &Pubkey,
) -> Result<u64, anyhow::Error> {
println!(
"get_token_balance payer: {}, mint: {}, rpc_url: {}",
payer, mint, self.trade_config.rpc_url
);
trading::pumpfun::common::get_token_balance(&self.rpc, payer, mint).await
}
#[inline]
pub async fn get_payer_token_balance(&self, mint: &Pubkey) -> Result<u64, anyhow::Error> {
trading::pumpfun::common::get_token_balance(&self.rpc, &self.payer.pubkey(), mint).await
}
#[inline]
pub fn get_payer_pubkey(&self) -> Pubkey {
self.payer.pubkey()
}
#[inline]
pub fn get_payer(&self) -> &Keypair {
self.payer.as_ref()
}
#[inline]
pub fn get_token_price(&self, virtual_sol_reserves: u64, virtual_token_reserves: u64) -> f64 {
trading::pumpfun::common::get_token_price(virtual_sol_reserves, virtual_token_reserves)
}
#[inline]
pub fn get_buy_price(&self, amount: u64, trade_info: &PumpFunTradeEvent) -> u64 {
trading::pumpfun::common::get_buy_price(amount, trade_info)
}
#[inline]
pub async fn transfer_sol(
&self,
payer: &Keypair,
receive_wallet: &Pubkey,
amount: u64,
) -> Result<(), anyhow::Error> {
trading::pumpfun::common::transfer_sol(&self.rpc, payer, receive_wallet, amount).await
}
#[inline]
pub async fn close_token_account(&self, mint: &Pubkey) -> Result<(), anyhow::Error> {
trading::pumpfun::common::close_token_account(&self.rpc, self.payer.as_ref(), mint).await
}
#[inline]
pub async fn get_current_price(&self, mint: &Pubkey) -> Result<f64, anyhow::Error> {
let (bonding_curve, _) =
trading::pumpfun::common::get_bonding_curve_account_v2(&self.rpc, mint).await?;
let virtual_sol_reserves = bonding_curve.virtual_sol_reserves;
let virtual_token_reserves = bonding_curve.virtual_token_reserves;
Ok(trading::pumpfun::common::get_token_price(
virtual_sol_reserves,
virtual_token_reserves,
))
}
#[inline]
pub async fn get_real_sol_reserves(&self, mint: &Pubkey) -> Result<u64, anyhow::Error> {
let (bonding_curve, _) =
trading::pumpfun::common::get_bonding_curve_account_v2(&self.rpc, mint).await?;
let actual_sol_reserves = bonding_curve.real_sol_reserves;
Ok(actual_sol_reserves)
}
#[inline]
pub async fn get_creator(&self, mint: &Pubkey) -> Result<Pubkey, anyhow::Error> {
let (bonding_curve, _) =
trading::pumpfun::common::get_bonding_curve_account_v2(&self.rpc, mint).await?;
let creator = bonding_curve.creator;
Ok(creator)
}
#[inline]
pub async fn get_current_price_with_pumpswap(
&self,
pool_address: &Pubkey,
) -> Result<f64, anyhow::Error> {
let pool = trading::pumpswap::pool::Pool::fetch(&self.rpc, pool_address).await?;
let (base_amount, quote_amount) = pool.get_token_balances(&self.rpc).await?;
// Calculate price using constant product formula (x * y = k)
// Price = quote_amount / base_amount
if base_amount == 0 {
return Err(anyhow::anyhow!(
"Base amount is zero, cannot calculate price"
));
}
let price = quote_amount as f64 / base_amount as f64;
Ok(price)
}
#[inline]
pub async fn get_real_sol_reserves_with_pumpswap(
&self,
pool_address: &Pubkey,
) -> Result<u64, anyhow::Error> {
let pool = trading::pumpswap::pool::Pool::fetch(&self.rpc, pool_address).await?;
let (_, quote_amount) = pool.get_token_balances(&self.rpc).await?;
Ok(quote_amount)
}
#[inline]
pub async fn get_payer_token_balance_with_pumpswap(
&self,
pool_address: &Pubkey,
) -> Result<u64, anyhow::Error> {
let pool = trading::pumpswap::pool::Pool::fetch(&self.rpc, pool_address).await?;
let (base_amount, _) = pool.get_token_balances(&self.rpc).await?;
Ok(base_amount)
} }
} }
+125 -184
View File
@@ -2,40 +2,50 @@ use std::{str::FromStr, sync::Arc};
use sol_trade_sdk::{ use sol_trade_sdk::{
common::{bonding_curve::BondingCurveAccount, AnyResult, PriorityFee, TradeConfig}, common::{bonding_curve::BondingCurveAccount, AnyResult, PriorityFee, TradeConfig},
constants::{pumpfun::global_constants::TOKEN_TOTAL_SUPPLY, trade_type}, constants::pumpfun::global_constants::TOKEN_TOTAL_SUPPLY,
streaming::event_parser::{
protocols::{
bonk::{BonkPoolCreateEvent, BonkTradeEvent}, pumpfun::{PumpFunCreateTokenEvent, PumpFunTradeEvent}, pumpswap::{
PumpSwapBuyEvent, PumpSwapCreatePoolEvent, PumpSwapDepositEvent, PumpSwapSellEvent,
PumpSwapWithdrawEvent,
}
},
Protocol, UnifiedEvent,
},
streaming::{ShredStreamGrpc, YellowstoneGrpc},
match_event, match_event,
streaming::{
event_parser::{
protocols::{
bonk::{BonkPoolCreateEvent, BonkTradeEvent},
pumpfun::{PumpFunCreateTokenEvent, PumpFunTradeEvent},
pumpswap::{
PumpSwapBuyEvent, PumpSwapCreatePoolEvent, PumpSwapDepositEvent,
PumpSwapSellEvent, PumpSwapWithdrawEvent,
},
},
Protocol, UnifiedEvent,
},
ShredStreamGrpc, YellowstoneGrpc,
},
swqos::{SwqosConfig, SwqosRegion}, swqos::{SwqosConfig, SwqosRegion},
trading::{ trading::{
core::params::{BonkParams, PumpFunParams, PumpFunSellParams, PumpSwapParams}, pumpfun::common::get_bonding_curve_account_v2, BuyParams, SellParams core::params::PumpFunParams, factory::TradingProtocol,
pumpfun::common::get_bonding_curve_account_v2,
}, },
SolanaTrade, SolanaTrade,
}; };
use solana_client::rpc_client::RpcClient;
use solana_sdk::{commitment_config::CommitmentConfig, pubkey::Pubkey, signature::Keypair}; use solana_sdk::{commitment_config::CommitmentConfig, pubkey::Pubkey, signature::Keypair};
#[tokio::main] #[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> { async fn main() -> Result<(), Box<dyn std::error::Error>> {
test_create_solana_trade_client().await?;
test_pumpfun().await?; test_pumpfun().await?;
// test_pumpswap().await?; test_pumpswap().await?;
// test_bonk().await?; test_bonk().await?;
// test_grpc().await?; test_grpc().await?;
// test_shreds().await?; test_shreds().await?;
Ok(()) Ok(())
} }
async fn test_pumpfun() -> AnyResult<()> { /// 创建 SolanaTrade 客户端的示例
async fn test_create_solana_trade_client() -> AnyResult<SolanaTrade> {
println!("Creating SolanaTrade client...");
let payer = Keypair::new(); let payer = Keypair::new();
let rpc_url = "https://mainnet.helius-rpc.com/?api-key=xxxxxx".to_string(); let rpc_url = "https://mainnet.helius-rpc.com/?api-key=xxxxxx".to_string();
// 配置各种 SWQOS 服务
let swqos_configs = vec![ let swqos_configs = vec![
SwqosConfig::Jito(SwqosRegion::Frankfurt), SwqosConfig::Jito(SwqosRegion::Frankfurt),
SwqosConfig::NextBlock("your api_token".to_string(), SwqosRegion::Frankfurt), SwqosConfig::NextBlock("your api_token".to_string(), SwqosRegion::Frankfurt),
@@ -44,7 +54,8 @@ async fn test_pumpfun() -> AnyResult<()> {
SwqosConfig::Temporal("your api_token".to_string(), SwqosRegion::Frankfurt), SwqosConfig::Temporal("your api_token".to_string(), SwqosRegion::Frankfurt),
SwqosConfig::Default(rpc_url.clone()), SwqosConfig::Default(rpc_url.clone()),
]; ];
// Define cluster configuration
// 定义交易配置
let trade_config = TradeConfig { let trade_config = TradeConfig {
rpc_url: rpc_url.clone(), rpc_url: rpc_url.clone(),
commitment: CommitmentConfig::confirmed(), commitment: CommitmentConfig::confirmed(),
@@ -52,13 +63,23 @@ async fn test_pumpfun() -> AnyResult<()> {
swqos_configs, swqos_configs,
lookup_table_key: None, lookup_table_key: None,
}; };
let solana_trade_client = SolanaTrade::new(Arc::new(payer), trade_config).await; let solana_trade_client = SolanaTrade::new(Arc::new(payer), trade_config).await;
println!("SolanaTrade client created successfully!");
Ok(solana_trade_client)
}
async fn test_pumpfun() -> AnyResult<()> {
println!("Testing PumpFun trading...");
let solana_trade_client = test_create_solana_trade_client().await?;
let creator = Pubkey::from_str("xxxxxx")?; // dev account let creator = Pubkey::from_str("xxxxxx")?; // dev account
let buy_sol_cost = 100_000; // 0.0001 SOL let buy_sol_cost = 100_000; // 0.0001 SOL
let slippage_basis_points = Some(100); let slippage_basis_points = Some(100);
let rpc = RpcClient::new(rpc_url); let recent_blockhash = solana_trade_client.rpc.get_latest_blockhash().await?;
let recent_blockhash = rpc.get_latest_blockhash().unwrap();
let mint_pubkey = Pubkey::from_str("xxxxxx")?; // token mint let mint_pubkey = Pubkey::from_str("xxxxxx")?; // token mint
println!("Buying tokens from PumpFun..."); println!("Buying tokens from PumpFun...");
// get bonding curve // get bonding curve
let (bonding_curve, bonding_curve_pda) = let (bonding_curve, bonding_curve_pda) =
@@ -82,195 +103,123 @@ async fn test_pumpfun() -> AnyResult<()> {
// let bonding_curve = // let bonding_curve =
// BondingCurveAccount::new(&mint_pubkey, dev_buy_token, dev_cost_sol, creator); // BondingCurveAccount::new(&mint_pubkey, dev_buy_token, dev_cost_sol, creator);
// buy // buy
let buy_protocol_params = PumpFunParams {
trade_type: trade_type::COPY_BUY.to_string(),
bonding_curve: Some(Arc::new(bonding_curve)),
};
let buy_params = BuyParams {
rpc: Some(solana_trade_client.rpc.clone()),
payer: solana_trade_client.payer.clone(),
mint: mint_pubkey,
creator: creator,
amount_sol: buy_sol_cost,
slippage_basis_points: slippage_basis_points,
priority_fee: solana_trade_client.trade_config.clone().priority_fee,
lookup_table_key: solana_trade_client.trade_config.clone().lookup_table_key,
recent_blockhash,
data_size_limit: 0,
protocol_params: Box::new(buy_protocol_params.clone()),
};
let buy_with_tip_params = buy_params
.clone()
.with_tip(solana_trade_client.swqos_clients.clone());
solana_trade_client solana_trade_client
.buy_use_buy_params(buy_with_tip_params, None) .buy(
mint_pubkey,
Some(creator),
buy_sol_cost,
slippage_basis_points,
recent_blockhash,
None,
false,
TradingProtocol::PumpFun,
Some(Box::new(PumpFunParams {
bonding_curve: Some(Arc::new(bonding_curve.clone())),
})),
)
.await?; .await?;
// sell // sell
println!("Selling tokens from PumpFun..."); println!("Selling tokens from PumpFun...");
let sell_protocol_params = PumpFunSellParams {};
let amount_token = 0; // 写上真实的amount_token let amount_token = 0; // 写上真实的amount_token
let sell_params = SellParams {
rpc: Some(solana_trade_client.rpc.clone()),
payer: solana_trade_client.payer.clone(),
mint: mint_pubkey,
creator: creator,
amount_token: Some(amount_token),
slippage_basis_points: None,
priority_fee: solana_trade_client.trade_config.clone().priority_fee,
lookup_table_key: solana_trade_client.trade_config.clone().lookup_table_key,
recent_blockhash,
protocol_params: Box::new(sell_protocol_params.clone()),
};
solana_trade_client solana_trade_client
.sell_by_amount_use_sell_params(sell_params) .sell(
mint_pubkey,
Some(creator),
amount_token,
slippage_basis_points,
recent_blockhash,
None,
false,
TradingProtocol::PumpFun,
None,
)
.await?; .await?;
Ok(()) Ok(())
} }
async fn test_pumpswap() -> AnyResult<()> { async fn test_pumpswap() -> AnyResult<()> {
let payer = Keypair::new(); println!("Testing PumpSwap trading...");
let rpc_url = "https://mainnet.helius-rpc.com/?api-key=xxxxxx".to_string();
let swqos_configs = vec![ let solana_trade_client = test_create_solana_trade_client().await?;
SwqosConfig::Jito(SwqosRegion::Frankfurt),
SwqosConfig::NextBlock("your api_token".to_string(), SwqosRegion::Frankfurt),
SwqosConfig::Bloxroute("your api_token".to_string(), SwqosRegion::Frankfurt),
SwqosConfig::ZeroSlot("your api_token".to_string(), SwqosRegion::Frankfurt),
SwqosConfig::Temporal("your api_token".to_string(), SwqosRegion::Frankfurt),
SwqosConfig::Default(rpc_url.clone()),
];
// Define cluster configuration
let trade_config = TradeConfig {
rpc_url: rpc_url.clone(),
commitment: CommitmentConfig::confirmed(),
priority_fee: PriorityFee::default(),
swqos_configs,
lookup_table_key: None,
};
let solana_trade_client = SolanaTrade::new(Arc::new(payer), trade_config).await;
let creator = Pubkey::from_str("11111111111111111111111111111111")?; // dev account let creator = Pubkey::from_str("11111111111111111111111111111111")?; // dev account
let buy_sol_cost = 100_000; // 0.0001 SOL let buy_sol_cost = 100_000; // 0.0001 SOL
let slippage_basis_points = Some(100); let slippage_basis_points = Some(100);
let rpc = RpcClient::new(rpc_url); let recent_blockhash = solana_trade_client.rpc.get_latest_blockhash().await?;
let recent_blockhash = rpc.get_latest_blockhash().unwrap();
let mint_pubkey = Pubkey::from_str("2zMMhcVQEXDtdE6vsFS7S7D5oUodfJHE8vd1gnBouauv")?; // token mint let mint_pubkey = Pubkey::from_str("2zMMhcVQEXDtdE6vsFS7S7D5oUodfJHE8vd1gnBouauv")?; // token mint
println!("Buying tokens from PumpSwap..."); println!("Buying tokens from PumpSwap...");
// buy // buy
let protocol_params = PumpSwapParams {
pool: None,
pool_base_token_account: None,
pool_quote_token_account: None,
user_base_token_account: None,
user_quote_token_account: None,
auto_handle_wsol: true,
};
let buy_params = BuyParams {
rpc: Some(solana_trade_client.rpc.clone()),
payer: solana_trade_client.payer.clone(),
mint: mint_pubkey,
creator: creator,
amount_sol: buy_sol_cost,
slippage_basis_points: slippage_basis_points,
priority_fee: solana_trade_client.trade_config.clone().priority_fee,
lookup_table_key: solana_trade_client.trade_config.clone().lookup_table_key,
recent_blockhash,
data_size_limit: 0,
protocol_params: Box::new(protocol_params.clone()),
};
let buy_with_tip_params = buy_params
.clone()
.with_tip(solana_trade_client.swqos_clients.clone());
solana_trade_client solana_trade_client
.buy_use_buy_params(buy_with_tip_params, None) .buy(
mint_pubkey,
Some(creator),
buy_sol_cost,
slippage_basis_points,
recent_blockhash,
None,
false,
TradingProtocol::PumpSwap,
None,
)
.await?; .await?;
// sell // sell
println!("Selling tokens from PumpSwap..."); println!("Selling tokens from PumpSwap...");
let amount_token = 0; // 写上真实的amount_token let amount_token = 0; // 写上真实的amount_token
let sell_params = SellParams {
rpc: Some(solana_trade_client.rpc.clone()),
payer: solana_trade_client.payer.clone(),
mint: mint_pubkey,
creator: creator,
amount_token: Some(amount_token),
slippage_basis_points: None,
priority_fee: solana_trade_client.trade_config.clone().priority_fee,
lookup_table_key: solana_trade_client.trade_config.clone().lookup_table_key,
recent_blockhash,
protocol_params: Box::new(protocol_params.clone()),
};
solana_trade_client solana_trade_client
.sell_by_amount_use_sell_params(sell_params) .sell(
mint_pubkey,
Some(creator),
amount_token,
slippage_basis_points,
recent_blockhash,
None,
false,
TradingProtocol::PumpSwap,
None,
)
.await?; .await?;
Ok(()) Ok(())
} }
async fn test_bonk() -> Result<(), Box<dyn std::error::Error>> { async fn test_bonk() -> Result<(), Box<dyn std::error::Error>> {
// 创建一个随机账户作为交易者 println!("Testing Bonk trading...");
let payer = Keypair::new();
let rpc_url = "https://mainnet.helius-rpc.com/?api-key=xxxxxx".to_string(); let solana_trade_client = test_create_solana_trade_client().await?;
let swqos_configs = vec![ let buy_sol_cost = 100_000; // 0.0001 SOL
SwqosConfig::Jito(SwqosRegion::Frankfurt), let slippage_basis_points = Some(100); // 1%
SwqosConfig::NextBlock("your api_token".to_string(), SwqosRegion::Frankfurt),
SwqosConfig::Bloxroute("your api_token".to_string(), SwqosRegion::Frankfurt),
SwqosConfig::ZeroSlot("your api_token".to_string(), SwqosRegion::Frankfurt),
SwqosConfig::Temporal("your api_token".to_string(), SwqosRegion::Frankfurt),
SwqosConfig::Default(rpc_url.clone()),
];
// Define cluster configuration
let trade_config = TradeConfig {
rpc_url: rpc_url.clone(),
commitment: CommitmentConfig::confirmed(),
priority_fee: PriorityFee::default(),
swqos_configs,
lookup_table_key: None,
};
let solana_trade_client = SolanaTrade::new(Arc::new(payer), trade_config).await;
let amount = 100_000; // 0.0001 SOL
let recent_blockhash = solana_trade_client.rpc.get_latest_blockhash().await?; let recent_blockhash = solana_trade_client.rpc.get_latest_blockhash().await?;
let mint = Pubkey::from_str("xxxxxxx")?; let mint_pubkey = Pubkey::from_str("xxxxxxx")?;
let bonk_params = BonkParams {
virtual_base: None,
virtual_quote: None,
real_base_before: None,
real_quote_before: None,
auto_handle_wsol: true,
};
println!("Buying tokens from letsbonk.fun..."); println!("Buying tokens from letsbonk.fun...");
// buy // buy
let buy_params = BuyParams {
rpc: Some(solana_trade_client.rpc.clone()),
payer: solana_trade_client.payer.clone(),
mint: mint,
creator: Pubkey::default(),
amount_sol: amount,
slippage_basis_points: None,
priority_fee: solana_trade_client.trade_config.clone().priority_fee,
lookup_table_key: solana_trade_client.trade_config.clone().lookup_table_key,
recent_blockhash,
data_size_limit: 0,
protocol_params: Box::new(bonk_params.clone()),
};
let buy_with_tip_params = buy_params
.clone()
.with_tip(solana_trade_client.swqos_clients.clone());
solana_trade_client solana_trade_client
.buy_use_buy_params(buy_with_tip_params, None) .buy(
mint_pubkey,
None,
buy_sol_cost,
slippage_basis_points,
recent_blockhash,
None,
false,
TradingProtocol::Bonk,
None,
)
.await?; .await?;
// sell // sell
println!("Selling tokens from letsbonk.fun..."); println!("Selling tokens from letsbonk.fun...");
let sell_params = SellParams { let amount_token = 0; // 写上真实的amount_token
rpc: Some(solana_trade_client.rpc.clone()),
payer: solana_trade_client.payer.clone(),
mint: mint,
creator: Pubkey::default(),
amount_token: None,
slippage_basis_points: None,
priority_fee: solana_trade_client.trade_config.clone().priority_fee,
lookup_table_key: solana_trade_client.trade_config.clone().lookup_table_key,
recent_blockhash,
protocol_params: Box::new(bonk_params.clone()),
};
solana_trade_client solana_trade_client
.sell_by_amount_use_sell_params(sell_params) .sell(
mint_pubkey,
None,
amount_token,
slippage_basis_points,
recent_blockhash,
None,
false,
TradingProtocol::Bonk,
None,
)
.await?; .await?;
Ok(()) Ok(())
} }
@@ -319,11 +268,7 @@ async fn test_grpc() -> Result<(), Box<dyn std::error::Error>> {
// 订阅 PumpSwap 事件 // 订阅 PumpSwap 事件
println!("开始监听事件,按 Ctrl+C 停止..."); println!("开始监听事件,按 Ctrl+C 停止...");
let protocols = vec![ let protocols = vec![Protocol::PumpFun, Protocol::PumpSwap, Protocol::Bonk];
Protocol::PumpFun,
Protocol::PumpSwap,
Protocol::Bonk,
];
grpc.subscribe_events(protocols, None, None, None, callback) grpc.subscribe_events(protocols, None, None, None, callback)
.await?; .await?;
@@ -371,11 +316,7 @@ async fn test_shreds() -> Result<(), Box<dyn std::error::Error>> {
// 订阅 PumpSwap 事件 // 订阅 PumpSwap 事件
println!("开始监听事件,按 Ctrl+C 停止..."); println!("开始监听事件,按 Ctrl+C 停止...");
let protocols = vec![ let protocols = vec![Protocol::PumpFun, Protocol::PumpSwap, Protocol::Bonk];
Protocol::PumpFun,
Protocol::PumpSwap,
Protocol::Bonk,
];
shred_stream shred_stream
.shredstream_subscribe(protocols, None, callback) .shredstream_subscribe(protocols, None, callback)
.await?; .await?;
@@ -1,7 +1,7 @@
use crate::streaming::event_parser::protocols::bonk::types::{ use crate::streaming::event_parser::protocols::bonk::types::{
CurveParams, MintParams, PoolStatus, TradeDirection, VestingParams, CurveParams, MintParams, PoolStatus, TradeDirection, VestingParams,
}; };
use crate::streaming::event_parser::{common::EventMetadata, core::traits::UnifiedEvent}; use crate::streaming::event_parser::common::EventMetadata;
use crate::impl_unified_event; use crate::impl_unified_event;
use borsh::BorshDeserialize; use borsh::BorshDeserialize;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@@ -2,7 +2,7 @@ use borsh::BorshDeserialize;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use solana_sdk::pubkey::Pubkey; use solana_sdk::pubkey::Pubkey;
use crate::streaming::event_parser::{common::EventMetadata, core::traits::UnifiedEvent}; use crate::streaming::event_parser::common::EventMetadata;
use crate::impl_unified_event; use crate::impl_unified_event;
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)] #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)]
@@ -2,7 +2,7 @@ use solana_sdk::{instruction::CompiledInstruction, pubkey::Pubkey};
use solana_transaction_status::UiCompiledInstruction; use solana_transaction_status::UiCompiledInstruction;
use crate::streaming::event_parser::{ use crate::streaming::event_parser::{
common::{utils::*, EventMetadata, EventType, ProtocolType}, common::{EventMetadata, EventType, ProtocolType},
core::traits::{EventParser, GenericEventParseConfig, GenericEventParser, UnifiedEvent}, core::traits::{EventParser, GenericEventParseConfig, GenericEventParser, UnifiedEvent},
protocols::pumpfun::{discriminators, PumpFunCreateTokenEvent, PumpFunTradeEvent}, protocols::pumpfun::{discriminators, PumpFunCreateTokenEvent, PumpFunTradeEvent},
}; };
@@ -2,7 +2,7 @@ use borsh::BorshDeserialize;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use solana_sdk::pubkey::Pubkey; use solana_sdk::pubkey::Pubkey;
use crate::streaming::event_parser::{common::EventMetadata, core::traits::UnifiedEvent}; use crate::streaming::event_parser::common::EventMetadata;
use crate::impl_unified_event; use crate::impl_unified_event;
/// 买入事件 /// 买入事件
+2 -6
View File
@@ -4,10 +4,8 @@ use chrono::Local;
use futures::{channel::mpsc, sink::Sink, SinkExt, Stream, StreamExt}; use futures::{channel::mpsc, sink::Sink, SinkExt, Stream, StreamExt};
use log::{error, info}; use log::{error, info};
use rustls::crypto::{ring::default_provider, CryptoProvider}; use rustls::crypto::{ring::default_provider, CryptoProvider};
use solana_sdk::{pubkey, pubkey::Pubkey, signature::Signature}; use solana_sdk::{pubkey::Pubkey, signature::Signature};
use solana_transaction_status::{ use solana_transaction_status::{EncodedTransactionWithStatusMeta, UiTransactionEncoding};
option_serializer::OptionSerializer, EncodedTransactionWithStatusMeta, UiTransactionEncoding,
};
use tonic::{transport::channel::ClientTlsConfig, Status}; use tonic::{transport::channel::ClientTlsConfig, Status};
use yellowstone_grpc_client::{GeyserGrpcClient, Interceptor}; use yellowstone_grpc_client::{GeyserGrpcClient, Interceptor};
use yellowstone_grpc_proto::geyser::{ use yellowstone_grpc_proto::geyser::{
@@ -21,8 +19,6 @@ use crate::streaming::event_parser::{EventParserFactory, Protocol, UnifiedEvent}
type TransactionsFilterMap = HashMap<String, SubscribeRequestFilterTransactions>; type TransactionsFilterMap = HashMap<String, SubscribeRequestFilterTransactions>;
const PUMP_PROGRAM_ID: Pubkey = pubkey!("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P");
const SYSTEM_PROGRAM_ID: Pubkey = pubkey!("11111111111111111111111111111111");
const CONNECT_TIMEOUT: u64 = 10; const CONNECT_TIMEOUT: u64 = 10;
const REQUEST_TIMEOUT: u64 = 60; const REQUEST_TIMEOUT: u64 = 60;
const CHANNEL_SIZE: usize = 1000; const CHANNEL_SIZE: usize = 1000;
-3
View File
@@ -4,9 +4,6 @@ use rand::seq::IndexedRandom;
use reqwest::Client; use reqwest::Client;
use serde_json::json; use serde_json::json;
use std::{sync::Arc, time::Instant}; use std::{sync::Arc, time::Instant};
use solana_sdk::{signature::Signature};
use std::time::Duration; use std::time::Duration;
use solana_transaction_status::UiTransactionEncoding; use solana_transaction_status::UiTransactionEncoding;
-107
View File
@@ -1,107 +0,0 @@
use solana_hash::Hash;
use solana_sdk::{pubkey::Pubkey, signature::Keypair};
use std::sync::Arc;
use crate::swqos::SwqosClient;
use crate::trading::{
core::params::{PumpSwapParams, BonkParams},
factory::Protocol,
BuyParams, TradeFactory,
};
use crate::{common::PriorityFee, SolanaRpcClient};
// Constants for compute budget
// Increased from 64KB to 256KB to handle larger transactions
const MAX_LOADED_ACCOUNTS_DATA_SIZE_LIMIT: u32 = 256 * 1024;
// Buy tokens from a Pumpswap pool
pub async fn buy(
rpc: Arc<SolanaRpcClient>,
payer: Arc<Keypair>,
mint: Pubkey,
virtual_base: u128,
virtual_quote: u128,
real_base_before: u128,
real_quote_before: u128,
amount_sol: u64,
slippage_basis_points: Option<u64>,
priority_fee: PriorityFee,
lookup_table_key: Option<Pubkey>,
recent_blockhash: Hash,
auto_handle_wsol: bool,
) -> Result<(), anyhow::Error> {
// 创建执行器
let executor = TradeFactory::create_executor(Protocol::Bonk);
// 创建协议特定参数
let protocol_params = Box::new(BonkParams {
auto_handle_wsol: auto_handle_wsol,
virtual_base: Some(virtual_base),
virtual_quote: Some(virtual_quote),
real_base_before: Some(real_base_before),
real_quote_before: Some(real_quote_before),
});
// 创建买入参数
let buy_params = BuyParams {
rpc: Some(rpc.clone()),
payer: payer,
mint: mint,
creator: Pubkey::default(),
amount_sol: amount_sol,
slippage_basis_points: slippage_basis_points,
priority_fee: priority_fee,
lookup_table_key: lookup_table_key,
recent_blockhash: recent_blockhash,
data_size_limit: MAX_LOADED_ACCOUNTS_DATA_SIZE_LIMIT,
protocol_params,
};
// 执行买入
executor.buy(buy_params).await?;
Ok(())
}
// Buy tokens using a MEV service
pub async fn buy_with_tip(
rpc: Arc<SolanaRpcClient>,
swqos_clients: Vec<Arc<SwqosClient>>,
payer: Arc<Keypair>,
mint: Pubkey,
virtual_base: u128,
virtual_quote: u128,
real_base_before: u128,
real_quote_before: u128,
amount_sol: u64,
slippage_basis_points: Option<u64>,
priority_fee: PriorityFee,
lookup_table_key: Option<Pubkey>,
recent_blockhash: Hash,
auto_handle_wsol: bool,
) -> Result<(), anyhow::Error> {
// 创建执行器
let executor = TradeFactory::create_executor(Protocol::Bonk);
// 创建协议特定参数
let protocol_params = Box::new(BonkParams {
auto_handle_wsol: auto_handle_wsol,
virtual_base: Some(virtual_base),
virtual_quote: Some(virtual_quote),
real_base_before: Some(real_base_before),
real_quote_before: Some(real_quote_before),
});
// 创建买入参数
let buy_params = BuyParams {
rpc: Some(rpc.clone()),
payer: payer,
mint: mint,
creator: Pubkey::default(),
amount_sol: amount_sol,
slippage_basis_points: slippage_basis_points,
priority_fee: priority_fee,
lookup_table_key: lookup_table_key,
recent_blockhash: recent_blockhash,
data_size_limit: MAX_LOADED_ACCOUNTS_DATA_SIZE_LIMIT,
protocol_params,
};
let buy_with_tip_params = buy_params.with_tip(swqos_clients);
// 执行买入
executor.buy_with_tip(buy_with_tip_params).await?;
Ok(())
}
+1 -20
View File
@@ -1,8 +1,5 @@
use anyhow::anyhow;
use solana_sdk::pubkey::Pubkey; use solana_sdk::pubkey::Pubkey;
use spl_associated_token_account::get_associated_token_address; use crate::constants;
use crate::{common::SolanaRpcClient, constants};
pub fn get_amount_out( pub fn get_amount_out(
amount_in: u64, amount_in: u64,
@@ -57,19 +54,3 @@ pub fn get_vault_pda(pool_state: &Pubkey, mint: &Pubkey) -> Option<Pubkey> {
let pda: Option<(Pubkey, u8)> = Pubkey::try_find_program_address(seeds, program_id); let pda: Option<(Pubkey, u8)> = Pubkey::try_find_program_address(seeds, program_id);
pda.map(|pubkey| pubkey.0) pda.map(|pubkey| pubkey.0)
} }
pub async fn get_token_balance(
rpc: &SolanaRpcClient,
payer: &Pubkey,
mint: &Pubkey,
) -> Result<u64, anyhow::Error> {
println!("payer: {:?}", payer);
println!("mint: {:?}", mint);
let ata = get_associated_token_address(payer, mint);
let balance = rpc.get_token_account_balance(&ata).await?;
let balance_u64 = balance
.amount
.parse::<u64>()
.map_err(|_| anyhow!("Failed to parse token balance"))?;
Ok(balance_u64)
}
-2
View File
@@ -1,4 +1,2 @@
pub mod buy;
pub mod sell;
pub mod common; pub mod common;
pub mod pool; pub mod pool;
-241
View File
@@ -1,241 +0,0 @@
use anyhow::anyhow;
use solana_hash::Hash;
use solana_sdk::{pubkey::Pubkey, signature::Keypair};
use solana_sdk::signature::Signer;
use std::sync::Arc;
use crate::common::{PriorityFee, SolanaRpcClient};
use crate::trading::bonk::common::get_token_balance;
use crate::swqos::SwqosClient;
use crate::trading::{
core::params::BonkParams, factory::Protocol, SellParams, TradeFactory,
};
// Sell tokens to a Pumpswap pool
pub async fn sell(
rpc: Arc<SolanaRpcClient>,
payer: Arc<Keypair>,
mint: Pubkey,
virtual_base: u128,
virtual_quote: u128,
real_base_before: u128,
real_quote_before: u128,
amount_token: Option<u64>,
slippage_basis_points: Option<u64>,
priority_fee: PriorityFee,
lookup_table_key: Option<Pubkey>,
recent_blockhash: Hash,
) -> Result<(), anyhow::Error> {
let executor = TradeFactory::create_executor(Protocol::Bonk);
// 创建PumpFun协议参数
let protocol_params = Box::new(BonkParams {
virtual_base: Some(virtual_base),
virtual_quote: Some(virtual_quote),
real_base_before: Some(real_base_before),
real_quote_before: Some(real_quote_before),
auto_handle_wsol: true,
});
// 创建卖出参数
let sell_params = SellParams {
rpc: Some(rpc.clone()),
payer: payer.clone(),
mint,
creator: Pubkey::default(),
amount_token: amount_token,
slippage_basis_points: slippage_basis_points,
priority_fee: priority_fee.clone(),
lookup_table_key,
recent_blockhash,
protocol_params,
};
// 执行卖出交易
executor.sell(sell_params).await?;
Ok(())
}
// Sell tokens by percentage
pub async fn sell_by_percent(
rpc: Arc<SolanaRpcClient>,
payer: Arc<Keypair>,
mint: Pubkey,
virtual_base: u128,
virtual_quote: u128,
real_base_before: u128,
real_quote_before: u128,
percent: u64,
slippage_basis_points: Option<u64>,
priority_fee: PriorityFee,
lookup_table_key: Option<Pubkey>,
recent_blockhash: Hash,
) -> Result<(), anyhow::Error> {
if percent == 0 || percent > 100 {
return Err(anyhow!("Percentage must be between 1 and 100"));
}
let balance_u64 = get_token_balance(&rpc, &payer.pubkey(), &mint).await?;
let amount = balance_u64 * percent / 100;
sell(
rpc,
payer,
mint,
virtual_base,
virtual_quote,
real_base_before,
real_quote_before,
Some(amount),
slippage_basis_points,
priority_fee,
lookup_table_key,
recent_blockhash,
)
.await
}
/// Sell tokens by amount
pub async fn sell_by_amount(
rpc: Arc<SolanaRpcClient>,
payer: Arc<Keypair>,
mint: Pubkey,
virtual_base: u128,
virtual_quote: u128,
real_base_before: u128,
real_quote_before: u128,
amount: u64,
slippage_basis_points: Option<u64>,
priority_fee: PriorityFee,
lookup_table_key: Option<Pubkey>,
recent_blockhash: Hash,
) -> Result<(), anyhow::Error> {
sell(
rpc,
payer,
mint,
virtual_base,
virtual_quote,
real_base_before,
real_quote_before,
Some(amount),
slippage_basis_points,
priority_fee,
lookup_table_key,
recent_blockhash,
)
.await
}
// Sell tokens using a MEV service
pub async fn sell_with_tip(
rpc: Arc<SolanaRpcClient>,
swqos_clients: Vec<Arc<SwqosClient>>,
payer: Arc<Keypair>,
mint: Pubkey,
virtual_base: u128,
virtual_quote: u128,
real_base_before: u128,
real_quote_before: u128,
amount_token: Option<u64>,
slippage_basis_points: Option<u64>,
priority_fee: PriorityFee,
lookup_table_key: Option<Pubkey>,
recent_blockhash: Hash,
) -> Result<(), anyhow::Error> {
let executor = TradeFactory::create_executor(Protocol::Bonk);
// 创建PumpFun协议参数
let protocol_params = Box::new(BonkParams {
virtual_base: Some(virtual_base),
virtual_quote: Some(virtual_quote),
real_base_before: Some(real_base_before),
real_quote_before: Some(real_quote_before),
auto_handle_wsol: true,
});
// 创建卖出参数
let sell_params = SellParams {
rpc: Some(rpc.clone()),
payer: payer.clone(),
mint,
creator: Pubkey::default(),
amount_token: amount_token,
slippage_basis_points: slippage_basis_points,
priority_fee: priority_fee.clone(),
lookup_table_key,
recent_blockhash,
protocol_params,
};
let sell_with_tip_params = sell_params.with_tip(swqos_clients);
// 执行卖出交易
executor.sell_with_tip(sell_with_tip_params).await?;
Ok(())
}
// Sell tokens by percentage using a MEV service
pub async fn sell_by_percent_with_tip(
rpc: Arc<SolanaRpcClient>,
swqos_clients: Vec<Arc<SwqosClient>>,
payer: Arc<Keypair>,
mint: Pubkey,
virtual_base: u128,
virtual_quote: u128,
real_base_before: u128,
real_quote_before: u128,
percent: u64,
slippage_basis_points: Option<u64>,
priority_fee: PriorityFee,
lookup_table_key: Option<Pubkey>,
recent_blockhash: Hash,
) -> Result<(), anyhow::Error> {
if percent == 0 || percent > 100 {
return Err(anyhow!("Percentage must be between 1 and 100"));
}
let balance_u64 = get_token_balance(&rpc, &payer.pubkey(), &mint).await?;
let amount = balance_u64 * percent / 100;
sell_with_tip(
rpc,
swqos_clients,
payer,
mint,
virtual_base,
virtual_quote,
real_base_before,
real_quote_before,
Some(amount),
slippage_basis_points,
priority_fee,
lookup_table_key,
recent_blockhash,
)
.await
}
// Sell tokens by amount using a MEV service
pub async fn sell_by_amount_with_tip(
rpc: Arc<SolanaRpcClient>,
swqos_clients: Vec<Arc<SwqosClient>>,
payer: Arc<Keypair>,
mint: Pubkey,
virtual_base: u128,
virtual_quote: u128,
real_base_before: u128,
real_quote_before: u128,
amount: u64,
slippage_basis_points: Option<u64>,
priority_fee: PriorityFee,
lookup_table_key: Option<Pubkey>,
recent_blockhash: Hash,
) -> Result<(), anyhow::Error> {
sell_with_tip(
rpc,
swqos_clients,
payer,
mint,
virtual_base,
virtual_quote,
real_base_before,
real_quote_before,
Some(amount),
slippage_basis_points,
priority_fee,
lookup_table_key,
recent_blockhash,
)
.await
}
+2
View File
@@ -2,9 +2,11 @@ pub mod nonce_manager;
pub mod transaction_builder; pub mod transaction_builder;
pub mod compute_budget_manager; pub mod compute_budget_manager;
pub mod address_lookup_manager; pub mod address_lookup_manager;
pub mod utils;
// Re-export commonly used functions // Re-export commonly used functions
pub use nonce_manager::*; pub use nonce_manager::*;
pub use transaction_builder::*; pub use transaction_builder::*;
pub use compute_budget_manager::*; pub use compute_budget_manager::*;
pub use address_lookup_manager::*; pub use address_lookup_manager::*;
pub use utils::*;
@@ -1,4 +1,3 @@
use anyhow::anyhow;
use solana_hash::Hash; use solana_hash::Hash;
use solana_sdk::{ use solana_sdk::{
instruction::Instruction, instruction::Instruction,
+134
View File
@@ -0,0 +1,134 @@
use solana_sdk::{
pubkey::Pubkey, signature::Keypair, signer::Signer, system_instruction,
transaction::Transaction,
};
use spl_associated_token_account::get_associated_token_address;
use spl_token::instruction::close_account;
use crate::common::SolanaRpcClient;
use anyhow::anyhow;
#[inline]
pub async fn get_token_balance(
rpc: &SolanaRpcClient,
payer: &Pubkey,
mint: &Pubkey,
) -> Result<u64, anyhow::Error> {
println!("payer: {:?}", payer);
println!("mint: {:?}", mint);
let ata = get_associated_token_address(payer, mint);
let balance = rpc.get_token_account_balance(&ata).await?;
let balance_u64 = balance
.amount
.parse::<u64>()
.map_err(|_| anyhow!("Failed to parse token balance"))?;
Ok(balance_u64)
}
#[inline]
pub async fn get_sol_balance(
rpc: &SolanaRpcClient,
account: &Pubkey,
) -> Result<u64, anyhow::Error> {
let balance = rpc.get_balance(account).await?;
Ok(balance)
}
// Calculate slippage for buy operations
#[inline]
pub fn calculate_with_slippage_buy(amount: u64, basis_points: u64) -> u64 {
amount + (amount * basis_points / 10000)
}
// Calculate slippage for sell operations
#[inline]
pub fn calculate_with_slippage_sell(amount: u64, basis_points: u64) -> u64 {
if amount <= basis_points / 10000 {
1
} else {
amount - (amount * basis_points / 10000)
}
}
pub async fn transfer_sol(
rpc: &SolanaRpcClient,
payer: &Keypair,
receive_wallet: &Pubkey,
amount: u64,
) -> Result<(), anyhow::Error> {
if amount == 0 {
return Err(anyhow!("transfer_sol: Amount cannot be zero"));
}
let balance = get_sol_balance(rpc, &payer.pubkey()).await?;
if balance < amount {
return Err(anyhow!("Insufficient balance"));
}
let transfer_instruction =
system_instruction::transfer(&payer.pubkey(), receive_wallet, amount);
let recent_blockhash = rpc.get_latest_blockhash().await?;
let transaction = Transaction::new_signed_with_payer(
&[transfer_instruction],
Some(&payer.pubkey()),
&[payer],
recent_blockhash,
);
rpc.send_and_confirm_transaction(&transaction).await?;
Ok(())
}
/// 关闭代币账户
///
/// 此函数用于关闭指定代币的关联代币账户,将账户中的代币余额转移给账户所有者。
///
/// # 参数
///
/// * `rpc` - Solana RPC客户端
/// * `payer` - 支付交易费用的账户
/// * `mint` - 代币的Mint地址
///
/// # 返回值
///
/// 返回一个Result,成功时返回(),失败时返回错误
pub async fn close_token_account(
rpc: &SolanaRpcClient,
payer: &Keypair,
mint: &Pubkey,
) -> Result<(), anyhow::Error> {
// 获取关联代币账户地址
let ata = get_associated_token_address(&payer.pubkey(), mint);
// 检查账户是否存在
let account_exists = rpc.get_account(&ata).await.is_ok();
if !account_exists {
return Ok(()); // 如果账户不存在,直接返回成功
}
// 构建关闭账户指令
let close_account_ix = close_account(
&spl_token::ID,
&ata,
&payer.pubkey(),
&payer.pubkey(),
&[&payer.pubkey()],
)?;
// 构建交易
let recent_blockhash = rpc.get_latest_blockhash().await?;
let transaction = Transaction::new_signed_with_payer(
&[close_account_ix],
Some(&payer.pubkey()),
&[payer],
recent_blockhash,
);
// 发送交易
rpc.send_and_confirm_transaction(&transaction).await?;
Ok(())
}
+10 -18
View File
@@ -1,5 +1,4 @@
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use solana_sdk::signer::Signer;
use std::sync::Arc; use std::sync::Arc;
use super::{ use super::{
@@ -13,6 +12,8 @@ use crate::{
trading::common::{build_rpc_transaction, build_sell_transaction}, trading::common::{build_rpc_transaction, build_sell_transaction},
}; };
const MAX_LOADED_ACCOUNTS_DATA_SIZE_LIMIT: u32 = 256 * 1024;
/// 通用交易执行器实现 /// 通用交易执行器实现
pub struct GenericTradeExecutor { pub struct GenericTradeExecutor {
instruction_builder: Arc<dyn InstructionBuilder>, instruction_builder: Arc<dyn InstructionBuilder>,
@@ -29,26 +30,14 @@ impl GenericTradeExecutor {
protocol_name, protocol_name,
} }
} }
/// 获取代币余额
async fn get_token_balance(
&self,
rpc: Arc<crate::common::SolanaRpcClient>,
payer: &solana_sdk::signature::Keypair,
mint: &solana_sdk::pubkey::Pubkey,
) -> Result<u64> {
let ata = spl_associated_token_account::get_associated_token_address(&payer.pubkey(), mint);
let balance = rpc.get_token_account_balance(&ata).await?;
balance
.amount
.parse::<u64>()
.map_err(|_| anyhow!("Failed to parse token balance"))
}
} }
#[async_trait::async_trait] #[async_trait::async_trait]
impl TradeExecutor for GenericTradeExecutor { impl TradeExecutor for GenericTradeExecutor {
async fn buy(&self, params: BuyParams) -> Result<()> { async fn buy(&self, mut params: BuyParams) -> Result<()> {
if params.data_size_limit == 0 {
params.data_size_limit = MAX_LOADED_ACCOUNTS_DATA_SIZE_LIMIT;
}
if params.rpc.is_none() { if params.rpc.is_none() {
return Err(anyhow!("RPC is not set")); return Err(anyhow!("RPC is not set"));
} }
@@ -80,7 +69,10 @@ impl TradeExecutor for GenericTradeExecutor {
Ok(()) Ok(())
} }
async fn buy_with_tip(&self, params: BuyWithTipParams) -> Result<()> { async fn buy_with_tip(&self, mut params: BuyWithTipParams) -> Result<()> {
if params.data_size_limit == 0 {
params.data_size_limit = MAX_LOADED_ACCOUNTS_DATA_SIZE_LIMIT;
}
let mut timer = TradeTimer::new("构建买入交易指令"); let mut timer = TradeTimer::new("构建买入交易指令");
// 验证参数 - 转换为BuyParams进行验证 // 验证参数 - 转换为BuyParams进行验证
+28 -17
View File
@@ -3,9 +3,9 @@ use solana_sdk::{pubkey::Pubkey, signature::Keypair};
use std::sync::Arc; use std::sync::Arc;
use super::traits::ProtocolParams; use super::traits::ProtocolParams;
use crate::common::bonding_curve::BondingCurveAccount;
use crate::common::{PriorityFee, SolanaRpcClient}; use crate::common::{PriorityFee, SolanaRpcClient};
use crate::swqos::SwqosClient; use crate::swqos::SwqosClient;
use crate::common::bonding_curve::BondingCurveAccount;
/// 通用买入参数 /// 通用买入参数
#[derive(Clone)] #[derive(Clone)]
@@ -74,24 +74,18 @@ pub struct SellWithTipParams {
/// PumpFun协议特定参数 /// PumpFun协议特定参数
#[derive(Clone)] #[derive(Clone)]
pub struct PumpFunParams { pub struct PumpFunParams {
pub trade_type: String,
pub bonding_curve: Option<Arc<BondingCurveAccount>>, pub bonding_curve: Option<Arc<BondingCurveAccount>>,
} }
impl ProtocolParams for PumpFunParams { impl PumpFunParams {
fn as_any(&self) -> &dyn std::any::Any { pub fn default() -> Self {
self Self {
} bonding_curve: None,
}
fn clone_box(&self) -> Box<dyn ProtocolParams> {
Box::new(self.clone())
} }
} }
#[derive(Clone)] impl ProtocolParams for PumpFunParams {
pub struct PumpFunSellParams {}
impl ProtocolParams for PumpFunSellParams {
fn as_any(&self) -> &dyn std::any::Any { fn as_any(&self) -> &dyn std::any::Any {
self self
} }
@@ -105,13 +99,18 @@ impl ProtocolParams for PumpFunSellParams {
#[derive(Clone)] #[derive(Clone)]
pub struct PumpSwapParams { pub struct PumpSwapParams {
pub pool: Option<Pubkey>, pub pool: Option<Pubkey>,
pub pool_base_token_account: Option<Pubkey>,
pub pool_quote_token_account: Option<Pubkey>,
pub user_base_token_account: Option<Pubkey>,
pub user_quote_token_account: Option<Pubkey>,
pub auto_handle_wsol: bool, pub auto_handle_wsol: bool,
} }
impl PumpSwapParams {
pub fn default() -> Self {
Self {
pool: None,
auto_handle_wsol: true,
}
}
}
impl ProtocolParams for PumpSwapParams { impl ProtocolParams for PumpSwapParams {
fn as_any(&self) -> &dyn std::any::Any { fn as_any(&self) -> &dyn std::any::Any {
self self
@@ -132,6 +131,18 @@ pub struct BonkParams {
pub auto_handle_wsol: bool, pub auto_handle_wsol: bool,
} }
impl BonkParams {
pub fn default() -> Self {
Self {
virtual_base: None,
virtual_quote: None,
real_base_before: None,
real_quote_before: None,
auto_handle_wsol: true,
}
}
}
impl ProtocolParams for BonkParams { impl ProtocolParams for BonkParams {
fn as_any(&self) -> &dyn std::any::Any { fn as_any(&self) -> &dyn std::any::Any {
self self
-2
View File
@@ -1,7 +1,5 @@
use anyhow::Result; use anyhow::Result;
use solana_sdk::instruction::Instruction; use solana_sdk::instruction::Instruction;
use std::sync::Arc;
use super::params::{BuyParams, BuyWithTipParams, SellParams, SellWithTipParams}; use super::params::{BuyParams, BuyWithTipParams, SellParams, SellWithTipParams};
/// 交易执行器trait - 定义了所有交易协议都需要实现的核心方法 /// 交易执行器trait - 定义了所有交易协议都需要实现的核心方法
+18 -18
View File
@@ -9,30 +9,30 @@ use super::{
/// 支持的交易协议 /// 支持的交易协议
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub enum Protocol { pub enum TradingProtocol {
PumpFun, PumpFun,
PumpSwap, PumpSwap,
Bonk, Bonk,
} }
impl std::fmt::Display for Protocol { impl std::fmt::Display for TradingProtocol {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {
Protocol::PumpFun => write!(f, "PumpFun"), TradingProtocol::PumpFun => write!(f, "PumpFun"),
Protocol::PumpSwap => write!(f, "PumpSwap"), TradingProtocol::PumpSwap => write!(f, "PumpSwap"),
Protocol::Bonk => write!(f, "Bonk"), TradingProtocol::Bonk => write!(f, "Bonk"),
} }
} }
} }
impl std::str::FromStr for Protocol { impl std::str::FromStr for TradingProtocol {
type Err = anyhow::Error; type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() { match s.to_lowercase().as_str() {
"pumpfun" => Ok(Protocol::PumpFun), "pumpfun" => Ok(TradingProtocol::PumpFun),
"pumpswap" => Ok(Protocol::PumpSwap), "pumpswap" => Ok(TradingProtocol::PumpSwap),
"bonk" => Ok(Protocol::Bonk), "bonk" => Ok(TradingProtocol::Bonk),
_ => Err(anyhow!("Unsupported protocol: {}", s)), _ => Err(anyhow!("Unsupported protocol: {}", s)),
} }
} }
@@ -43,17 +43,17 @@ pub struct TradeFactory;
impl TradeFactory { impl TradeFactory {
/// 创建指定协议的交易执行器 /// 创建指定协议的交易执行器
pub fn create_executor(protocol: Protocol) -> Arc<dyn TradeExecutor> { pub fn create_executor(protocol: TradingProtocol) -> Arc<dyn TradeExecutor> {
match protocol { match protocol {
Protocol::PumpFun => { TradingProtocol::PumpFun => {
let instruction_builder = Arc::new(PumpFunInstructionBuilder); let instruction_builder = Arc::new(PumpFunInstructionBuilder);
Arc::new(GenericTradeExecutor::new(instruction_builder, "PumpFun")) Arc::new(GenericTradeExecutor::new(instruction_builder, "PumpFun"))
} }
Protocol::PumpSwap => { TradingProtocol::PumpSwap => {
let instruction_builder = Arc::new(PumpSwapInstructionBuilder); let instruction_builder = Arc::new(PumpSwapInstructionBuilder);
Arc::new(GenericTradeExecutor::new(instruction_builder, "PumpSwap")) Arc::new(GenericTradeExecutor::new(instruction_builder, "PumpSwap"))
} }
Protocol::Bonk => { TradingProtocol::Bonk => {
let instruction_builder = Arc::new(BonkInstructionBuilder); let instruction_builder = Arc::new(BonkInstructionBuilder);
Arc::new(GenericTradeExecutor::new( Arc::new(GenericTradeExecutor::new(
instruction_builder, instruction_builder,
@@ -64,16 +64,16 @@ impl TradeFactory {
} }
/// 获取所有支持的协议 /// 获取所有支持的协议
pub fn supported_protocols() -> Vec<Protocol> { pub fn supported_protocols() -> Vec<TradingProtocol> {
vec![ vec![
Protocol::PumpFun, TradingProtocol::PumpFun,
Protocol::PumpSwap, TradingProtocol::PumpSwap,
Protocol::Bonk, TradingProtocol::Bonk,
] ]
} }
/// 检查协议是否支持 /// 检查协议是否支持
pub fn is_supported(protocol: &Protocol) -> bool { pub fn is_supported(protocol: &TradingProtocol) -> bool {
Self::supported_protocols().contains(protocol) Self::supported_protocols().contains(protocol)
} }
} }
-88
View File
@@ -1,88 +0,0 @@
use crate::{
common::{bonding_curve::BondingCurveAccount, PriorityFee, SolanaRpcClient},
swqos::SwqosClient,
trading::{core::params::PumpFunParams, factory::Protocol, BuyParams, TradeFactory},
};
use solana_hash::Hash;
use solana_sdk::{pubkey::Pubkey, signature::Keypair};
use std::sync::Arc;
const MAX_LOADED_ACCOUNTS_DATA_SIZE_LIMIT: u32 = 250000;
pub async fn buy(
rpc: Arc<SolanaRpcClient>,
payer: Arc<Keypair>,
mint: Pubkey,
creator: Pubkey,
buy_sol_cost: u64,
slippage_basis_points: Option<u64>,
priority_fee: PriorityFee,
lookup_table_key: Option<Pubkey>,
recent_blockhash: Hash,
bonding_curve: Option<Arc<BondingCurveAccount>>,
trade_type: String,
) -> Result<(), anyhow::Error> {
// 创建执行器
let executor = TradeFactory::create_executor(Protocol::PumpFun);
// 创建协议特定参数
let protocol_params = Box::new(PumpFunParams {
trade_type: trade_type,
bonding_curve: bonding_curve,
});
// 创建买入参数
let buy_params = BuyParams {
rpc: Some(rpc),
payer,
mint,
creator,
amount_sol: buy_sol_cost,
slippage_basis_points: slippage_basis_points,
priority_fee: priority_fee,
lookup_table_key: lookup_table_key,
recent_blockhash,
data_size_limit: MAX_LOADED_ACCOUNTS_DATA_SIZE_LIMIT,
protocol_params,
};
// 执行买入
executor.buy(buy_params).await?;
Ok(())
}
pub async fn buy_with_tip(
swqos_clients: Vec<Arc<SwqosClient>>,
payer: Arc<Keypair>,
mint: Pubkey,
creator: Pubkey,
buy_sol_cost: u64,
slippage_basis_points: Option<u64>,
priority_fee: PriorityFee,
lookup_table_key: Option<Pubkey>,
recent_blockhash: Hash,
bonding_curve: Option<Arc<BondingCurveAccount>>,
trade_type: String,
) -> Result<(), anyhow::Error> {
// 创建执行器
let executor = TradeFactory::create_executor(Protocol::PumpFun);
// 创建协议特定参数
let protocol_params = Box::new(PumpFunParams {
trade_type: trade_type,
bonding_curve: bonding_curve,
});
// 创建买入参数
let buy_params = BuyParams {
rpc: None,
payer,
mint,
creator,
amount_sol: buy_sol_cost,
slippage_basis_points: slippage_basis_points,
priority_fee: priority_fee,
lookup_table_key: lookup_table_key,
recent_blockhash,
data_size_limit: MAX_LOADED_ACCOUNTS_DATA_SIZE_LIMIT,
protocol_params,
};
let buy_with_tip_params = buy_params.with_tip(swqos_clients);
// 执行买入
executor.buy_with_tip(buy_with_tip_params).await?;
Ok(())
}
+3 -142
View File
@@ -1,104 +1,24 @@
use anyhow::anyhow; use anyhow::anyhow;
use borsh::BorshDeserialize;
use spl_token::instruction::close_account;
use tokio::sync::RwLock; use tokio::sync::RwLock;
use std::{collections::HashMap, sync::Arc}; use std::{collections::HashMap, sync::Arc};
use solana_sdk::{ use solana_sdk::{
compute_budget::ComputeBudgetInstruction, instruction::Instruction, pubkey::Pubkey, signature::Keypair, signer::Signer, system_instruction, transaction::Transaction compute_budget::ComputeBudgetInstruction, instruction::Instruction, pubkey::Pubkey
}; };
use spl_associated_token_account::get_associated_token_address;
use pumpfun_program::accounts::BondingCurveAccount as PumpfunBondingCurveAccount; use pumpfun_program::accounts::BondingCurveAccount as PumpfunBondingCurveAccount;
use crate::{ use crate::{
common::{ common::{
bonding_curve::BondingCurveAccount, global::GlobalAccount, PriorityFee, SolanaRpcClient bonding_curve::BondingCurveAccount, global::GlobalAccount, PriorityFee, SolanaRpcClient
}, },
constants::{ constants::{
self, pumpfun::{global_constants::{CREATOR_FEE, FEE_BASIS_POINTS}, trade::DEFAULT_SLIPPAGE} self, pumpfun::global_constants::{CREATOR_FEE, FEE_BASIS_POINTS}, trade::trade::DEFAULT_SLIPPAGE
}, },
streaming::event_parser::protocols::pumpfun::PumpFunTradeEvent streaming::event_parser::protocols::pumpfun::PumpFunTradeEvent, trading::common::calculate_with_slippage_buy
}; };
lazy_static::lazy_static! { lazy_static::lazy_static! {
static ref ACCOUNT_CACHE: RwLock<HashMap<Pubkey, Arc<GlobalAccount>>> = RwLock::new(HashMap::new()); static ref ACCOUNT_CACHE: RwLock<HashMap<Pubkey, Arc<GlobalAccount>>> = RwLock::new(HashMap::new());
} }
pub async fn transfer_sol(rpc: &SolanaRpcClient, payer: &Keypair, receive_wallet: &Pubkey, amount: u64) -> Result<(), anyhow::Error> {
if amount == 0 {
return Err(anyhow!("transfer_sol: Amount cannot be zero"));
}
let balance = get_sol_balance(rpc, &payer.pubkey()).await?;
if balance < amount {
return Err(anyhow!("Insufficient balance"));
}
let transfer_instruction = system_instruction::transfer(
&payer.pubkey(),
receive_wallet,
amount,
);
let recent_blockhash = rpc.get_latest_blockhash().await?;
let transaction = Transaction::new_signed_with_payer(
&[transfer_instruction],
Some(&payer.pubkey()),
&[payer],
recent_blockhash,
);
rpc.send_and_confirm_transaction(&transaction).await?;
Ok(())
}
/// 关闭代币账户
///
/// 此函数用于关闭指定代币的关联代币账户,将账户中的代币余额转移给账户所有者。
///
/// # 参数
///
/// * `rpc` - Solana RPC客户端
/// * `payer` - 支付交易费用的账户
/// * `mint` - 代币的Mint地址
///
/// # 返回值
///
/// 返回一个Result,成功时返回(),失败时返回错误
pub async fn close_token_account(rpc: &SolanaRpcClient, payer: &Keypair, mint: &Pubkey) -> Result<(), anyhow::Error> {
// 获取关联代币账户地址
let ata = get_associated_token_address(&payer.pubkey(), mint);
// 检查账户是否存在
let account_exists = rpc.get_account(&ata).await.is_ok();
if !account_exists {
return Ok(()); // 如果账户不存在,直接返回成功
}
// 构建关闭账户指令
let close_account_ix = close_account(
&spl_token::ID,
&ata,
&payer.pubkey(),
&payer.pubkey(),
&[&payer.pubkey()],
)?;
// 构建交易
let recent_blockhash = rpc.get_latest_blockhash().await?;
let transaction = Transaction::new_signed_with_payer(
&[close_account_ix],
Some(&payer.pubkey()),
&[payer],
recent_blockhash,
);
// 发送交易
rpc.send_and_confirm_transaction(&transaction).await?;
Ok(())
}
#[inline] #[inline]
pub fn create_priority_fee_instructions(priority_fee: PriorityFee) -> Vec<Instruction> { pub fn create_priority_fee_instructions(priority_fee: PriorityFee) -> Vec<Instruction> {
let mut instructions = Vec::with_capacity(2); let mut instructions = Vec::with_capacity(2);
@@ -108,45 +28,6 @@ pub fn create_priority_fee_instructions(priority_fee: PriorityFee) -> Vec<Instru
instructions instructions
} }
// #[inline]
pub async fn get_token_balance(rpc: &SolanaRpcClient, payer: &Pubkey, mint: &Pubkey) -> Result<u64, anyhow::Error> {
let ata = get_associated_token_address(payer, mint);
// let account_data = rpc.get_account_data(&ata).await?;
// let token_account = Account::unpack(&account_data.as_slice())?;
// Ok(token_account.amount)
// println!("get_token_balance ata: {}", ata);
let balance = rpc.get_token_account_balance(&ata).await?;
let balance_u64 = balance.amount.parse::<u64>()
.map_err(|_| anyhow!("Failed to parse token balance"))?;
Ok(balance_u64)
}
#[inline]
pub async fn get_token_balance_and_ata(rpc: &SolanaRpcClient, payer: &Keypair, mint: &Pubkey) -> Result<(u64, Pubkey), anyhow::Error> {
let ata = get_associated_token_address(&payer.pubkey(), mint);
// let account_data = rpc.get_account_data(&ata).await?;
// let token_account = Account::unpack(&account_data)?;
// Ok((token_account.amount, ata))
let balance = rpc.get_token_account_balance(&ata).await?;
let balance_u64 = balance.amount.parse::<u64>()
.map_err(|_| anyhow!("Failed to parse token balance"))?;
if balance_u64 == 0 {
return Err(anyhow!("Balance is 0"));
}
Ok((balance_u64, ata))
}
#[inline]
pub async fn get_sol_balance(rpc: &SolanaRpcClient, account: &Pubkey) -> Result<u64, anyhow::Error> {
let balance = rpc.get_balance(account).await?;
Ok(balance)
}
#[inline] #[inline]
pub fn get_global_pda() -> Pubkey { pub fn get_global_pda() -> Pubkey {
@@ -194,18 +75,8 @@ pub fn get_metadata_pda(mint: &Pubkey) -> Pubkey {
#[inline] #[inline]
pub async fn get_global_account(/*rpc: &SolanaRpcClient*/) -> Result<Arc<GlobalAccount>, anyhow::Error> { pub async fn get_global_account(/*rpc: &SolanaRpcClient*/) -> Result<Arc<GlobalAccount>, anyhow::Error> {
// let global = constants::global_constants::GLOBAL_ACCOUNT;
// if let Some(account) = ACCOUNT_CACHE.read().await.get(&global) {
// return Ok(account.clone());
// }
let global_account = GlobalAccount::new(); let global_account = GlobalAccount::new();
// let account = rpc.get_account(&global).await?;
// let global_account = bincode::deserialize::<accounts::GlobalAccount>(&account.data)?;
let global_account = Arc::new(global_account); let global_account = Arc::new(global_account);
// ACCOUNT_CACHE.write().await.insert(global, global_account.clone());
Ok(global_account) Ok(global_account)
} }
@@ -348,13 +219,3 @@ pub fn get_buy_price(amount: u64, trade_info: &PumpFunTradeEvent) -> u64 {
s_u64.min(trade_info.real_token_reserves) s_u64.min(trade_info.real_token_reserves)
} }
#[inline]
pub fn calculate_with_slippage_buy(amount: u64, basis_points: u64) -> u64 {
amount + (amount * basis_points) / 10000
}
#[inline]
pub fn calculate_with_slippage_sell(amount: u64, basis_points: u64) -> u64 {
amount - (amount * basis_points) / 10000
}
-2
View File
@@ -1,3 +1 @@
pub mod buy;
pub mod sell;
pub mod common; pub mod common;
-184
View File
@@ -1,184 +0,0 @@
use crate::trading::{
core::params::PumpFunSellParams, factory::Protocol, SellParams, TradeFactory,
};
use crate::{
common::{PriorityFee, SolanaRpcClient},
swqos::SwqosClient,
};
use anyhow::anyhow;
use solana_hash::Hash;
use solana_sdk::{pubkey::Pubkey, signature::Keypair};
use std::sync::Arc;
pub async fn sell(
rpc: Arc<SolanaRpcClient>,
payer: Arc<Keypair>,
mint: Pubkey,
creator: Pubkey,
amount_token: u64,
priority_fee: PriorityFee,
lookup_table_key: Option<Pubkey>,
recent_blockhash: Hash,
) -> Result<(), anyhow::Error> {
let executor = TradeFactory::create_executor(Protocol::PumpFun);
// 创建PumpFun协议参数
let protocol_params = Box::new(PumpFunSellParams {});
// 创建卖出参数
let sell_params = SellParams {
rpc: Some(rpc.clone()),
payer: payer.clone(),
mint,
creator,
amount_token: Some(amount_token),
slippage_basis_points: None,
priority_fee: priority_fee.clone(),
lookup_table_key,
recent_blockhash,
protocol_params,
};
// 执行卖出交易
executor.sell(sell_params).await?;
Ok(())
}
/// Sell tokens by percentage
pub async fn sell_by_percent(
rpc: Arc<SolanaRpcClient>,
payer: Arc<Keypair>,
mint: Pubkey,
creator: Pubkey,
percent: u64,
amount_token: u64,
priority_fee: PriorityFee,
lookup_table_key: Option<Pubkey>,
recent_blockhash: Hash,
) -> Result<(), anyhow::Error> {
if percent == 0 || percent > 100 {
return Err(anyhow!("Percentage must be between 1 and 100"));
}
let amount = amount_token * percent / 100;
sell(
rpc,
payer,
mint,
creator,
amount,
priority_fee,
lookup_table_key,
recent_blockhash,
)
.await
}
/// Sell tokens by amount
pub async fn sell_by_amount(
rpc: Arc<SolanaRpcClient>,
payer: Arc<Keypair>,
mint: Pubkey,
creator: Pubkey,
amount: u64,
priority_fee: PriorityFee,
lookup_table_key: Option<Pubkey>,
recent_blockhash: Hash,
) -> Result<(), anyhow::Error> {
sell(
rpc,
payer,
mint,
creator,
amount,
priority_fee,
lookup_table_key,
recent_blockhash,
)
.await
}
pub async fn sell_by_percent_with_tip(
rpc: Arc<SolanaRpcClient>,
fee_clients: Vec<Arc<SwqosClient>>,
payer: Arc<Keypair>,
mint: Pubkey,
creator: Pubkey,
percent: u64,
amount_token: u64,
priority_fee: PriorityFee,
lookup_table_key: Option<Pubkey>,
recent_blockhash: Hash,
) -> Result<(), anyhow::Error> {
if percent == 0 || percent > 100 {
return Err(anyhow!("Percentage must be between 1 and 100"));
}
let amount = amount_token * percent / 100;
sell_with_tip(
rpc,
fee_clients,
payer,
mint,
creator,
amount,
priority_fee,
lookup_table_key,
recent_blockhash,
)
.await
}
pub async fn sell_by_amount_with_tip(
rpc: Arc<SolanaRpcClient>,
fee_clients: Vec<Arc<SwqosClient>>,
payer: Arc<Keypair>,
mint: Pubkey,
creator: Pubkey,
amount: u64,
priority_fee: PriorityFee,
lookup_table_key: Option<Pubkey>,
recent_blockhash: Hash,
) -> Result<(), anyhow::Error> {
sell_with_tip(
rpc,
fee_clients,
payer,
mint,
creator,
amount,
priority_fee,
lookup_table_key,
recent_blockhash,
)
.await
}
/// Sell tokens using Jito
pub async fn sell_with_tip(
rpc: Arc<SolanaRpcClient>,
fee_clients: Vec<Arc<SwqosClient>>,
payer: Arc<Keypair>,
mint: Pubkey,
creator: Pubkey,
amount_token: u64,
priority_fee: PriorityFee,
lookup_table_key: Option<Pubkey>,
recent_blockhash: Hash,
) -> Result<(), anyhow::Error> {
let executor = TradeFactory::create_executor(Protocol::PumpFun);
// 创建PumpFun协议参数
let protocol_params = Box::new(PumpFunSellParams {});
// 创建卖出参数
let sell_params = SellParams {
rpc: Some(rpc.clone()),
payer: payer.clone(),
mint,
creator,
amount_token: Some(amount_token),
slippage_basis_points: None,
priority_fee: priority_fee.clone(),
lookup_table_key,
recent_blockhash,
protocol_params,
};
let sell_with_tip_params = sell_params.with_tip(fee_clients);
// 执行卖出交易
executor.sell_with_tip(sell_with_tip_params).await?;
Ok(())
}
-111
View File
@@ -1,111 +0,0 @@
use solana_hash::Hash;
use solana_sdk::{pubkey::Pubkey, signature::Keypair};
use std::sync::Arc;
use crate::swqos::SwqosClient;
use crate::trading::{core::params::PumpSwapParams, factory::Protocol, BuyParams, TradeFactory};
use crate::{common::PriorityFee, SolanaRpcClient};
// Constants for compute budget
// Increased from 64KB to 256KB to handle larger transactions
const MAX_LOADED_ACCOUNTS_DATA_SIZE_LIMIT: u32 = 256 * 1024;
// Buy tokens from a Pumpswap pool
pub async fn buy(
rpc: Arc<SolanaRpcClient>,
payer: Arc<Keypair>,
mint: Pubkey,
creator: Pubkey,
amount_sol: u64,
slippage_basis_points: Option<u64>,
priority_fee: PriorityFee,
lookup_table_key: Option<Pubkey>,
recent_blockhash: Hash,
// 可选(必须全部传)
pool: Option<Pubkey>,
pool_base_token_account: Option<Pubkey>,
pool_quote_token_account: Option<Pubkey>,
user_base_token_account: Option<Pubkey>,
user_quote_token_account: Option<Pubkey>,
auto_handle_wsol: bool,
) -> Result<(), anyhow::Error> {
// 创建执行器
let executor = TradeFactory::create_executor(Protocol::PumpSwap);
// 创建协议特定参数
let protocol_params = Box::new(PumpSwapParams {
pool: pool,
pool_base_token_account: pool_base_token_account,
pool_quote_token_account: pool_quote_token_account,
user_base_token_account: user_base_token_account,
user_quote_token_account: user_quote_token_account,
auto_handle_wsol: auto_handle_wsol,
});
// 创建买入参数
let buy_params = BuyParams {
rpc: Some(rpc.clone()),
payer: payer,
mint: mint,
creator: creator,
amount_sol: amount_sol,
slippage_basis_points: slippage_basis_points,
priority_fee: priority_fee,
lookup_table_key: lookup_table_key,
recent_blockhash: recent_blockhash,
data_size_limit: MAX_LOADED_ACCOUNTS_DATA_SIZE_LIMIT,
protocol_params,
};
// 执行买入
executor.buy(buy_params).await?;
Ok(())
}
// Buy tokens using a MEV service
pub async fn buy_with_tip(
rpc: Arc<SolanaRpcClient>,
swqos_clients: Vec<Arc<SwqosClient>>,
payer: Arc<Keypair>,
mint: Pubkey,
creator: Pubkey,
amount_sol: u64,
slippage_basis_points: Option<u64>,
priority_fee: PriorityFee,
lookup_table_key: Option<Pubkey>,
recent_blockhash: Hash,
// 可选(必须全部传)
pool: Option<Pubkey>,
pool_base_token_account: Option<Pubkey>,
pool_quote_token_account: Option<Pubkey>,
user_base_token_account: Option<Pubkey>,
user_quote_token_account: Option<Pubkey>,
auto_handle_wsol: bool,
) -> Result<(), anyhow::Error> {
// 创建执行器
let executor = TradeFactory::create_executor(Protocol::PumpSwap);
// 创建协议特定参数
let protocol_params = Box::new(PumpSwapParams {
pool: pool,
pool_base_token_account: pool_base_token_account,
pool_quote_token_account: pool_quote_token_account,
user_base_token_account: user_base_token_account,
user_quote_token_account: user_quote_token_account,
auto_handle_wsol: auto_handle_wsol,
});
// 创建买入参数
let buy_params = BuyParams {
rpc: Some(rpc.clone()),
payer: payer,
mint: mint,
creator: creator,
amount_sol: amount_sol,
slippage_basis_points: slippage_basis_points,
priority_fee: priority_fee,
lookup_table_key: lookup_table_key,
recent_blockhash: recent_blockhash,
data_size_limit: MAX_LOADED_ACCOUNTS_DATA_SIZE_LIMIT,
protocol_params,
};
let buy_with_tip_params = buy_params.with_tip(swqos_clients);
// 执行买入
executor.buy_with_tip(buy_with_tip_params).await?;
Ok(())
}
+2 -40
View File
@@ -1,47 +1,9 @@
use anyhow::anyhow;
use solana_sdk::{
pubkey::Pubkey,
signature::{Keypair, Signer},
};
use crate::common::SolanaRpcClient; use crate::common::SolanaRpcClient;
use crate::trading::pumpswap; use crate::trading::pumpswap;
use solana_sdk::pubkey::Pubkey;
// Calculate slippage for buy operations
pub fn calculate_with_slippage_buy(amount: u64, basis_points: u64) -> u64 {
amount + (amount * basis_points / 10000)
}
// Calculate slippage for sell operations
pub fn calculate_with_slippage_sell(amount: u64, basis_points: u64) -> u64 {
if amount <= basis_points / 10000 {
1
} else {
amount - (amount * basis_points / 10000)
}
}
// Get token balance for a specific mint and owner
pub async fn get_token_balance(
rpc: &SolanaRpcClient,
owner: &Keypair,
mint: &Pubkey,
) -> Result<(u64, Pubkey), anyhow::Error> {
let ata = spl_associated_token_account::get_associated_token_address(&owner.pubkey(), mint);
match rpc.get_token_account_balance(&ata).await {
Ok(balance) => {
let amount = balance.amount.parse::<u64>().map_err(|e| anyhow!(e))?;
Ok((amount, ata))
}
Err(_) => Ok((0, ata)),
}
}
// Find a pool for a specific mint // Find a pool for a specific mint
pub async fn find_pool( pub async fn find_pool(rpc: &SolanaRpcClient, mint: &Pubkey) -> Result<Pubkey, anyhow::Error> {
rpc: &SolanaRpcClient,
mint: &Pubkey,
) -> Result<Pubkey, anyhow::Error> {
let (pool_address, _) = pumpswap::pool::Pool::find_by_mint(rpc, mint).await?; let (pool_address, _) = pumpswap::pool::Pool::find_by_mint(rpc, mint).await?;
Ok(pool_address) Ok(pool_address)
} }
-2
View File
@@ -1,4 +1,2 @@
pub mod buy;
pub mod sell;
pub mod common; pub mod common;
pub mod pool; pub mod pool;
-1
View File
@@ -2,7 +2,6 @@ use crate::{common::SolanaRpcClient, constants::pumpswap::accounts};
use anyhow::anyhow; use anyhow::anyhow;
use solana_account_decoder::UiAccountEncoding; use solana_account_decoder::UiAccountEncoding;
use solana_sdk::pubkey::Pubkey; use solana_sdk::pubkey::Pubkey;
use std::str::FromStr;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Pool { pub struct Pool {
-266
View File
@@ -1,266 +0,0 @@
use anyhow::anyhow;
use solana_hash::Hash;
use solana_sdk::{pubkey::Pubkey, signature::Keypair};
use std::sync::Arc;
use crate::common::{PriorityFee, SolanaRpcClient};
use crate::swqos::SwqosClient;
use crate::trading::pumpswap::common::get_token_balance;
use crate::trading::{core::params::PumpSwapParams, factory::Protocol, SellParams, TradeFactory};
// Sell tokens to a Pumpswap pool
pub async fn sell(
rpc: Arc<SolanaRpcClient>,
payer: Arc<Keypair>,
mint: Pubkey,
creator: Pubkey,
amount_token: Option<u64>,
slippage_basis_points: Option<u64>,
priority_fee: PriorityFee,
lookup_table_key: Option<Pubkey>,
recent_blockhash: Hash,
// 可选(必须全部传)
pool: Option<Pubkey>,
pool_base_token_account: Option<Pubkey>,
pool_quote_token_account: Option<Pubkey>,
user_base_token_account: Option<Pubkey>,
user_quote_token_account: Option<Pubkey>,
) -> Result<(), anyhow::Error> {
let executor = TradeFactory::create_executor(Protocol::PumpSwap);
// 创建PumpFun协议参数
let protocol_params = Box::new(PumpSwapParams {
pool,
pool_base_token_account,
pool_quote_token_account,
user_base_token_account,
user_quote_token_account,
auto_handle_wsol: true,
});
// 创建卖出参数
let sell_params = SellParams {
rpc: Some(rpc.clone()),
payer: payer.clone(),
mint,
creator,
amount_token: amount_token,
slippage_basis_points: slippage_basis_points,
priority_fee: priority_fee.clone(),
lookup_table_key,
recent_blockhash,
protocol_params,
};
// 执行卖出交易
executor.sell(sell_params).await?;
Ok(())
}
// Sell tokens by percentage
pub async fn sell_by_percent(
rpc: Arc<SolanaRpcClient>,
payer: Arc<Keypair>,
mint: Pubkey,
creator: Pubkey,
percent: u64,
slippage_basis_points: Option<u64>,
priority_fee: PriorityFee,
lookup_table_key: Option<Pubkey>,
recent_blockhash: Hash,
// 可选(必须全部传)
pool: Option<Pubkey>,
pool_base_token_account: Option<Pubkey>,
pool_quote_token_account: Option<Pubkey>,
user_base_token_account: Option<Pubkey>,
user_quote_token_account: Option<Pubkey>,
) -> Result<(), anyhow::Error> {
if percent == 0 || percent > 100 {
return Err(anyhow!("Percentage must be between 1 and 100"));
}
let (balance_u64, _) = get_token_balance(rpc.as_ref(), payer.as_ref(), &mint).await?;
let amount = balance_u64 * percent / 100;
sell(
rpc,
payer,
mint,
creator,
Some(amount),
slippage_basis_points,
priority_fee,
lookup_table_key,
recent_blockhash,
pool,
pool_base_token_account,
pool_quote_token_account,
user_base_token_account,
user_quote_token_account,
)
.await
}
/// Sell tokens by amount
pub async fn sell_by_amount(
rpc: Arc<SolanaRpcClient>,
payer: Arc<Keypair>,
mint: Pubkey,
creator: Pubkey,
amount: u64,
slippage_basis_points: Option<u64>,
priority_fee: PriorityFee,
lookup_table_key: Option<Pubkey>,
recent_blockhash: Hash,
// 可选(必须全部传)
pool: Option<Pubkey>,
pool_base_token_account: Option<Pubkey>,
pool_quote_token_account: Option<Pubkey>,
user_base_token_account: Option<Pubkey>,
user_quote_token_account: Option<Pubkey>,
) -> Result<(), anyhow::Error> {
sell(
rpc,
payer,
mint,
creator,
Some(amount),
slippage_basis_points,
priority_fee,
lookup_table_key,
recent_blockhash,
pool,
pool_base_token_account,
pool_quote_token_account,
user_base_token_account,
user_quote_token_account,
)
.await
}
// Sell tokens using a MEV service
pub async fn sell_with_tip(
rpc: Arc<SolanaRpcClient>,
swqos_clients: Vec<Arc<SwqosClient>>,
payer: Arc<Keypair>,
mint: Pubkey,
creator: Pubkey,
amount_token: Option<u64>,
slippage_basis_points: Option<u64>,
priority_fee: PriorityFee,
lookup_table_key: Option<Pubkey>,
recent_blockhash: Hash,
// 可选(必须全部传)
pool: Option<Pubkey>,
pool_base_token_account: Option<Pubkey>,
pool_quote_token_account: Option<Pubkey>,
user_base_token_account: Option<Pubkey>,
user_quote_token_account: Option<Pubkey>,
) -> Result<(), anyhow::Error> {
let executor = TradeFactory::create_executor(Protocol::PumpSwap);
// 创建PumpFun协议参数
let protocol_params = Box::new(PumpSwapParams {
pool,
pool_base_token_account,
pool_quote_token_account,
user_base_token_account,
user_quote_token_account,
auto_handle_wsol: true,
});
// 创建卖出参数
let sell_params = SellParams {
rpc: Some(rpc.clone()),
payer: payer.clone(),
mint,
creator,
amount_token: amount_token,
slippage_basis_points: slippage_basis_points,
priority_fee: priority_fee.clone(),
lookup_table_key,
recent_blockhash,
protocol_params,
};
let sell_with_tip_params = sell_params.with_tip(swqos_clients);
// 执行卖出交易
executor.sell_with_tip(sell_with_tip_params).await?;
Ok(())
}
// Sell tokens by percentage using a MEV service
pub async fn sell_by_percent_with_tip(
rpc: Arc<SolanaRpcClient>,
swqos_clients: Vec<Arc<SwqosClient>>,
payer: Arc<Keypair>,
mint: Pubkey,
creator: Pubkey,
percent: u64,
slippage_basis_points: Option<u64>,
priority_fee: PriorityFee,
lookup_table_key: Option<Pubkey>,
recent_blockhash: Hash,
// 可选(必须全部传)
pool: Option<Pubkey>,
pool_base_token_account: Option<Pubkey>,
pool_quote_token_account: Option<Pubkey>,
user_base_token_account: Option<Pubkey>,
user_quote_token_account: Option<Pubkey>,
) -> Result<(), anyhow::Error> {
if percent == 0 || percent > 100 {
return Err(anyhow!("Percentage must be between 1 and 100"));
}
let (balance_u64, _) = get_token_balance(rpc.as_ref(), payer.as_ref(), &mint).await?;
let amount = balance_u64 * percent / 100;
sell_with_tip(
rpc,
swqos_clients,
payer,
mint,
creator,
Some(amount),
slippage_basis_points,
priority_fee,
lookup_table_key,
recent_blockhash,
pool,
pool_base_token_account,
pool_quote_token_account,
user_base_token_account,
user_quote_token_account,
)
.await
}
// Sell tokens by amount using a MEV service
pub async fn sell_by_amount_with_tip(
rpc: Arc<SolanaRpcClient>,
swqos_clients: Vec<Arc<SwqosClient>>,
payer: Arc<Keypair>,
mint: Pubkey,
creator: Pubkey,
amount: u64,
slippage_basis_points: Option<u64>,
priority_fee: PriorityFee,
lookup_table_key: Option<Pubkey>,
recent_blockhash: Hash,
// 可选(必须全部传)
pool: Option<Pubkey>,
pool_base_token_account: Option<Pubkey>,
pool_quote_token_account: Option<Pubkey>,
user_base_token_account: Option<Pubkey>,
user_quote_token_account: Option<Pubkey>,
) -> Result<(), anyhow::Error> {
sell_with_tip(
rpc,
swqos_clients,
payer,
mint,
creator,
Some(amount),
slippage_basis_points,
priority_fee,
lookup_table_key,
recent_blockhash,
pool,
pool_base_token_account,
pool_quote_token_account,
user_base_token_account,
user_quote_token_account,
)
.await
}
+165
View File
@@ -0,0 +1,165 @@
use crate::streaming::event_parser::protocols::pumpfun::PumpFunTradeEvent;
use crate::trading;
use crate::SolanaTrade;
use solana_sdk::pubkey::Pubkey;
use solana_sdk::signature::Keypair;
use solana_sdk::signer::Signer;
impl SolanaTrade {
#[inline]
pub async fn get_sol_balance(&self, payer: &Pubkey) -> Result<u64, anyhow::Error> {
trading::common::utils::get_sol_balance(&self.rpc, payer).await
}
#[inline]
pub async fn get_payer_sol_balance(&self) -> Result<u64, anyhow::Error> {
trading::common::utils::get_sol_balance(&self.rpc, &self.payer.pubkey()).await
}
#[inline]
pub async fn get_token_balance(
&self,
payer: &Pubkey,
mint: &Pubkey,
) -> Result<u64, anyhow::Error> {
println!(
"get_token_balance payer: {}, mint: {}, rpc_url: {}",
payer, mint, self.trade_config.rpc_url
);
trading::common::utils::get_token_balance(&self.rpc, payer, mint).await
}
#[inline]
pub async fn get_payer_token_balance(&self, mint: &Pubkey) -> Result<u64, anyhow::Error> {
trading::common::utils::get_token_balance(&self.rpc, &self.payer.pubkey(), mint).await
}
#[inline]
pub fn get_payer_pubkey(&self) -> Pubkey {
self.payer.pubkey()
}
#[inline]
pub fn get_payer(&self) -> &Keypair {
self.payer.as_ref()
}
#[inline]
pub async fn transfer_sol(
&self,
payer: &Keypair,
receive_wallet: &Pubkey,
amount: u64,
) -> Result<(), anyhow::Error> {
trading::common::utils::transfer_sol(&self.rpc, payer, receive_wallet, amount).await
}
#[inline]
pub async fn close_token_account(&self, mint: &Pubkey) -> Result<(), anyhow::Error> {
trading::common::utils::close_token_account(&self.rpc, self.payer.as_ref(), mint).await
}
// -------------------------------- PumpFun --------------------------------
#[inline]
pub fn get_pumpfun_token_price(
&self,
virtual_sol_reserves: u64,
virtual_token_reserves: u64,
) -> f64 {
trading::pumpfun::common::get_token_price(virtual_sol_reserves, virtual_token_reserves)
}
#[inline]
pub fn get_pumpfun_token_buy_price(&self, amount: u64, trade_info: &PumpFunTradeEvent) -> u64 {
trading::pumpfun::common::get_buy_price(amount, trade_info)
}
#[inline]
pub async fn get_pumpfun_token_current_price(
&self,
mint: &Pubkey,
) -> Result<f64, anyhow::Error> {
let (bonding_curve, _) =
trading::pumpfun::common::get_bonding_curve_account_v2(&self.rpc, mint).await?;
let virtual_sol_reserves = bonding_curve.virtual_sol_reserves;
let virtual_token_reserves = bonding_curve.virtual_token_reserves;
Ok(trading::pumpfun::common::get_token_price(
virtual_sol_reserves,
virtual_token_reserves,
))
}
#[inline]
pub async fn get_pumpfun_token_real_sol_reserves(
&self,
mint: &Pubkey,
) -> Result<u64, anyhow::Error> {
let (bonding_curve, _) =
trading::pumpfun::common::get_bonding_curve_account_v2(&self.rpc, mint).await?;
let actual_sol_reserves = bonding_curve.real_sol_reserves;
Ok(actual_sol_reserves)
}
#[inline]
pub async fn get_pumpfun_token_creator(&self, mint: &Pubkey) -> Result<Pubkey, anyhow::Error> {
let (bonding_curve, _) =
trading::pumpfun::common::get_bonding_curve_account_v2(&self.rpc, mint).await?;
let creator = bonding_curve.creator;
Ok(creator)
}
// -------------------------------- PumpSwap --------------------------------
#[inline]
pub async fn get_pumpswap_token_current_price(
&self,
pool_address: &Pubkey,
) -> Result<f64, anyhow::Error> {
let pool = trading::pumpswap::pool::Pool::fetch(&self.rpc, pool_address).await?;
let (base_amount, quote_amount) = pool.get_token_balances(&self.rpc).await?;
// Calculate price using constant product formula (x * y = k)
// Price = quote_amount / base_amount
if base_amount == 0 {
return Err(anyhow::anyhow!(
"Base amount is zero, cannot calculate price"
));
}
let price = quote_amount as f64 / base_amount as f64;
Ok(price)
}
#[inline]
pub async fn get_pumpswap_token_real_sol_reserves(
&self,
pool_address: &Pubkey,
) -> Result<u64, anyhow::Error> {
let pool = trading::pumpswap::pool::Pool::fetch(&self.rpc, pool_address).await?;
let (_, quote_amount) = pool.get_token_balances(&self.rpc).await?;
Ok(quote_amount)
}
#[inline]
pub async fn get_pumpswap_payer_token_balance(
&self,
pool_address: &Pubkey,
) -> Result<u64, anyhow::Error> {
let pool = trading::pumpswap::pool::Pool::fetch(&self.rpc, pool_address).await?;
let (base_amount, _) = pool.get_token_balances(&self.rpc).await?;
Ok(base_amount)
}
}