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
+1 -1
View File
@@ -3,7 +3,7 @@ use std::sync::Arc;
use solana_client::rpc_client::RpcClient;
use solana_sdk::{commitment_config::CommitmentConfig, pubkey::Pubkey, signature::Keypair};
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)]
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 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
}
+6 -12
View File
@@ -1,17 +1,11 @@
pub mod bonk;
pub mod pumpfun;
pub mod pumpswap;
pub mod bonk;
pub mod swqos;
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;
pub mod trade_platform {
pub const PUMPFUN: &'static str = "pumpfun";
pub const PUMPFUN_SWAP: &'static str = "pumpswap";
pub const BONK: &'static str = "bonk";
}
pub const PUMPFUN: &'static str = "pumpfun";
pub const PUMPFUN_SWAP: &'static str = "pumpswap";
pub const BONK: &'static str = "bonk";
}
+5 -16
View File
@@ -38,7 +38,7 @@ pub mod global_constants {
pub const INITIAL_REAL_TOKEN_RESERVES: u64 = 793_100_000_000_000;
pub const TOKEN_TOTAL_SUPPLY: u64 = 1_000_000_000_000_000;
pub const FEE_BASIS_POINTS: u64 = 95;
pub const ENABLE_MIGRATE: bool = false;
@@ -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_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_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
@@ -99,7 +99,8 @@ pub mod accounts {
pub const TOKEN_PROGRAM: Pubkey = pubkey!("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA");
/// Associated Token Program ID
pub const ASSOCIATED_TOKEN_PROGRAM: Pubkey = pubkey!("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL");
pub const ASSOCIATED_TOKEN_PROGRAM: Pubkey =
pubkey!("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL");
/// Rent Sysvar ID
pub const RENT: Pubkey = pubkey!("SysvarRent111111111111111111111111111111111");
@@ -107,20 +108,8 @@ pub mod accounts {
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;
impl Symbol {
pub const SOLANA: &'static str = "solana";
}
+5 -13
View File
@@ -32,7 +32,7 @@ pub mod accounts {
/// Public key for the fee recipient
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
pub const GLOBAL_ACCOUNT: Pubkey = pubkey!("ADyA8hdefvWN2dbGGWFotbzWxrAvLW83WG6QCVXvJKqw");
@@ -49,26 +49,18 @@ pub mod accounts {
pub const TOKEN_PROGRAM: Pubkey = pubkey!("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA");
/// Associated Token Program ID
pub const ASSOCIATED_TOKEN_PROGRAM: Pubkey = pubkey!("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL");
pub const ASSOCIATED_TOKEN_PROGRAM: Pubkey =
pubkey!("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL");
// PumpSwap 协议费用接收者
pub const PROTOCOL_FEE_RECIPIENT: Pubkey = pubkey!("62qc2CNXwrYqQScmEdiZFFAnJR262PxWEuNQtxfafNgV");
pub const PROTOCOL_FEE_RECIPIENT: Pubkey =
pubkey!("62qc2CNXwrYqQScmEdiZFFAnJR262PxWEuNQtxfafNgV");
/// Rent Sysvar ID
pub const RENT: Pubkey = pubkey!("SysvarRent111111111111111111111111111111111");
pub const AMM_PROGRAM: Pubkey = pubkey!("pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA");
}
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 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 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 crate::{
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::{
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,
},
trading::common::utils::get_token_balance,
trading::core::{
params::{BuyParams, BonkParams, SellParams},
traits::InstructionBuilder,
+24 -27
View File
@@ -1,34 +1,28 @@
use anyhow::{anyhow, Result};
use solana_sdk::{
instruction::Instruction, native_token::sol_to_lamports,
};
use solana_sdk::{instruction::Instruction, native_token::sol_to_lamports};
use spl_associated_token_account::{
get_associated_token_address, instruction::create_associated_token_account,
};
use spl_token::instruction::close_account;
use crate::{
constants, trading::pumpfun::common::{
get_bonding_curve_pda, get_global_pda, get_metadata_pda, get_mint_authority_pda
}
constants,
trading::pumpfun::common::{
get_bonding_curve_pda, get_global_pda, get_metadata_pda, get_mint_authority_pda,
},
};
use solana_sdk::{
instruction::AccountMeta,
pubkey::Pubkey,
signature::Keypair,
signer::Signer,
};
use solana_sdk::{instruction::AccountMeta, pubkey::Pubkey, signature::Keypair, signer::Signer};
use crate::{
constants::pumpfun::{global_constants::FEE_RECIPIENT, trade::DEFAULT_SLIPPAGE},
trading::pumpfun::common::{
calculate_with_slippage_buy, get_buy_token_amount_from_sol_amount, get_creator_vault_pda,
},
constants::pumpfun::global_constants::FEE_RECIPIENT,
constants::trade::trade::DEFAULT_SLIPPAGE,
trading::common::utils::calculate_with_slippage_buy,
trading::core::{
params::{BuyParams, PumpFunParams, SellParams},
traits::InstructionBuilder,
},
trading::pumpfun::common::{get_buy_token_amount_from_sol_amount, get_creator_vault_pda},
};
/// PumpFun协议的指令构建器
@@ -56,9 +50,7 @@ impl InstructionBuilder for PumpFunInstructionBuilder {
let max_sol_cost = calculate_with_slippage_buy(
params.amount_sol,
params
.slippage_basis_points
.unwrap_or(DEFAULT_SLIPPAGE),
params.slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE),
);
let creator_vault_pda = bonding_curve.get_creator_vault_pda();
@@ -161,22 +153,24 @@ pub struct Create {
impl Create {
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
data.extend_from_slice(&[24, 30, 200, 40, 5, 28, 7, 119]); // discriminator
// 添加 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.len() as u32).to_le_bytes()); // 添加 name 长度
data.extend_from_slice(self._name.as_bytes()); // 添加 name 内容
// 添加 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.len() as u32).to_le_bytes()); // 添加 symbol 长度
data.extend_from_slice(self._symbol.as_bytes()); // 添加 symbol 内容
// 添加 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.len() as u32).to_le_bytes()); // 添加 uri 长度
data.extend_from_slice(self._uri.as_bytes()); // 添加 uri 内容
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_readonly(constants::pumpfun::accounts::SYSTEM_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::EVENT_AUTHORITY, 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 crate::{
constants::pumpswap::{
accounts, trade::DEFAULT_SLIPPAGE, BUY_DISCRIMINATOR, SELL_DISCRIMINATOR,
},
trading::pumpswap::common::{
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,
constants::pumpswap::{accounts, BUY_DISCRIMINATOR, SELL_DISCRIMINATOR},
constants::trade::trade::DEFAULT_SLIPPAGE,
trading::common::utils::{
calculate_with_slippage_buy, calculate_with_slippage_sell, get_token_balance,
},
trading::core::{
params::{BuyParams, PumpSwapParams, SellParams},
traits::InstructionBuilder,
},
trading::pumpswap::common::{
coin_creator_vault_ata, coin_creator_vault_authority, find_pool, get_buy_token_amount,
get_sell_sol_amount,
},
};
/// PumpSwap协议的指令构建器
@@ -35,27 +36,11 @@ impl InstructionBuilder for PumpSwapInstructionBuilder {
}
// 根据是否提供了账户信息来构建指令
match (
&protocol_params.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),
) => {
match (&protocol_params.pool,) {
(Some(pool),) => {
self.build_buy_instructions_with_accounts(
params,
*pool,
*pool_base_token_account,
*pool_quote_token_account,
*user_base_token_account,
*user_quote_token_account,
protocol_params.auto_handle_wsol,
)
.await
@@ -73,29 +58,10 @@ impl InstructionBuilder for PumpSwapInstructionBuilder {
.ok_or_else(|| anyhow!("Invalid protocol params for PumpSwap"))?;
// 根据是否提供了账户信息来构建指令
match (
&protocol_params.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_sell_instructions_with_accounts(
params,
*pool,
*pool_base_token_account,
*pool_quote_token_account,
*user_base_token_account,
*user_quote_token_account,
)
.await
match (&protocol_params.pool,) {
(Some(pool),) => {
self.build_sell_instructions_with_accounts(params, *pool)
.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 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,
);
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
self.build_buy_instructions_with_accounts(params, pool, true)
.await
}
/// 自动发现池和账户信息并构建卖出指令
@@ -165,6 +98,30 @@ impl PumpSwapInstructionBuilder {
// 查找池
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(
&params.payer.pubkey(),
@@ -190,41 +147,6 @@ impl PumpSwapInstructionBuilder {
&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![];
if auto_handle_wsol {
@@ -328,10 +250,6 @@ impl PumpSwapInstructionBuilder {
&self,
params: &SellParams,
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>> {
if params.rpc.is_none() {
return Err(anyhow!("RPC is not set"));
@@ -341,8 +259,8 @@ impl PumpSwapInstructionBuilder {
// 获取代币余额
let mut amount = params.amount_token;
if params.amount_token.is_none() {
let (balance_u64, _) =
get_token_balance(rpc.as_ref(), params.payer.as_ref(), &params.mint).await?;
let balance_u64 =
get_token_balance(rpc.as_ref(), &params.payer.pubkey(), &params.mint).await?;
amount = Some(balance_u64);
}
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_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![];
// 插入wsol
+324 -598
View File
@@ -2,34 +2,28 @@ pub mod common;
pub mod constants;
pub mod instruction;
pub mod protos;
pub mod swqos;
pub mod streaming;
pub mod swqos;
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::Mutex;
use rustls::crypto::{ring::default_provider, CryptoProvider};
use solana_sdk::{
pubkey::Pubkey,
signature::{Keypair, Signer},
};
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 payer: Arc<Keypair>,
pub rpc: Arc<SolanaRpcClient>,
@@ -107,11 +101,95 @@ impl SolanaTrade {
.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,
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>,
with_tip: bool,
protocol: TradingProtocol,
protocol_params: Option<Box<dyn ProtocolParams>>,
) -> 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();
if custom_buy_tip_fee.is_some() {
priority_fee.buy_tip_fee = custom_buy_tip_fee.unwrap();
@@ -122,85 +200,126 @@ impl SolanaTrade {
custom_buy_tip_fee.unwrap(),
];
}
let mint = buy_params.mint;
let creator = buy_params.creator;
let buy_sol_cost = buy_params.amount_sol;
let slippage_basis_points = buy_params.slippage_basis_points;
let recent_blockhash = buy_params.recent_blockhash;
if let Some(protocol_params) = buy_params
.protocol_params
.as_any()
.downcast_ref::<PumpFunParams>()
{
trading::pumpfun::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.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 {
let buy_with_tip_params = buy_params.clone().with_tip(self.swqos_clients.clone());
// Validate protocol params
let is_valid_params = match protocol {
TradingProtocol::PumpFun => protocol_params
.as_any()
.downcast_ref::<PumpFunParams>()
.is_some(),
TradingProtocol::PumpSwap => protocol_params
.as_any()
.downcast_ref::<PumpSwapParams>()
.is_some(),
TradingProtocol::Bonk => protocol_params
.as_any()
.downcast_ref::<BonkParams>()
.is_some(),
};
if !is_valid_params {
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,
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>,
with_tip: bool,
protocol: TradingProtocol,
protocol_params: Option<Box<dyn ProtocolParams>>,
) -> 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() {
priority_fee.buy_tip_fee = custom_buy_tip_fee.unwrap();
priority_fee.buy_tip_fees = vec![
@@ -210,516 +329,123 @@ impl SolanaTrade {
custom_buy_tip_fee.unwrap(),
];
}
let mint = buy_params.mint;
let creator = buy_params.creator;
let buy_sol_cost = buy_params.amount_sol;
let slippage_basis_points = buy_params.slippage_basis_points;
let recent_blockhash = buy_params.recent_blockhash;
if let Some(protocol_params) = buy_params
.protocol_params
.as_any()
.downcast_ref::<PumpFunParams>()
{
trading::pumpfun::buy::buy_with_tip(
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.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 {
let sell_with_tip_params = sell_params.clone().with_tip(self.swqos_clients.clone());
// Validate protocol params
let is_valid_params = match protocol {
TradingProtocol::PumpFun => protocol_params
.as_any()
.downcast_ref::<PumpFunParams>()
.is_some(),
TradingProtocol::PumpSwap => protocol_params
.as_any()
.downcast_ref::<PumpSwapParams>()
.is_some(),
TradingProtocol::Bonk => protocol_params
.as_any()
.downcast_ref::<BonkParams>()
.is_some(),
};
if !is_valid_params {
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
pub async fn sell_by_percent_use_sell_params(
/// Execute a sell order for a percentage of the specified token amount
///
/// 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,
sell_params: SellParams,
mint: Pubkey,
creator: Option<Pubkey>,
amount_token: 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> {
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(
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"));
if percent == 0 || percent > 100 {
return Err(anyhow::anyhow!("Percentage must be between 1 and 100"));
}
}
/// Sell tokens by amount
pub async fn sell_by_amount_use_sell_params(
&self,
sell_params: SellParams,
) -> 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(
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)
let amount = amount_token * percent / 100;
self.sell(
mint,
creator,
amount,
slippage_basis_points,
recent_blockhash,
custom_buy_tip_fee,
with_tip,
protocol,
protocol_params,
)
.await
}
}
+125 -184
View File
@@ -2,40 +2,50 @@ use std::{str::FromStr, sync::Arc};
use sol_trade_sdk::{
common::{bonding_curve::BondingCurveAccount, AnyResult, PriorityFee, TradeConfig},
constants::{pumpfun::global_constants::TOKEN_TOTAL_SUPPLY, trade_type},
streaming::event_parser::{
protocols::{
bonk::{BonkPoolCreateEvent, BonkTradeEvent}, pumpfun::{PumpFunCreateTokenEvent, PumpFunTradeEvent}, pumpswap::{
PumpSwapBuyEvent, PumpSwapCreatePoolEvent, PumpSwapDepositEvent, PumpSwapSellEvent,
PumpSwapWithdrawEvent,
}
},
Protocol, UnifiedEvent,
},
streaming::{ShredStreamGrpc, YellowstoneGrpc},
constants::pumpfun::global_constants::TOKEN_TOTAL_SUPPLY,
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},
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,
};
use solana_client::rpc_client::RpcClient;
use solana_sdk::{commitment_config::CommitmentConfig, pubkey::Pubkey, signature::Keypair};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
test_create_solana_trade_client().await?;
test_pumpfun().await?;
// test_pumpswap().await?;
// test_bonk().await?;
// test_grpc().await?;
// test_shreds().await?;
test_pumpswap().await?;
test_bonk().await?;
test_grpc().await?;
test_shreds().await?;
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 rpc_url = "https://mainnet.helius-rpc.com/?api-key=xxxxxx".to_string();
// 配置各种 SWQOS 服务
let swqos_configs = vec![
SwqosConfig::Jito(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::Default(rpc_url.clone()),
];
// Define cluster configuration
// 定义交易配置
let trade_config = TradeConfig {
rpc_url: rpc_url.clone(),
commitment: CommitmentConfig::confirmed(),
@@ -52,13 +63,23 @@ async fn test_pumpfun() -> AnyResult<()> {
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)
}
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 buy_sol_cost = 100_000; // 0.0001 SOL
let slippage_basis_points = Some(100);
let rpc = RpcClient::new(rpc_url);
let recent_blockhash = rpc.get_latest_blockhash().unwrap();
let recent_blockhash = solana_trade_client.rpc.get_latest_blockhash().await?;
let mint_pubkey = Pubkey::from_str("xxxxxx")?; // token mint
println!("Buying tokens from PumpFun...");
// get bonding curve
let (bonding_curve, bonding_curve_pda) =
@@ -82,195 +103,123 @@ async fn test_pumpfun() -> AnyResult<()> {
// 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()),
};
let buy_with_tip_params = buy_params
.clone()
.with_tip(solana_trade_client.swqos_clients.clone());
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?;
// sell
println!("Selling tokens from PumpFun...");
let sell_protocol_params = PumpFunSellParams {};
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
.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?;
Ok(())
}
async fn test_pumpswap() -> AnyResult<()> {
let payer = Keypair::new();
let rpc_url = "https://mainnet.helius-rpc.com/?api-key=xxxxxx".to_string();
let swqos_configs = vec![
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;
println!("Testing PumpSwap trading...");
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 slippage_basis_points = Some(100);
let rpc = RpcClient::new(rpc_url);
let recent_blockhash = rpc.get_latest_blockhash().unwrap();
let recent_blockhash = solana_trade_client.rpc.get_latest_blockhash().await?;
let mint_pubkey = Pubkey::from_str("2zMMhcVQEXDtdE6vsFS7S7D5oUodfJHE8vd1gnBouauv")?; // token mint
println!("Buying tokens from PumpSwap...");
// 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
.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?;
// sell
println!("Selling tokens from PumpSwap...");
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
.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?;
Ok(())
}
async fn test_bonk() -> Result<(), Box<dyn std::error::Error>> {
// 创建一个随机账户作为交易者
let payer = Keypair::new();
let rpc_url = "https://mainnet.helius-rpc.com/?api-key=xxxxxx".to_string();
let swqos_configs = vec![
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 amount = 100_000; // 0.0001 SOL
println!("Testing Bonk trading...");
let solana_trade_client = test_create_solana_trade_client().await?;
let buy_sol_cost = 100_000; // 0.0001 SOL
let slippage_basis_points = Some(100); // 1%
let recent_blockhash = solana_trade_client.rpc.get_latest_blockhash().await?;
let mint = 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,
};
let mint_pubkey = Pubkey::from_str("xxxxxxx")?;
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
.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?;
// sell
println!("Selling tokens from letsbonk.fun...");
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()),
};
let amount_token = 0; // 写上真实的amount_token
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?;
Ok(())
}
@@ -319,11 +268,7 @@ async fn test_grpc() -> Result<(), Box<dyn std::error::Error>> {
// 订阅 PumpSwap 事件
println!("开始监听事件,按 Ctrl+C 停止...");
let protocols = vec![
Protocol::PumpFun,
Protocol::PumpSwap,
Protocol::Bonk,
];
let protocols = vec![Protocol::PumpFun, Protocol::PumpSwap, Protocol::Bonk];
grpc.subscribe_events(protocols, None, None, None, callback)
.await?;
@@ -371,11 +316,7 @@ async fn test_shreds() -> Result<(), Box<dyn std::error::Error>> {
// 订阅 PumpSwap 事件
println!("开始监听事件,按 Ctrl+C 停止...");
let protocols = vec![
Protocol::PumpFun,
Protocol::PumpSwap,
Protocol::Bonk,
];
let protocols = vec![Protocol::PumpFun, Protocol::PumpSwap, Protocol::Bonk];
shred_stream
.shredstream_subscribe(protocols, None, callback)
.await?;
@@ -1,7 +1,7 @@
use crate::streaming::event_parser::protocols::bonk::types::{
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 borsh::BorshDeserialize;
use serde::{Deserialize, Serialize};
@@ -2,7 +2,7 @@ use borsh::BorshDeserialize;
use serde::{Deserialize, Serialize};
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;
#[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 crate::streaming::event_parser::{
common::{utils::*, EventMetadata, EventType, ProtocolType},
common::{EventMetadata, EventType, ProtocolType},
core::traits::{EventParser, GenericEventParseConfig, GenericEventParser, UnifiedEvent},
protocols::pumpfun::{discriminators, PumpFunCreateTokenEvent, PumpFunTradeEvent},
};
@@ -2,7 +2,7 @@ use borsh::BorshDeserialize;
use serde::{Deserialize, Serialize};
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;
/// 买入事件
+2 -6
View File
@@ -4,10 +4,8 @@ use chrono::Local;
use futures::{channel::mpsc, sink::Sink, SinkExt, Stream, StreamExt};
use log::{error, info};
use rustls::crypto::{ring::default_provider, CryptoProvider};
use solana_sdk::{pubkey, pubkey::Pubkey, signature::Signature};
use solana_transaction_status::{
option_serializer::OptionSerializer, EncodedTransactionWithStatusMeta, UiTransactionEncoding,
};
use solana_sdk::{pubkey::Pubkey, signature::Signature};
use solana_transaction_status::{EncodedTransactionWithStatusMeta, UiTransactionEncoding};
use tonic::{transport::channel::ClientTlsConfig, Status};
use yellowstone_grpc_client::{GeyserGrpcClient, Interceptor};
use yellowstone_grpc_proto::geyser::{
@@ -21,8 +19,6 @@ use crate::streaming::event_parser::{EventParserFactory, Protocol, UnifiedEvent}
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 REQUEST_TIMEOUT: u64 = 60;
const CHANNEL_SIZE: usize = 1000;
-3
View File
@@ -4,9 +4,6 @@ use rand::seq::IndexedRandom;
use reqwest::Client;
use serde_json::json;
use std::{sync::Arc, time::Instant};
use solana_sdk::{signature::Signature};
use std::time::Duration;
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(())
}
+2 -21
View File
@@ -1,8 +1,5 @@
use anyhow::anyhow;
use solana_sdk::pubkey::Pubkey;
use spl_associated_token_account::get_associated_token_address;
use crate::{common::SolanaRpcClient, constants};
use crate::constants;
pub fn get_amount_out(
amount_in: u64,
@@ -56,20 +53,4 @@ pub fn get_vault_pda(pool_state: &Pubkey, mint: &Pubkey) -> Option<Pubkey> {
let program_id: &Pubkey = &constants::bonk::accounts::BONK;
let pda: Option<(Pubkey, u8)> = Pubkey::try_find_program_address(seeds, program_id);
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 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
}
+3 -1
View File
@@ -2,9 +2,11 @@ pub mod nonce_manager;
pub mod transaction_builder;
pub mod compute_budget_manager;
pub mod address_lookup_manager;
pub mod utils;
// Re-export commonly used functions
pub use nonce_manager::*;
pub use transaction_builder::*;
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_sdk::{
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 solana_sdk::signer::Signer;
use std::sync::Arc;
use super::{
@@ -13,6 +12,8 @@ use crate::{
trading::common::{build_rpc_transaction, build_sell_transaction},
};
const MAX_LOADED_ACCOUNTS_DATA_SIZE_LIMIT: u32 = 256 * 1024;
/// 通用交易执行器实现
pub struct GenericTradeExecutor {
instruction_builder: Arc<dyn InstructionBuilder>,
@@ -29,26 +30,14 @@ impl GenericTradeExecutor {
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]
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() {
return Err(anyhow!("RPC is not set"));
}
@@ -80,7 +69,10 @@ impl TradeExecutor for GenericTradeExecutor {
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("构建买入交易指令");
// 验证参数 - 转换为BuyParams进行验证
+28 -17
View File
@@ -3,9 +3,9 @@ use solana_sdk::{pubkey::Pubkey, signature::Keypair};
use std::sync::Arc;
use super::traits::ProtocolParams;
use crate::common::bonding_curve::BondingCurveAccount;
use crate::common::{PriorityFee, SolanaRpcClient};
use crate::swqos::SwqosClient;
use crate::common::bonding_curve::BondingCurveAccount;
/// 通用买入参数
#[derive(Clone)]
@@ -74,24 +74,18 @@ pub struct SellWithTipParams {
/// PumpFun协议特定参数
#[derive(Clone)]
pub struct PumpFunParams {
pub trade_type: String,
pub bonding_curve: Option<Arc<BondingCurveAccount>>,
}
impl ProtocolParams for PumpFunParams {
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn clone_box(&self) -> Box<dyn ProtocolParams> {
Box::new(self.clone())
impl PumpFunParams {
pub fn default() -> Self {
Self {
bonding_curve: None,
}
}
}
#[derive(Clone)]
pub struct PumpFunSellParams {}
impl ProtocolParams for PumpFunSellParams {
impl ProtocolParams for PumpFunParams {
fn as_any(&self) -> &dyn std::any::Any {
self
}
@@ -105,13 +99,18 @@ impl ProtocolParams for PumpFunSellParams {
#[derive(Clone)]
pub struct PumpSwapParams {
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,
}
impl PumpSwapParams {
pub fn default() -> Self {
Self {
pool: None,
auto_handle_wsol: true,
}
}
}
impl ProtocolParams for PumpSwapParams {
fn as_any(&self) -> &dyn std::any::Any {
self
@@ -132,6 +131,18 @@ pub struct BonkParams {
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 {
fn as_any(&self) -> &dyn std::any::Any {
self
-2
View File
@@ -1,7 +1,5 @@
use anyhow::Result;
use solana_sdk::instruction::Instruction;
use std::sync::Arc;
use super::params::{BuyParams, BuyWithTipParams, SellParams, SellWithTipParams};
/// 交易执行器trait - 定义了所有交易协议都需要实现的核心方法
+18 -18
View File
@@ -9,30 +9,30 @@ use super::{
/// 支持的交易协议
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Protocol {
pub enum TradingProtocol {
PumpFun,
PumpSwap,
Bonk,
}
impl std::fmt::Display for Protocol {
impl std::fmt::Display for TradingProtocol {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Protocol::PumpFun => write!(f, "PumpFun"),
Protocol::PumpSwap => write!(f, "PumpSwap"),
Protocol::Bonk => write!(f, "Bonk"),
TradingProtocol::PumpFun => write!(f, "PumpFun"),
TradingProtocol::PumpSwap => write!(f, "PumpSwap"),
TradingProtocol::Bonk => write!(f, "Bonk"),
}
}
}
impl std::str::FromStr for Protocol {
impl std::str::FromStr for TradingProtocol {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"pumpfun" => Ok(Protocol::PumpFun),
"pumpswap" => Ok(Protocol::PumpSwap),
"bonk" => Ok(Protocol::Bonk),
"pumpfun" => Ok(TradingProtocol::PumpFun),
"pumpswap" => Ok(TradingProtocol::PumpSwap),
"bonk" => Ok(TradingProtocol::Bonk),
_ => Err(anyhow!("Unsupported protocol: {}", s)),
}
}
@@ -43,17 +43,17 @@ pub struct TradeFactory;
impl TradeFactory {
/// 创建指定协议的交易执行器
pub fn create_executor(protocol: Protocol) -> Arc<dyn TradeExecutor> {
pub fn create_executor(protocol: TradingProtocol) -> Arc<dyn TradeExecutor> {
match protocol {
Protocol::PumpFun => {
TradingProtocol::PumpFun => {
let instruction_builder = Arc::new(PumpFunInstructionBuilder);
Arc::new(GenericTradeExecutor::new(instruction_builder, "PumpFun"))
}
Protocol::PumpSwap => {
TradingProtocol::PumpSwap => {
let instruction_builder = Arc::new(PumpSwapInstructionBuilder);
Arc::new(GenericTradeExecutor::new(instruction_builder, "PumpSwap"))
}
Protocol::Bonk => {
TradingProtocol::Bonk => {
let instruction_builder = Arc::new(BonkInstructionBuilder);
Arc::new(GenericTradeExecutor::new(
instruction_builder,
@@ -64,16 +64,16 @@ impl TradeFactory {
}
/// 获取所有支持的协议
pub fn supported_protocols() -> Vec<Protocol> {
pub fn supported_protocols() -> Vec<TradingProtocol> {
vec![
Protocol::PumpFun,
Protocol::PumpSwap,
Protocol::Bonk,
TradingProtocol::PumpFun,
TradingProtocol::PumpSwap,
TradingProtocol::Bonk,
]
}
/// 检查协议是否支持
pub fn is_supported(protocol: &Protocol) -> bool {
pub fn is_supported(protocol: &TradingProtocol) -> bool {
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 borsh::BorshDeserialize;
use spl_token::instruction::close_account;
use tokio::sync::RwLock;
use std::{collections::HashMap, sync::Arc};
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 crate::{
common::{
bonding_curve::BondingCurveAccount, global::GlobalAccount, PriorityFee, SolanaRpcClient
},
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! {
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]
pub fn create_priority_fee_instructions(priority_fee: PriorityFee) -> Vec<Instruction> {
let mut instructions = Vec::with_capacity(2);
@@ -108,45 +28,6 @@ pub fn create_priority_fee_instructions(priority_fee: PriorityFee) -> Vec<Instru
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]
pub fn get_global_pda() -> Pubkey {
@@ -194,18 +75,8 @@ pub fn get_metadata_pda(mint: &Pubkey) -> Pubkey {
#[inline]
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 account = rpc.get_account(&global).await?;
// let global_account = bincode::deserialize::<accounts::GlobalAccount>(&account.data)?;
let global_account = Arc::new(global_account);
// ACCOUNT_CACHE.write().await.insert(global, global_account.clone());
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)
}
#[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;
-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(())
}
+3 -41
View File
@@ -1,47 +1,9 @@
use anyhow::anyhow;
use solana_sdk::{
pubkey::Pubkey,
signature::{Keypair, Signer},
};
use crate::common::SolanaRpcClient;
use crate::trading::pumpswap;
// 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)),
}
}
use solana_sdk::pubkey::Pubkey;
// Find a pool for a specific mint
pub async fn find_pool(
rpc: &SolanaRpcClient,
mint: &Pubkey,
) -> Result<Pubkey, anyhow::Error> {
pub async fn find_pool(rpc: &SolanaRpcClient, mint: &Pubkey) -> Result<Pubkey, anyhow::Error> {
let (pool_address, _) = pumpswap::pool::Pool::find_by_mint(rpc, mint).await?;
Ok(pool_address)
}
@@ -83,4 +45,4 @@ pub(crate) fn coin_creator_vault_ata(coin_creator: Pubkey) -> Pubkey {
&crate::constants::pumpswap::accounts::TOKEN_PROGRAM,
);
associated_token_creator_vault_authority
}
}
-2
View File
@@ -1,4 +1,2 @@
pub mod buy;
pub mod sell;
pub mod common;
pub mod pool;
-1
View File
@@ -2,7 +2,6 @@ use crate::{common::SolanaRpcClient, constants::pumpswap::accounts};
use anyhow::anyhow;
use solana_account_decoder::UiAccountEncoding;
use solana_sdk::pubkey::Pubkey;
use std::str::FromStr;
#[derive(Debug, Clone)]
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)
}
}