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
-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(())
}