Refactor the code to optimize performance.

This commit is contained in:
William
2025-02-15 18:53:17 +08:00
parent 7cf908ae5d
commit 38f1ce3fad
5 changed files with 189 additions and 580 deletions
+151 -175
View File
@@ -8,16 +8,12 @@ pub mod grpc;
pub mod common;
use anyhow::anyhow;
use solana_client::{connection_cache::ConnectionCache, rpc_client::RpcClient, rpc_config::{RpcSendTransactionConfig, RpcSimulateTransactionConfig}, send_and_confirm_transactions_in_parallel::{send_and_confirm_transactions_in_parallel, SendAndConfirmConfig}, tpu_client::{TpuClient, TpuClientConfig}};
use solana_client::{
rpc_client::RpcClient,
rpc_config::RpcSimulateTransactionConfig
};
use solana_sdk::{
commitment_config::CommitmentConfig,
pubkey::Pubkey,
signature::{Keypair, Signature},
signer::Signer,
instruction::Instruction,
system_instruction,
compute_budget::ComputeBudgetInstruction,
transaction::Transaction,
commitment_config::CommitmentConfig, compute_budget::ComputeBudgetInstruction, instruction::Instruction, native_token::sol_to_lamports, pubkey::Pubkey, signature::{Keypair, Signature}, signer::Signer, system_instruction, transaction::Transaction
};
use spl_associated_token_account::{
get_associated_token_address,
@@ -30,6 +26,8 @@ use spl_token::instruction::close_account;
use std::sync::Arc;
use std::time::Instant;
use std::collections::HashMap;
use tokio::sync::RwLock;
use crate::jito::JitoClient;
@@ -39,10 +37,14 @@ use borsh::BorshDeserialize;
const DEFAULT_SLIPPAGE: u64 = 1000; // 10%
const DEFAULT_COMPUTE_UNIT_LIMIT: u32 = 78000;
const DEFAULT_COMPUTE_UNIT_PRICE: u64 = 500000;
const JITO_TIP_AMOUNT: u64 = 5644005;
// const WS_URL: &str = "ws://127.0.0.1:8900";
const JITO_TIP_AMOUNT: f64 = 0.00006;
// Cache
lazy_static::lazy_static! {
static ref ACCOUNT_CACHE: RwLock<HashMap<Pubkey, Arc<accounts::GlobalAccount>>> = RwLock::new(HashMap::new());
static ref BONDING_CURVE_CACHE: RwLock<HashMap<Pubkey, Arc<accounts::BondingCurveAccount>>> = RwLock::new(HashMap::new());
}
/// Priority fee configuration
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PriorityFee {
pub limit: Option<u32>,
@@ -75,7 +77,7 @@ impl Clone for PumpFun {
}
impl PumpFun {
/// Create a new PumpFun client instance
#[inline]
pub fn new(
rpc_url: String,
commitment: Option<CommitmentConfig>,
@@ -96,10 +98,6 @@ impl PumpFun {
}
}
pub fn get_rpc(&self) -> &RpcClient {
&self.rpc
}
/// Create a new token
pub async fn create(
&self,
@@ -109,7 +107,7 @@ impl PumpFun {
) -> Result<Signature, anyhow::Error> {
let ipfs = utils::create_token_metadata(metadata)
.await
.map_err(|_| anyhow!("Failed to upload metadata"))?;
.map_err(|e| anyhow!("Failed to upload metadata: {}", e))?;
let mut instructions = self.create_priority_fee_instructions(priority_fee);
@@ -145,11 +143,15 @@ impl PumpFun {
slippage_basis_points: Option<u64>,
priority_fee: Option<PriorityFee>,
) -> Result<Signature, anyhow::Error> {
if amount_sol == 0 {
return Err(anyhow!("Amount cannot be zero"));
}
let ipfs = utils::create_token_metadata(metadata)
.await
.map_err(|e| anyhow!(e.to_string()))?;
.map_err(|e| anyhow!("Failed to upload metadata: {}", e))?;
let global_account = self.get_global_account()?;
let global_account = self.get_global_account().await?;
let buy_amount = global_account.get_initial_buy_price(amount_sol);
let buy_amount_with_slippage =
utils::calculate_with_slippage_buy(amount_sol, slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE));
@@ -207,8 +209,12 @@ impl PumpFun {
slippage_basis_points: Option<u64>,
priority_fee: Option<PriorityFee>,
) -> Result<Signature, anyhow::Error> {
let global_account = self.get_global_account()?;
let bonding_curve_account = self.get_bonding_curve_account(mint)?;
if amount_sol == 0 {
return Err(anyhow!("Amount cannot be zero"));
}
let global_account = self.get_global_account().await?;
let bonding_curve_account = self.get_bonding_curve_account(mint).await?;
let buy_amount = bonding_curve_account
.get_buy_price(amount_sol)
.map_err(|e| anyhow!(e))?;
@@ -256,19 +262,23 @@ impl PumpFun {
buy_token_amount: u64,
max_sol_cost: u64,
slippage_basis_points: Option<u64>,
jito_fee: Option<u64>,
jito_fee: Option<f64>,
) -> Result<String, anyhow::Error> {
if buy_token_amount == 0 || max_sol_cost == 0 {
return Err(anyhow!("Amount cannot be zero"));
}
let start_time = Instant::now();
let jito_client = self.jito_client.as_ref()
.ok_or_else(|| anyhow!("Jito client not found"))?;
let global_account = self.get_global_account()?;
let global_account = self.get_global_account().await?;
let buy_amount_with_slippage =
utils::calculate_with_slippage_buy(max_sol_cost, slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE));
let mut instructions = self.create_priority_fee_instructions(None);
let tip_account = jito_client.get_tip_account().await.map_err(|e| anyhow!(e)).unwrap();
let tip_account = jito_client.get_tip_account().await.map_err(|e| anyhow!(e))?;
let ata = get_associated_token_address(&self.payer.pubkey(), mint);
if self.rpc.get_account(&ata).is_err() {
instructions.push(create_associated_token_account(
@@ -294,7 +304,7 @@ impl PumpFun {
system_instruction::transfer(
&self.payer.pubkey(),
&tip_account,
jito_fee,
sol_to_lamports(jito_fee),
),
);
@@ -320,7 +330,6 @@ impl PumpFun {
slippage_basis_points: Option<u64>,
priority_fee: Option<PriorityFee>,
) -> Result<(), anyhow::Error> {
// 获取代币账户余额
let ata = get_associated_token_address(&self.payer.pubkey(), mint);
let balance = self.rpc.get_token_account_balance(&ata)?;
let balance_u64 = balance.amount.parse::<u64>()
@@ -328,12 +337,11 @@ impl PumpFun {
let amount = amount_token.unwrap_or(balance_u64);
if amount == 0 {
return Err(anyhow!("Balance is 0"));
return Err(anyhow!("Amount cannot be zero"));
}
// 计算最小SOL输出
let global_account = self.get_global_account()?;
let bonding_curve_account = self.get_bonding_curve_account(mint)?;
let global_account = self.get_global_account().await?;
let bonding_curve_account = self.get_bonding_curve_account(mint).await?;
let min_sol_output = bonding_curve_account
.get_sell_price(amount, global_account.fee_basis_points)
.map_err(|e| anyhow!(e))?;
@@ -342,7 +350,6 @@ impl PumpFun {
slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE),
);
// 构建指令
let mut instructions = vec![
ComputeBudgetInstruction::set_compute_unit_limit(1_400_000),
ComputeBudgetInstruction::set_compute_unit_price(0),
@@ -364,12 +371,10 @@ impl PumpFun {
&self.payer.pubkey(),
&self.payer.pubkey(),
&[&self.payer.pubkey()],
).unwrap());
)?);
// 获取最新区块哈希
let commitment_config = CommitmentConfig::confirmed();
let recent_blockhash = self.rpc.get_latest_blockhash_with_commitment(commitment_config)
.map_err(|_| anyhow!("Failed to get latest blockhash"))?
let recent_blockhash = self.rpc.get_latest_blockhash_with_commitment(commitment_config)?
.0;
let simulate_tx = Transaction::new_signed_with_payer(
@@ -379,7 +384,6 @@ impl PumpFun {
recent_blockhash,
);
// 模拟交易
let config = RpcSimulateTransactionConfig {
sig_verify: true,
commitment: Some(commitment_config),
@@ -393,12 +397,15 @@ impl PumpFun {
return Err(anyhow!("Simulation failed: {:?}", result.err));
}
// 更新计算单元和优先费用
let result_cu = result.units_consumed.ok_or_else(|| anyhow!("No compute units consumed"))?;
let fees = self.rpc.get_recent_prioritization_fees(&[])?;
let average_fees = fees.iter()
.map(|fee| fee.prioritization_fee)
.sum::<u64>() / fees.len() as u64;
let average_fees = if fees.is_empty() {
DEFAULT_COMPUTE_UNIT_PRICE
} else {
fees.iter()
.map(|fee| fee.prioritization_fee)
.sum::<u64>() / fees.len() as u64
};
let unit_price = match priority_fee {
None => average_fees,
@@ -417,7 +424,6 @@ impl PumpFun {
recent_blockhash,
);
// 发送交易
self.rpc.send_and_confirm_transaction(&transaction)?;
Ok(())
}
@@ -430,8 +436,8 @@ impl PumpFun {
slippage_basis_points: Option<u64>,
priority_fee: Option<PriorityFee>,
) -> Result<(), anyhow::Error> {
if percent > 100 {
return Err(anyhow!("Percentage must be between 0 and 100"));
if percent == 0 || percent > 100 {
return Err(anyhow!("Percentage must be between 1 and 100"));
}
let ata = get_associated_token_address(&self.payer.pubkey(), mint);
@@ -452,10 +458,10 @@ impl PumpFun {
mint: &Pubkey,
percent: u64,
slippage_basis_points: Option<u64>,
jito_fee: Option<u64>,
jito_fee: Option<f64>,
) -> Result<String, anyhow::Error> {
if percent > 100 {
return Err(anyhow!("Percentage must be between 0 and 100"));
if percent == 0 || percent > 100 {
return Err(anyhow!("Percentage must be between 1 and 100"));
}
let ata = get_associated_token_address(&self.payer.pubkey(), mint);
@@ -477,7 +483,7 @@ impl PumpFun {
mint: &Pubkey,
amount_token: Option<u64>,
slippage_basis_points: Option<u64>,
jito_fee: Option<u64>,
jito_fee: Option<f64>,
) -> Result<String, anyhow::Error> {
let start_time = Instant::now();
@@ -494,8 +500,8 @@ impl PumpFun {
return Err(anyhow!("Amount cannot be zero"));
}
let global_account = self.get_global_account()?;
let bonding_curve_account = self.get_bonding_curve_account(mint)?;
let global_account = self.get_global_account().await?;
let bonding_curve_account = self.get_bonding_curve_account(mint).await?;
let min_sol_output = bonding_curve_account
.get_sell_price(amount, global_account.fee_basis_points)
.map_err(|e| anyhow!(e))?;
@@ -522,13 +528,14 @@ impl PumpFun {
&self.payer.pubkey(),
&self.payer.pubkey(),
&[&self.payer.pubkey()],
).unwrap());
)?);
let jito_fee = jito_fee.unwrap_or(JITO_TIP_AMOUNT);
instructions.push(
system_instruction::transfer(
&self.payer.pubkey(),
&tip_account,
jito_fee.unwrap_or(JITO_TIP_AMOUNT/10),
sol_to_lamports(jito_fee),
),
);
@@ -546,9 +553,40 @@ impl PumpFun {
Ok(signature)
}
pub async fn transfer_sol(&self, receive_wallet: &Pubkey, amount: u64) -> Result<(), anyhow::Error> {
if amount == 0 {
return Err(anyhow!("Amount cannot be zero"));
}
let balance = self.get_payer_sol_balance()?;
if balance < amount {
return Err(anyhow!("Insufficient balance"));
}
let transfer_instruction = system_instruction::transfer(
&self.payer.pubkey(),
receive_wallet,
amount,
);
let recent_blockhash = self.rpc.get_latest_blockhash()?;
let transaction = Transaction::new_signed_with_payer(
&[transfer_instruction],
Some(&self.payer.pubkey()),
&[&self.payer.clone()],
recent_blockhash,
);
self.rpc.send_and_confirm_transaction(&transaction)?;
Ok(())
}
// Helper methods
#[inline]
fn create_priority_fee_instructions(&self, priority_fee: Option<PriorityFee>) -> Vec<Instruction> {
let mut instructions = Vec::new();
let mut instructions = Vec::with_capacity(2);
let fee = priority_fee.unwrap_or(PriorityFee::default());
if let Some(limit) = fee.limit {
instructions.push(ComputeBudgetInstruction::set_compute_unit_limit(limit));
@@ -560,11 +598,17 @@ impl PumpFun {
instructions
}
// Public interface methods
#[inline]
pub fn get_rpc(&self) -> &RpcClient {
&self.rpc
}
#[inline]
pub fn get_payer_pubkey(&self) -> Pubkey {
self.payer.pubkey()
}
#[inline]
pub fn get_token_balance(&self, account: &Pubkey, mint: &Pubkey) -> Result<u64, anyhow::Error> {
let ata = get_associated_token_address(account, mint);
if self.rpc.get_account(&ata).is_err() {
@@ -576,27 +620,38 @@ impl PumpFun {
.map_err(|_| anyhow!("Failed to parse token balance"))
}
#[inline]
pub fn get_sol_balance(&self, account: &Pubkey) -> Result<u64, anyhow::Error> {
self.rpc.get_balance(account).map_err(|_| anyhow!("Failed to get SOL balance"))
}
#[inline]
pub fn get_payer_token_balance(&self, mint: &Pubkey) -> Result<u64, anyhow::Error> {
self.get_token_balance(&self.payer.pubkey(), mint)
}
#[inline]
pub fn get_payer_sol_balance(&self) -> Result<u64, anyhow::Error> {
self.get_sol_balance(&self.payer.pubkey())
}
// PDA related methods
#[inline]
pub fn get_global_pda() -> Pubkey {
Pubkey::find_program_address(&[constants::seeds::GLOBAL_SEED], &constants::accounts::PUMPFUN).0
static GLOBAL_PDA: once_cell::sync::Lazy<Pubkey> = once_cell::sync::Lazy::new(|| {
Pubkey::find_program_address(&[constants::seeds::GLOBAL_SEED], &constants::accounts::PUMPFUN).0
});
*GLOBAL_PDA
}
#[inline]
pub fn get_mint_authority_pda() -> Pubkey {
Pubkey::find_program_address(&[constants::seeds::MINT_AUTHORITY_SEED], &constants::accounts::PUMPFUN).0
static MINT_AUTHORITY_PDA: once_cell::sync::Lazy<Pubkey> = once_cell::sync::Lazy::new(|| {
Pubkey::find_program_address(&[constants::seeds::MINT_AUTHORITY_SEED], &constants::accounts::PUMPFUN).0
});
*MINT_AUTHORITY_PDA
}
#[inline]
pub fn get_bonding_curve_pda(mint: &Pubkey) -> Option<Pubkey> {
Pubkey::try_find_program_address(
&[constants::seeds::BONDING_CURVE_SEED, mint.as_ref()],
@@ -604,6 +659,7 @@ impl PumpFun {
).map(|(pubkey, _)| pubkey)
}
#[inline]
pub fn get_metadata_pda(mint: &Pubkey) -> Pubkey {
Pubkey::find_program_address(
&[
@@ -615,26 +671,49 @@ impl PumpFun {
).0
}
// Account related methods
pub fn get_global_account(&self) -> Result<accounts::GlobalAccount, anyhow::Error> {
#[inline]
pub async fn get_global_account(&self) -> Result<Arc<accounts::GlobalAccount>, anyhow::Error> {
let global = Self::get_global_pda();
// Try cache first
if let Some(account) = ACCOUNT_CACHE.read().await.get(&global) {
return Ok(account.clone());
}
// Cache miss, fetch from RPC
let account = self.rpc.get_account(&global)?;
accounts::GlobalAccount::try_from_slice(&account.data)
.map_err(|e| anyhow!(e))
let global_account = Arc::new(accounts::GlobalAccount::try_from_slice(&account.data)?);
// Update cache
ACCOUNT_CACHE.write().await.insert(global, global_account.clone());
Ok(global_account)
}
pub fn get_bonding_curve_account(
#[inline]
pub async fn get_bonding_curve_account(
&self,
mint: &Pubkey,
) -> Result<accounts::BondingCurveAccount, anyhow::Error> {
) -> Result<Arc<accounts::BondingCurveAccount>, anyhow::Error> {
let bonding_curve_pda = Self::get_bonding_curve_pda(mint)
.ok_or(anyhow!("Bonding curve not found"))?;
// Try cache first
if let Some(account) = BONDING_CURVE_CACHE.read().await.get(&bonding_curve_pda) {
return Ok(account.clone());
}
// Cache miss, fetch from RPC
let account = self.rpc.get_account(&bonding_curve_pda)?;
accounts::BondingCurveAccount::try_from_slice(&account.data)
.map_err(|e| anyhow!(e))
let bonding_curve = Arc::new(accounts::BondingCurveAccount::try_from_slice(&account.data)?);
// Update cache
BONDING_CURVE_CACHE.write().await.insert(bonding_curve_pda, bonding_curve.clone());
Ok(bonding_curve)
}
// Subscription related methods
#[inline]
pub async fn tokens_subscription<F>(
&self,
ws_url: &str,
@@ -648,138 +727,35 @@ impl PumpFun {
logs_subscribe::tokens_subscription(ws_url, commitment, callback, bot_wallet).await
}
#[inline]
pub async fn stop_subscription(&self, subscription_handle: SubscriptionHandle) {
subscription_handle.shutdown().await;
}
pub async fn transfer_sol(&self, recieve_wallet: &Pubkey, amount: u64) -> Result<(), anyhow::Error> {
let mut instructions = vec![];
let transfer_instruction = system_instruction::transfer(
&self.payer.pubkey(), // 付款方地址
recieve_wallet, // 收款方地址
amount, // 转账金额
);
instructions.push(transfer_instruction);
let recent_blockhash = self.rpc.get_latest_blockhash()?;
let transaction = Transaction::new_signed_with_payer(
&instructions,
Some(&self.payer.pubkey()),
&[&self.payer.clone()],
recent_blockhash,
);
self.rpc.send_and_confirm_transaction(&transaction)?;
Ok(())
}
#[inline]
pub fn get_buy_amount_with_slippage(&self, amount_sol: u64, slippage_basis_points: Option<u64>) -> u64 {
utils::calculate_with_slippage_buy(amount_sol, slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE))
}
#[inline]
pub fn get_token_price(&self, virtual_sol_reserves: u64, virtual_token_reserves: u64) -> f64 {
let v_sol = virtual_sol_reserves as f64 / 100_000_000.0;
let v_tokens = virtual_token_reserves as f64 / 100_000.0;
let token_price = v_sol / v_tokens;
token_price
v_sol / v_tokens
}
#[inline]
pub fn get_buy_price(&self, amount: u64, trade_info: &TradeInfo) -> Result<u64, &'static str> {
if amount == 0 {
return Ok(0);
}
// Calculate the product of virtual reserves using u128 to avoid overflow
let n: u128 = (trade_info.virtual_sol_reserves as u128) * (trade_info.virtual_token_reserves as u128);
// Calculate the new virtual sol reserves after the purchase
let i: u128 = (trade_info.virtual_sol_reserves as u128) + (amount as u128);
// Calculate the new virtual token reserves after the purchase
let r: u128 = n / i + 1;
// Calculate the amount of tokens to be purchased
let s: u128 = (trade_info.virtual_token_reserves as u128) - r;
// Convert back to u64 and return the minimum of calculated tokens and real reserves
let s_u64 = s as u64;
Ok(if s_u64 < trade_info.real_token_reserves {
s_u64
} else {
trade_info.real_token_reserves
})
}
pub async fn get_token_price_in_usdc(&self, token_amount: f64) -> Result<f64, anyhow::Error> {
if token_amount == 0.0 {
return Ok(0.0);
}
let url = "https://api.jup.ag/price/v2?ids=So11111111111111111111111111111111111111112";
let response: serde_json::Value = reqwest::get(url)
.await
.map_err(|e: reqwest::Error| anyhow!(e))?
.json()
.await
.map_err(|e: reqwest::Error| anyhow!(e))?;
let sol_price_str = response["data"]["So11111111111111111111111111111111111111112"]["price"]
.as_str()
.ok_or(anyhow!("Failed to find SOL price as a string"))?;
let sol_price_in_usdc: f64 = sol_price_str
.parse()
.map_err(|e: std::num::ParseFloatError| anyhow!(e))?;
let token_price_in_usdc = sol_price_in_usdc * token_amount;
Ok(token_price_in_usdc)
}
pub async fn get_sol_price_in_usdc(&self) -> Result<f64, anyhow::Error> {
let url = "https://api.jup.ag/price/v2?ids=So11111111111111111111111111111111111111112";
let response: serde_json::Value = reqwest::get(url)
.await
.map_err(|_| anyhow!("Failed to install crypto provider"))?
.json()
.await
.map_err(|_| anyhow!("Failed to install crypto provider"))?;
let sol_price_str = response["data"]["So11111111111111111111111111111111111111112"]["price"]
.as_str()
.ok_or(anyhow!("Failed to find SOL price as a string"))?;
let sol_price_in_usdc: f64 = sol_price_str
.parse()
.map_err(|_| anyhow!("Failed to parse SOL price as a string"))?;
Ok(sol_price_in_usdc)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_client() {
let payer = Arc::new(Keypair::new());
let client = PumpFun::new(Cluster::Devnet, None, Arc::clone(&payer), None);
assert_eq!(client.payer.pubkey(), payer.pubkey());
}
#[test]
fn test_get_pdas() {
let mint = Keypair::new();
let global_pda = PumpFun::get_global_pda();
let mint_authority_pda = PumpFun::get_mint_authority_pda();
let bonding_curve_pda = PumpFun::get_bonding_curve_pda(&mint.pubkey());
let metadata_pda = PumpFun::get_metadata_pda(&mint.pubkey());
assert!(global_pda != Pubkey::default());
assert!(mint_authority_pda != Pubkey::default());
assert!(bonding_curve_pda.is_some());
assert!(metadata_pda != Pubkey::default());
Ok(s_u64.min(trade_info.real_token_reserves))
}
}