From c9f0241de22e56540cc78b4607ecee75ec075253 Mon Sep 17 00:00:00 2001 From: William Date: Thu, 9 Jan 2025 00:12:43 +0800 Subject: [PATCH] fix buy --- .gitignore | 0 Cargo.toml | 0 LICENSE | 0 README.md | 0 crates/pumpfun-cpi/Cargo.toml | 0 crates/pumpfun-cpi/LICENSE | 0 crates/pumpfun-cpi/README.md | 0 crates/pumpfun-cpi/idl.classic.json | 0 crates/pumpfun-cpi/idl.json | 0 crates/pumpfun-cpi/src/lib.rs | 0 crates/pumpfun/Cargo.toml | 0 crates/pumpfun/LICENSE-APACHE | 0 crates/pumpfun/LICENSE-MIT | 0 crates/pumpfun/README.md | 0 crates/pumpfun/RUSTDOC.md | 0 crates/pumpfun/src/accounts/bonding_curve.rs | 0 crates/pumpfun/src/accounts/global.rs | 0 crates/pumpfun/src/accounts/mod.rs | 0 crates/pumpfun/src/constants/mod.rs | 0 crates/pumpfun/src/error/mod.rs | 14 + crates/pumpfun/src/instruction/logs_data.rs | 0 crates/pumpfun/src/instruction/logs_events.rs | 0 .../pumpfun/src/instruction/logs_filters.rs | 0 crates/pumpfun/src/instruction/logs_parser.rs | 0 .../pumpfun/src/instruction/logs_subscribe.rs | 0 crates/pumpfun/src/instruction/mod.rs | 0 crates/pumpfun/src/jito/mod.rs | 106 +-- crates/pumpfun/src/lib.rs | 300 ++++--- crates/pumpfun/src/sdk.rs | 730 ++++++++++++++++++ crates/pumpfun/src/utils/mod.rs | 0 30 files changed, 949 insertions(+), 201 deletions(-) mode change 100644 => 100755 .gitignore mode change 100644 => 100755 Cargo.toml mode change 100644 => 100755 LICENSE mode change 100644 => 100755 README.md mode change 100644 => 100755 crates/pumpfun-cpi/Cargo.toml mode change 100644 => 100755 crates/pumpfun-cpi/LICENSE mode change 100644 => 100755 crates/pumpfun-cpi/README.md mode change 100644 => 100755 crates/pumpfun-cpi/idl.classic.json mode change 100644 => 100755 crates/pumpfun-cpi/idl.json mode change 100644 => 100755 crates/pumpfun-cpi/src/lib.rs mode change 100644 => 100755 crates/pumpfun/Cargo.toml mode change 100644 => 100755 crates/pumpfun/LICENSE-APACHE mode change 100644 => 100755 crates/pumpfun/LICENSE-MIT mode change 100644 => 100755 crates/pumpfun/README.md mode change 100644 => 100755 crates/pumpfun/RUSTDOC.md mode change 100644 => 100755 crates/pumpfun/src/accounts/bonding_curve.rs mode change 100644 => 100755 crates/pumpfun/src/accounts/global.rs mode change 100644 => 100755 crates/pumpfun/src/accounts/mod.rs mode change 100644 => 100755 crates/pumpfun/src/constants/mod.rs mode change 100644 => 100755 crates/pumpfun/src/error/mod.rs mode change 100644 => 100755 crates/pumpfun/src/instruction/logs_data.rs mode change 100644 => 100755 crates/pumpfun/src/instruction/logs_events.rs mode change 100644 => 100755 crates/pumpfun/src/instruction/logs_filters.rs mode change 100644 => 100755 crates/pumpfun/src/instruction/logs_parser.rs mode change 100644 => 100755 crates/pumpfun/src/instruction/logs_subscribe.rs mode change 100644 => 100755 crates/pumpfun/src/instruction/mod.rs mode change 100644 => 100755 crates/pumpfun/src/jito/mod.rs mode change 100644 => 100755 crates/pumpfun/src/lib.rs create mode 100644 crates/pumpfun/src/sdk.rs mode change 100644 => 100755 crates/pumpfun/src/utils/mod.rs diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 diff --git a/Cargo.toml b/Cargo.toml old mode 100644 new mode 100755 diff --git a/LICENSE b/LICENSE old mode 100644 new mode 100755 diff --git a/README.md b/README.md old mode 100644 new mode 100755 diff --git a/crates/pumpfun-cpi/Cargo.toml b/crates/pumpfun-cpi/Cargo.toml old mode 100644 new mode 100755 diff --git a/crates/pumpfun-cpi/LICENSE b/crates/pumpfun-cpi/LICENSE old mode 100644 new mode 100755 diff --git a/crates/pumpfun-cpi/README.md b/crates/pumpfun-cpi/README.md old mode 100644 new mode 100755 diff --git a/crates/pumpfun-cpi/idl.classic.json b/crates/pumpfun-cpi/idl.classic.json old mode 100644 new mode 100755 diff --git a/crates/pumpfun-cpi/idl.json b/crates/pumpfun-cpi/idl.json old mode 100644 new mode 100755 diff --git a/crates/pumpfun-cpi/src/lib.rs b/crates/pumpfun-cpi/src/lib.rs old mode 100644 new mode 100755 diff --git a/crates/pumpfun/Cargo.toml b/crates/pumpfun/Cargo.toml old mode 100644 new mode 100755 diff --git a/crates/pumpfun/LICENSE-APACHE b/crates/pumpfun/LICENSE-APACHE old mode 100644 new mode 100755 diff --git a/crates/pumpfun/LICENSE-MIT b/crates/pumpfun/LICENSE-MIT old mode 100644 new mode 100755 diff --git a/crates/pumpfun/README.md b/crates/pumpfun/README.md old mode 100644 new mode 100755 diff --git a/crates/pumpfun/RUSTDOC.md b/crates/pumpfun/RUSTDOC.md old mode 100644 new mode 100755 diff --git a/crates/pumpfun/src/accounts/bonding_curve.rs b/crates/pumpfun/src/accounts/bonding_curve.rs old mode 100644 new mode 100755 diff --git a/crates/pumpfun/src/accounts/global.rs b/crates/pumpfun/src/accounts/global.rs old mode 100644 new mode 100755 diff --git a/crates/pumpfun/src/accounts/mod.rs b/crates/pumpfun/src/accounts/mod.rs old mode 100644 new mode 100755 diff --git a/crates/pumpfun/src/constants/mod.rs b/crates/pumpfun/src/constants/mod.rs old mode 100644 new mode 100755 diff --git a/crates/pumpfun/src/error/mod.rs b/crates/pumpfun/src/error/mod.rs old mode 100644 new mode 100755 index 517ba36..656a62e --- a/crates/pumpfun/src/error/mod.rs +++ b/crates/pumpfun/src/error/mod.rs @@ -24,6 +24,7 @@ use anchor_client::solana_client::{ client_error::ClientError as SolanaClientError, pubsub_client::PubsubClientError }; +use solana_sdk::pubkey::ParsePubkeyError; #[derive(Debug)] pub enum ClientError { @@ -58,6 +59,8 @@ pub enum ClientError { Parse(String, String), + Pubkey(String, String), + Jito(String, String), Join(String), @@ -103,6 +106,7 @@ impl std::fmt::Display for ClientError { Self::Jito(msg, details) => write!(f, "Jito error: {}, details: {}", msg, details), Self::Redis(msg, details) => write!(f, "Redis error: {}, details: {}", msg, details), Self::Join(msg) => write!(f, "Task join error: {}", msg), + Self::Pubkey(msg, details) => write!(f, "Pubkey error: {}, details: {}", msg, details), Self::Subscribe(msg, details) => write!(f, "Subscribe error: {}, details: {}", msg, details), Self::Send(msg, details) => write!(f, "Send error: {}, details: {}", msg, details), Self::Other(msg) => write!(f, "Other error: {}", msg), @@ -129,6 +133,7 @@ impl std::error::Error for ClientError { Self::Parse(_, _) => None, Self::Jito(_, _) => None, Self::Join(_) => None, + Self::Pubkey(_, _) => None, Self::Subscribe(_, _) => None, Self::Send(_, _) => None, Self::Other(_) => None, @@ -161,6 +166,15 @@ impl From for ClientError { } } +impl From for ClientError { + fn from(error: ParsePubkeyError) -> Self { + ClientError::Pubkey( + "Pubkey error".to_string(), + error.to_string(), + ) + } +} + impl From for ClientError { fn from(err: Error) -> Self { ClientError::Parse( diff --git a/crates/pumpfun/src/instruction/logs_data.rs b/crates/pumpfun/src/instruction/logs_data.rs old mode 100644 new mode 100755 diff --git a/crates/pumpfun/src/instruction/logs_events.rs b/crates/pumpfun/src/instruction/logs_events.rs old mode 100644 new mode 100755 diff --git a/crates/pumpfun/src/instruction/logs_filters.rs b/crates/pumpfun/src/instruction/logs_filters.rs old mode 100644 new mode 100755 diff --git a/crates/pumpfun/src/instruction/logs_parser.rs b/crates/pumpfun/src/instruction/logs_parser.rs old mode 100644 new mode 100755 diff --git a/crates/pumpfun/src/instruction/logs_subscribe.rs b/crates/pumpfun/src/instruction/logs_subscribe.rs old mode 100644 new mode 100755 diff --git a/crates/pumpfun/src/instruction/mod.rs b/crates/pumpfun/src/instruction/mod.rs old mode 100644 new mode 100755 diff --git a/crates/pumpfun/src/jito/mod.rs b/crates/pumpfun/src/jito/mod.rs old mode 100644 new mode 100755 index bd191b7..0a8213a --- a/crates/pumpfun/src/jito/mod.rs +++ b/crates/pumpfun/src/jito/mod.rs @@ -1,16 +1,22 @@ -use { - bincode, bs58, reqwest, serde::Deserialize, serde_json::{json, Value}, - std::{str::FromStr, time::Duration} -}; - +use rand::Rng; +use bincode; +use bs58; +use reqwest; +use serde::Deserialize; +use serde_json::{json, Value}; +use std::str::FromStr; +use std::time::Duration; +use tokio::sync::Mutex; use anchor_client::solana_sdk::{ - commitment_config::CommitmentConfig, pubkey::Pubkey, signature::Signature, transaction::Transaction + commitment_config::CommitmentConfig, + pubkey::Pubkey, + signature::Signature, + transaction::Transaction, }; - use crate::error::ClientError; -pub const MAX_RETRIES: u8 = 3; -pub const RETRY_DELAY: Duration = Duration::from_millis(200); +pub const MAX_RETRIES: u8 = 3; +pub const RETRY_DELAY: Duration = Duration::from_millis(200); #[derive(Debug, Clone)] pub struct TransactionConfig { @@ -31,7 +37,7 @@ impl Default for TransactionConfig { } } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct JitoClient { endpoint: String, client: reqwest::Client, @@ -49,26 +55,25 @@ impl JitoClient { pub async fn get_tip_account(&self) -> Result { let response = self.send_request("getTipAccounts", json!([])).await?; - + if let Some(accounts) = response["result"].as_array() { if accounts.is_empty() { - return Err(ClientError::Other( - "No JITO tip accounts found".to_string() - )); + return Err(ClientError::Other("No JITO tip accounts found".to_string())); } - - let random_index = rand::random::() % accounts.len(); + + let random_index = rand::rngs::OsRng.gen_range(0..accounts.len()); if let Some(account) = accounts.get(random_index) { if let Some(address) = account.as_str() { - return Pubkey::from_str(address) - .map_err(|e| ClientError::Parse( + return Pubkey::from_str(address).map_err(|e| { + ClientError::Parse( "Invalid tip account address".to_string(), - e.to_string() - )); + e.to_string(), + ) + }); } } } - + Err(ClientError::Other("Failed to get Tip Account".to_string())) } @@ -83,19 +88,20 @@ impl JitoClient { }); let response = self.send_request("qn_estimatePriorityFees", params).await?; - + if let Some(result) = response.get("result") { - let estimate: PriorityFeeEstimate = serde_json::from_value(result.clone()) - .map_err(|e| ClientError::Parse( + let estimate: PriorityFeeEstimate = serde_json::from_value(result.clone()).map_err(|e| { + ClientError::Parse( "Failed to parse priority fee estimate".to_string(), - e.to_string() - ))?; - + e.to_string(), + ) + })?; + Ok(estimate) } else { Err(ClientError::Parse( "Invalid response format".to_string(), - "Missing result field".to_string() + "Missing result field".to_string(), )) } } @@ -104,23 +110,25 @@ impl JitoClient { &self, transaction: &Transaction, ) -> Result { - let wire_transaction = bincode::serialize(transaction).map_err(|e| + let wire_transaction = bincode::serialize(transaction).map_err(|e| { ClientError::Parse( "Transaction serialization failed".to_string(), - e.to_string() - ))?; - + e.to_string(), + ) + })?; + let encoded_tx = bs58::encode(&wire_transaction).into_string(); for retry in 0..MAX_RETRIES { match self.try_send_transaction(&encoded_tx).await { Ok(signature) => { - return Ok(Signature::from_str(&signature).map_err(|e| + return Ok(Signature::from_str(&signature).map_err(|e| { ClientError::Parse( "Invalid signature".to_string(), - e.to_string() - ))?); - }, + e.to_string(), + ) + })?); + } Err(e) => { println!("Retry {} failed: {:?}", retry, e); if retry == MAX_RETRIES - 1 { @@ -130,7 +138,7 @@ impl JitoClient { } } } - + Err(ClientError::Other("Max retries exceeded".to_string())) } @@ -146,17 +154,14 @@ impl JitoClient { } ]); - let response = self.send_request( - "sendTransaction", - params - ).await?; + let response = self.send_request("sendTransaction", params).await?; response["result"] .as_str() .map(|s| s.to_string()) .ok_or_else(|| ClientError::Parse( "Invalid response format".to_string(), - "Missing result field".to_string() + "Missing result field".to_string(), )) } @@ -176,19 +181,20 @@ impl JitoClient { .await .map_err(|e| ClientError::Solana( "Request failed".to_string(), - e.to_string() + e.to_string(), ))?; - let response_data: Value = response.json().await - .map_err(|e| ClientError::Parse( + let response_data: Value = response.json().await.map_err(|e| { + ClientError::Parse( "Invalid JSON response".to_string(), - e.to_string() - ))?; - + e.to_string(), + ) + })?; + if let Some(error) = response_data.get("error") { return Err(ClientError::Solana( "RPC error".to_string(), - error.to_string() + error.to_string(), )); } @@ -209,4 +215,4 @@ pub struct PriorityFeeLevel { pub high: u64, // 80th percentile pub medium: u64, // 60th percentile pub low: u64, // 40th percentile -} +} \ No newline at end of file diff --git a/crates/pumpfun/src/lib.rs b/crates/pumpfun/src/lib.rs old mode 100644 new mode 100755 index 63959b4..6f71bda --- a/crates/pumpfun/src/lib.rs +++ b/crates/pumpfun/src/lib.rs @@ -1,4 +1,4 @@ -// #![doc = include_str!("../RUSTDOC.md")] +#![doc = include_str!("../RUSTDOC.md")] pub mod accounts; pub mod constants; @@ -19,8 +19,8 @@ use anchor_client::{ compute_budget::ComputeBudgetInstruction, transaction::Transaction, }, - Client, Cluster, Program, }; +use anchor_client::Cluster; use anchor_spl::associated_token::{ get_associated_token_address, spl_associated_token_account::instruction::create_associated_token_account, @@ -37,77 +37,74 @@ pub use pumpfun_cpi as cpi; use crate::jito::JitoClient; use crate::error::ClientError; -// 常量定义 +// Constants const DEFAULT_SLIPPAGE: u64 = 500; // 10% const DEFAULT_COMPUTE_UNIT_LIMIT: u32 = 68_000; const DEFAULT_COMPUTE_UNIT_PRICE: u64 = 400_000; -/// 优先费用配置 +/// Priority fee configuration #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct PriorityFee { pub limit: Option, pub price: Option, } -/// PumpFun 客户端 pub struct PumpFun { pub rpc: RpcClient, pub payer: Arc, - pub client: Client>, pub jito_client: Option, - pub program: Program>, +} + +impl Clone for PumpFun { + fn clone(&self) -> Self { + Self { + rpc: RpcClient::new_with_commitment( + self.rpc.url().to_string(), + self.rpc.commitment() + ), + payer: self.payer.clone(), + jito_client: self.jito_client.clone(), + } + } } impl PumpFun { - // 创建新实例 + /// Create a new PumpFun client instance pub fn new( cluster: Cluster, - jito_url: Option, + commitment: Option, payer: Arc, - options: Option, - ws: Option, + jito_url: Option, ) -> Self { - let rpc = RpcClient::new(if ws.unwrap_or(false) { - cluster.ws_url() - } else { - cluster.url() - }); + let rpc = RpcClient::new_with_commitment( + cluster.url(), + commitment.unwrap_or(CommitmentConfig::confirmed()) + ); let jito_client = jito_url.map(|url| JitoClient::new(&url)); - let client = if let Some(options) = options { - Client::new_with_options(cluster.clone(), payer.clone(), options) - } else { - Client::new(cluster.clone(), payer.clone()) - }; - - let program = client.program(cpi::ID).unwrap(); - Self { rpc, payer, jito_client, - client, - program, } } - // 创建代币 + /// Create a new token pub async fn create( &self, mint: &Keypair, metadata: utils::CreateTokenMetadata, priority_fee: Option, - ) -> Result { + ) -> Result { let ipfs = utils::create_token_metadata(metadata) .await - .map_err(error::ClientError::UploadMetadataError)?; + .map_err(ClientError::UploadMetadataError)?; - let mut request = self.program.request(); - request = self.add_priority_fee(request, priority_fee); + let mut instructions = self.create_priority_fee_instructions(priority_fee); - request = request.instruction(instruction::create( - &self.payer.clone().as_ref(), + instructions.push(instruction::create( + self.payer.as_ref(), mint, cpi::instruction::Create { _name: ipfs.metadata.name, @@ -116,17 +113,20 @@ impl PumpFun { }, )); - request = request.signer(&self.payer).signer(mint); + let recent_blockhash = self.rpc.get_latest_blockhash()?; + let transaction = Transaction::new_signed_with_payer( + &instructions, + Some(&self.payer.pubkey()), + &[&self.payer.clone(), mint], + recent_blockhash, + ); - let signature = request - .send() - .await - .map_err(error::ClientError::AnchorClientError)?; + let signature = self.rpc.send_and_confirm_transaction(&transaction)?; Ok(signature) } - // 创建并购买代币 + /// Create and buy tokens in one transaction pub async fn create_and_buy( &self, mint: &Keypair, @@ -134,22 +134,20 @@ impl PumpFun { amount_sol: u64, slippage_basis_points: Option, priority_fee: Option, - ) -> Result { + ) -> Result { let ipfs = utils::create_token_metadata(metadata) .await - .map_err(error::ClientError::UploadMetadataError)?; + .map_err(ClientError::UploadMetadataError)?; let global_account = self.get_global_account()?; 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)); - let mut request = self.program.request(); + let mut instructions = self.create_priority_fee_instructions(priority_fee); - request = self.add_priority_fee(request, priority_fee); - - request = request.instruction(instruction::create( - &self.payer.clone().as_ref(), + instructions.push(instruction::create( + self.payer.as_ref(), mint, cpi::instruction::Create { _name: ipfs.metadata.name, @@ -160,7 +158,7 @@ impl PumpFun { let ata = get_associated_token_address(&self.payer.pubkey(), &mint.pubkey()); if self.rpc.get_account(&ata).is_err() { - request = request.instruction(create_associated_token_account( + instructions.push(create_associated_token_account( &self.payer.pubkey(), &self.payer.pubkey(), &mint.pubkey(), @@ -168,8 +166,8 @@ impl PumpFun { )); } - request = request.instruction(instruction::buy( - &self.payer.clone().as_ref(), + instructions.push(instruction::buy( + self.payer.as_ref(), &mint.pubkey(), &global_account.fee_recipient, cpi::instruction::Buy { @@ -178,39 +176,40 @@ impl PumpFun { }, )); - let signature = request - .signer(&self.payer) - .signer(mint) - .send() - .await - .map_err(error::ClientError::AnchorClientError)?; + let recent_blockhash = self.rpc.get_latest_blockhash()?; + let transaction = Transaction::new_signed_with_payer( + &instructions, + Some(&self.payer.pubkey()), + &[&self.payer.clone(), mint], + recent_blockhash, + ); + + let signature = self.rpc.send_and_confirm_transaction(&transaction)?; Ok(signature) } - // 购买代币 + /// Buy tokens pub async fn buy( &self, mint: &Pubkey, amount_sol: u64, slippage_basis_points: Option, priority_fee: Option, - ) -> Result { + ) -> Result { let global_account = self.get_global_account()?; let bonding_curve_account = self.get_bonding_curve_account(mint)?; let buy_amount = bonding_curve_account .get_buy_price(amount_sol) - .map_err(error::ClientError::BondingCurveError)?; + .map_err(ClientError::BondingCurveError)?; let buy_amount_with_slippage = utils::calculate_with_slippage_buy(amount_sol, slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE)); - let mut request = self.program.request(); - - request = self.add_priority_fee(request, priority_fee); + let mut instructions = self.create_priority_fee_instructions(priority_fee); let ata = get_associated_token_address(&self.payer.pubkey(), mint); if self.rpc.get_account(&ata).is_err() { - request = request.instruction(create_associated_token_account( + instructions.push(create_associated_token_account( &self.payer.pubkey(), &self.payer.pubkey(), mint, @@ -218,8 +217,8 @@ impl PumpFun { )); } - request = request.instruction(instruction::buy( - &self.payer.clone().as_ref(), + instructions.push(instruction::buy( + &self.payer.clone(), mint, &global_account.fee_recipient, cpi::instruction::Buy { @@ -228,35 +227,39 @@ impl PumpFun { }, )); - let signature = request - .signer(&self.payer) - .send() - .await - .map_err(error::ClientError::AnchorClientError)?; + 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, + ); + + let signature = self.rpc.send_transaction(&transaction)?; Ok(signature) } - // 使用 Jito 购买代币 + /// Buy tokens using Jito pub async fn buy_with_jito( &self, mint: &Pubkey, amount_sol: u64, slippage_basis_points: Option, priority_fee: Option, - ) -> Result { + ) -> Result { let start_time = Instant::now(); - let jito_client = self.jito_client.as_ref().ok_or_else(|| - ClientError::Other("Jito client not found".to_string()) - )?; + let jito_client = self.jito_client.as_ref() + .ok_or_else(|| ClientError::Other("Jito client not found".to_string()))?; let global_account = self.get_global_account()?; - let bonding_curve_pda = Self::get_bonding_curve_pda(mint).unwrap(); + let bonding_curve_pda = Self::get_bonding_curve_pda(mint) + .ok_or(ClientError::BondingCurveNotFound)?; let bonding_curve_account = self.get_bonding_curve_account(mint)?; let buy_amount = bonding_curve_account .get_buy_price(amount_sol) - .map_err(error::ClientError::BondingCurveError)?; + .map_err(ClientError::BondingCurveError)?; let buy_amount_with_slippage = utils::calculate_with_slippage_buy(amount_sol, slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE)); @@ -277,7 +280,7 @@ impl PumpFun { } instructions.push(instruction::buy( - &self.payer.clone().as_ref(), + self.payer.as_ref(), mint, &global_account.fee_recipient, cpi::instruction::Buy { @@ -310,17 +313,18 @@ impl PumpFun { Ok(signature) } - // 出售代币 + /// Sell tokens pub async fn sell( &self, mint: &Pubkey, amount_token: Option, slippage_basis_points: Option, priority_fee: Option, - ) -> Result { + ) -> Result { 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::().unwrap(); + let balance_u64 = balance.amount.parse::() + .map_err(|_| ClientError::Other("Failed to parse token balance".to_string()))?; let amount = amount_token.unwrap_or(balance_u64); if amount == 0 { @@ -331,18 +335,16 @@ impl PumpFun { let bonding_curve_account = self.get_bonding_curve_account(mint)?; let min_sol_output = bonding_curve_account .get_sell_price(amount, global_account.fee_basis_points) - .map_err(error::ClientError::BondingCurveError)?; + .map_err(ClientError::BondingCurveError)?; let min_sol_output_with_slippage = utils::calculate_with_slippage_sell( min_sol_output, slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE), ); - let mut request = self.program.request(); + let mut instructions = self.create_priority_fee_instructions(priority_fee); - request = self.add_priority_fee(request, priority_fee); - - request = request.instruction(instruction::sell( - &self.payer.clone().as_ref(), + instructions.push(instruction::sell( + self.payer.as_ref(), mint, &global_account.fee_recipient, cpi::instruction::Sell { @@ -351,26 +353,35 @@ impl PumpFun { }, )); - let signature = request - .signer(&self.payer) - .send() - .await - .map_err(error::ClientError::AnchorClientError)?; + 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, + ); + + let signature = self.rpc.send_and_confirm_transaction(&transaction)?; Ok(signature) } - // 按百分比出售代币 + /// Sell tokens by percentage pub async fn sell_by_percent( &self, mint: &Pubkey, percent: u64, slippage_basis_points: Option, priority_fee: Option, - ) -> Result { + ) -> Result { + if percent > 100 { + return Err(ClientError::Other("Percentage must be between 0 and 100".to_string())); + } + 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::().unwrap(); + let balance_u64 = balance.amount.parse::() + .map_err(|_| ClientError::Other("Failed to parse token balance".to_string()))?; if balance_u64 == 0 { return Err(ClientError::Other("Balance is 0".to_string())); @@ -380,31 +391,36 @@ impl PumpFun { self.sell(mint, Some(amount), slippage_basis_points, priority_fee).await } - // 使用 Jito 出售代币 + /// Sell tokens using Jito pub async fn sell_with_jito( &self, mint: &Pubkey, amount_token: Option, slippage_basis_points: Option, priority_fee: Option, - ) -> Result { + ) -> Result { let start_time = Instant::now(); - let jito_client = self.jito_client.as_ref().ok_or_else(|| - ClientError::Other("Jito client not found".to_string()) - )?; + let jito_client = self.jito_client.as_ref() + .ok_or_else(|| ClientError::Other("Jito client not found".to_string()))?; 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::().unwrap(); + let balance_u64 = balance.amount.parse::() + .map_err(|_| ClientError::Other("Failed to parse token balance".to_string()))?; let amount = amount_token.unwrap_or(balance_u64); + if amount == 0 { + return Err(ClientError::Other("Amount cannot be zero".to_string())); + } + let global_account = self.get_global_account()?; - let bonding_curve_pda = Self::get_bonding_curve_pda(mint).unwrap(); + let bonding_curve_pda = Self::get_bonding_curve_pda(mint) + .ok_or(ClientError::BondingCurveNotFound)?; let bonding_curve_account = self.get_bonding_curve_account(mint)?; let min_sol_output = bonding_curve_account .get_sell_price(amount, global_account.fee_basis_points) - .map_err(error::ClientError::BondingCurveError)?; + .map_err(ClientError::BondingCurveError)?; let min_sol_output_with_slippage = utils::calculate_with_slippage_sell( min_sol_output, slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE), @@ -417,7 +433,7 @@ impl PumpFun { let tip_account = jito_client.get_tip_account().await?; instructions.push(instruction::sell( - &self.payer.clone().as_ref(), + self.payer.as_ref(), mint, &global_account.fee_recipient, cpi::instruction::Sell { @@ -450,34 +466,7 @@ impl PumpFun { Ok(signature) } - // 辅助方法 - fn add_priority_fee<'a>( - &self, - request: anchor_client::RequestBuilder<'a, Arc>, - priority_fee: Option, - ) -> anchor_client::RequestBuilder<'a, Arc> { - let mut request = request; - if let Some(fee) = priority_fee { - if let Some(limit) = fee.limit { - request = request.instruction(ComputeBudgetInstruction::set_compute_unit_limit(limit)); - } - if let Some(price) = fee.price { - request = request.instruction(ComputeBudgetInstruction::set_compute_unit_price(price)); - } - } - request - } - - fn get_compute_units(&self, priority_fee: Option) -> (u32, u64) { - let unit_limit = priority_fee - .and_then(|fee| fee.limit) - .unwrap_or(DEFAULT_COMPUTE_UNIT_LIMIT); - let unit_price = priority_fee - .and_then(|fee| fee.price) - .unwrap_or(DEFAULT_COMPUTE_UNIT_PRICE); - (unit_limit, unit_price) - } - + // Helper methods fn create_priority_fee_instructions(&self, priority_fee: Option) -> Vec { let mut instructions = Vec::new(); if let Some(fee) = priority_fee { @@ -491,35 +480,46 @@ impl PumpFun { instructions } + fn get_compute_units(&self, priority_fee: Option) -> (u32, u64) { + let unit_limit = priority_fee + .and_then(|fee| fee.limit) + .unwrap_or(DEFAULT_COMPUTE_UNIT_LIMIT); + let unit_price = priority_fee + .and_then(|fee| fee.price) + .unwrap_or(DEFAULT_COMPUTE_UNIT_PRICE); + (unit_limit, unit_price) + } + fn calculate_priority_fee(&self, priority_fee_per_cu: u64, unit_limit: u32) -> u64 { let total_priority_fee_microlamports = priority_fee_per_cu as u128 * unit_limit as u128; (total_priority_fee_microlamports / 1_000_000) as u64 } - // 公共接口方法 + // Public interface methods pub fn get_payer_pubkey(&self) -> Pubkey { self.payer.pubkey() } - pub fn get_token_balance(&self, account: &Pubkey, mint: &Pubkey) -> Result { + pub fn get_token_balance(&self, account: &Pubkey, mint: &Pubkey) -> Result { let ata = get_associated_token_address(account, mint); let balance = self.rpc.get_token_account_balance(&ata)?; - Ok(balance.amount.parse::().unwrap()) + balance.amount.parse::() + .map_err(|_| ClientError::Other("Failed to parse token balance".to_string())) } - pub fn get_sol_balance(&self, account: &Pubkey) -> Result { - self.rpc.get_balance(account).map_err(error::ClientError::SolanaClientError) + pub fn get_sol_balance(&self, account: &Pubkey) -> Result { + self.rpc.get_balance(account).map_err(ClientError::SolanaClientError) } - pub fn get_payer_token_balance(&self, mint: &Pubkey) -> Result { + pub fn get_payer_token_balance(&self, mint: &Pubkey) -> Result { self.get_token_balance(&self.payer.pubkey(), mint) } - pub fn get_payer_sol_balance(&self) -> Result { + pub fn get_payer_sol_balance(&self) -> Result { self.get_sol_balance(&self.payer.pubkey()) } - // PDA 相关方法 + // PDA related methods pub fn get_global_pda() -> Pubkey { Pubkey::find_program_address(&[constants::seeds::GLOBAL_SEED], &cpi::ID).0 } @@ -546,28 +546,26 @@ impl PumpFun { ).0 } - // 账户相关方法 - pub fn get_global_account(&self) -> Result { + // Account related methods + pub fn get_global_account(&self) -> Result { let global = Self::get_global_pda(); - let account = self.rpc.get_account(&global) - .map_err(error::ClientError::SolanaClientError)?; + let account = self.rpc.get_account(&global)?; accounts::GlobalAccount::try_from_slice(&account.data) - .map_err(error::ClientError::BorshError) + .map_err(ClientError::BorshError) } pub fn get_bonding_curve_account( &self, mint: &Pubkey, - ) -> Result { + ) -> Result { let bonding_curve_pda = Self::get_bonding_curve_pda(mint) - .ok_or(error::ClientError::BondingCurveNotFound)?; - let account = self.rpc.get_account(&bonding_curve_pda) - .map_err(error::ClientError::SolanaClientError)?; + .ok_or(ClientError::BondingCurveNotFound)?; + let account = self.rpc.get_account(&bonding_curve_pda)?; accounts::BondingCurveAccount::try_from_slice(&account.data) - .map_err(error::ClientError::BorshError) + .map_err(ClientError::BorshError) } - // 订阅相关方法 + // Subscription related methods pub async fn tokens_subscription( &self, ws_url: &str, @@ -593,7 +591,7 @@ mod tests { #[test] fn test_new_client() { let payer = Arc::new(Keypair::new()); - let client = PumpFun::new(Cluster::Devnet, None, Arc::clone(&payer), None, None); + let client = PumpFun::new(Cluster::Devnet, None, Arc::clone(&payer), None); assert_eq!(client.payer.pubkey(), payer.pubkey()); } diff --git a/crates/pumpfun/src/sdk.rs b/crates/pumpfun/src/sdk.rs new file mode 100644 index 0000000..d9c9ba2 --- /dev/null +++ b/crates/pumpfun/src/sdk.rs @@ -0,0 +1,730 @@ +use { + super::jito::JitoClient, crate::{InfrastructureError, InfrastructureResult}, borsh::{BorshDeserialize, BorshSerialize}, solana_client::rpc_client::RpcClient, solana_sdk::{ + commitment_config::CommitmentConfig, compute_budget::ComputeBudgetInstruction, instruction::{AccountMeta, Instruction}, pubkey::Pubkey, signature::{Keypair, Signature, Signer}, system_instruction, system_program, sysvar, transaction::Transaction + }, spl_associated_token_account::{get_associated_token_address, instruction::create_associated_token_account}, spl_token, std::str::FromStr +}; + +const PROGRAM_ID: &str = "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"; +const DEFAULT_SLIPPAGE: u64 = 500; // 10% +const DEFAULT_COMPUTE_UNIT_LIMIT: u32 = 68_000; +const DEFAULT_COMPUTE_UNIT_PRICE: u64 = 400_000; +pub struct PumpFunSDK { + client: RpcClient, + jito_client: Option, +} + +impl Clone for PumpFunSDK { + fn clone(&self) -> Self { + Self { + client: RpcClient::new_with_commitment( + self.client.url().to_string(), + self.client.commitment() + ), + jito_client: self.jito_client.clone(), + } + } +} + +#[derive(BorshDeserialize, BorshSerialize)] +pub struct GlobalAccount { + pub discriminator: u64, + pub initialized: bool, + pub authority: Pubkey, + pub fee_recipient: Pubkey, + pub initial_virtual_token_reserves: u64, + pub initial_virtual_sol_reserves: u64, + pub initial_real_token_reserves: u64, + pub token_total_supply: u64, + pub fee_basis_points: u64, +} + +impl GlobalAccount { + pub fn get_initial_buy_price(&self, amount: u64) -> u64 { + if amount == 0 { + return 0; + } + + let n = self.initial_virtual_sol_reserves + .checked_mul(self.initial_virtual_token_reserves) + .unwrap_or(0); + + let i = self.initial_virtual_sol_reserves + .checked_add(amount) + .unwrap_or(u64::MAX); + + let r = n.checked_div(i) + .map(|x| x.checked_add(1).unwrap_or(u64::MAX)) + .unwrap_or(0); + + let s = self.initial_virtual_token_reserves + .checked_sub(r) + .unwrap_or(0); + + if s < self.initial_real_token_reserves { + s + } else { + self.initial_real_token_reserves + } + } + + pub fn from_buffer(buffer: &[u8]) -> InfrastructureResult { + Self::try_from_slice(buffer) + .map_err(|e| InfrastructureError::Other(e.to_string())) + } +} + +#[derive(BorshDeserialize, BorshSerialize)] +pub struct BondingCurveAccount { + pub discriminator: u64, + pub virtual_token_reserves: u64, + pub virtual_sol_reserves: u64, + pub real_token_reserves: u64, + pub real_sol_reserves: u64, + pub token_total_supply: u64, + pub complete: bool, +} + +impl BondingCurveAccount { + pub fn get_buy_price(&self, amount: u64) -> InfrastructureResult { + if self.complete { + return Err(InfrastructureError::Other("Curve is complete".to_string())); + } + + if amount == 0 { + return Ok(0); + } + + // 使用 u128 进行计算,模仿 TypeScript 中的 bigint 计算 + let virtual_sol = self.virtual_sol_reserves as u128; + let virtual_token = self.virtual_token_reserves as u128; + let amount = amount as u128; + + // n = virtualSolReserves * virtualTokenReserves + let n = virtual_sol + .checked_mul(virtual_token) + .ok_or_else(|| InfrastructureError::Other("Overflow in virtual reserves multiplication".to_string()))?; + + // i = virtualSolReserves + amount + let i = virtual_sol + .checked_add(amount) + .ok_or_else(|| InfrastructureError::Other("Overflow in sol reserves addition".to_string()))?; + + // r = n / i + 1 + let r = n.checked_div(i) + .ok_or_else(|| InfrastructureError::Other("Division by zero".to_string()))? + .checked_add(1) + .ok_or_else(|| InfrastructureError::Other("Overflow in token calculation".to_string()))?; + + // s = virtualTokenReserves - r + let s = virtual_token + .checked_sub(r) + .ok_or_else(|| InfrastructureError::Other("Underflow in token calculation".to_string()))?; + + // 确保结果不超过 u64 + if s > u64::MAX as u128 { + return Err(InfrastructureError::Other("Result exceeds u64 max value".to_string())); + } + + let result = s as u64; + + // Return the minimum of s and realTokenReserves + Ok(if result < self.real_token_reserves { + result + } else { + self.real_token_reserves + }) + } + + pub fn get_sell_price(&self, amount: u64, fee_basis_points: u64) -> InfrastructureResult { + if self.complete { + return Err(InfrastructureError::Other("Curve is complete".to_string())); + } + + if amount == 0 { + return Ok(0); + } + + let amount = amount as u128; + let virtual_sol = self.virtual_sol_reserves as u128; + let virtual_token = self.virtual_token_reserves as u128; + let fee_basis_points = fee_basis_points as u128; + + // n = (amount * virtualSolReserves) / (virtualTokenReserves + amount) + let n = amount + .checked_mul(virtual_sol) + .ok_or_else(|| InfrastructureError::Other("Overflow in sol calculation".to_string()))? + .checked_div( + virtual_token + .checked_add(amount) + .ok_or_else(|| InfrastructureError::Other("Overflow in token addition".to_string()))? + ) + .ok_or_else(|| InfrastructureError::Other("Division by zero".to_string()))?; + + // a = (n * feeBasisPoints) / 10000 + let a = n + .checked_mul(fee_basis_points) + .ok_or_else(|| InfrastructureError::Other("Overflow in fee calculation".to_string()))? + .checked_div(10000) + .ok_or_else(|| InfrastructureError::Other("Division by zero in fee calculation".to_string()))?; + + // result = n - a + let result = n + .checked_sub(a) + .ok_or_else(|| InfrastructureError::Other("Underflow in final calculation".to_string()))?; + + if result > u64::MAX as u128 { + return Err(InfrastructureError::Other("Result exceeds u64 max value".to_string())); + } + + Ok(result as u64) + } + + pub fn get_market_cap_sol(&self) -> u64 { + if self.virtual_token_reserves == 0 { + return 0; + } + + self.token_total_supply + .checked_mul(self.virtual_sol_reserves) + .and_then(|n| n.checked_div(self.virtual_token_reserves)) + .unwrap_or(0) + } + + pub fn get_final_market_cap_sol(&self, fee_basis_points: u64) -> Result> { + let total_sell_value = self.get_buy_out_price(self.real_token_reserves, fee_basis_points)?; + let total_virtual_value = self.virtual_sol_reserves + .checked_add(total_sell_value) + .ok_or("Overflow in virtual value calculation")?; + let total_virtual_tokens = self.virtual_token_reserves + .checked_sub(self.real_token_reserves) + .ok_or("Underflow in virtual tokens calculation")?; + + if total_virtual_tokens == 0 { + return Ok(0); + } + + self.token_total_supply + .checked_mul(total_virtual_value) + .ok_or("Overflow in market cap calculation")? + .checked_div(total_virtual_tokens) + .ok_or("Division by zero in market cap calculation".into()) + } + + pub fn get_buy_out_price(&self, amount: u64, fee_basis_points: u64) -> Result> { + let sol_tokens = if amount < self.real_sol_reserves { + self.real_sol_reserves + } else { + amount + }; + + let total_sell_value = sol_tokens + .checked_mul(self.virtual_sol_reserves) + .ok_or("Overflow in sell value calculation")? + .checked_div( + self.virtual_token_reserves + .checked_sub(sol_tokens) + .ok_or("Underflow in token reserves calculation")? + ) + .ok_or("Division by zero")? + .checked_add(1) + .ok_or("Overflow in sell value calculation")?; + + let fee = total_sell_value + .checked_mul(fee_basis_points) + .ok_or("Overflow in fee calculation")? + .checked_div(10000) + .ok_or("Division by zero in fee calculation")?; + + total_sell_value + .checked_add(fee) + .ok_or("Overflow in final price calculation".into()) + } + + pub fn from_buffer(buffer: &[u8]) -> Result> { + Self::try_from_slice(buffer).map_err(|e| e.into()) + } +} + +impl PumpFunSDK { + pub fn new(rpc_url: &str) -> Self { + Self { + client: RpcClient::new_with_commitment( + rpc_url.to_string(), + CommitmentConfig::processed(), + ), + jito_client: None, + } + } + + pub fn with_jito(mut self, jito_endpoint: &str) -> Self { + self.jito_client = Some(JitoClient::new(jito_endpoint)); + self + } + + // 添加公共方法来获取代币余额 + pub async fn get_token_balance(&self, wallet_address: &Pubkey, mint: &Pubkey) -> InfrastructureResult { + // 获取用户代币账户地址 + let user_token_account = get_associated_token_address(&wallet_address, &mint); + // 首先检查账户是否存在 + match self.client.get_token_account_balance(&user_token_account) { + Ok(balance) => { + balance.amount + .parse::() + .map_err(|e| InfrastructureError::Other(e.to_string())) + }, + Err(_) => Ok(0) // 如果账户不存在,返回 None + } + } + + // 添加公共方法来获取 SOL 余额 + pub async fn get_sol_balance(&self, address: &Pubkey) -> Result> { + Ok(self.client.get_balance(address)?) + } + + // Buy token with SOL + pub async fn buy( + &self, + buyer: &Keypair, + mint: Pubkey, + buy_amount_sol: u64, + slippage_basis_points: Option, + ) -> InfrastructureResult { + let start_time = std::time::Instant::now(); + println!("Starting buy operation..."); + + // 1. 获取 bonding curve 账户 + let bonding_curve_start = std::time::Instant::now(); + let bonding_curve_address = self.get_bonding_curve_pda(&mint)?; + let bonding_curve = self.get_bonding_curve_account(&bonding_curve_address).await?; + println!("Bonding curve virtual sol reserves: {}", bonding_curve.virtual_sol_reserves); + println!("Bonding curve real sol reserves: {}", bonding_curve.real_sol_reserves); + println!("Got bonding curve account: {:?}ms", bonding_curve_start.elapsed().as_millis()); + + // 2. 计算买入数量和滑点 + let buy_amount = bonding_curve.get_buy_price(buy_amount_sol)?; + let slippage = slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE); + let max_sol_cost = calculate_with_slippage_buy(buy_amount_sol, slippage); + + // 3. 获取全局账户 + let global_start = std::time::Instant::now(); + let global_account = self.get_global_account().await?; + println!("Got global account: {:?}ms", global_start.elapsed().as_millis()); + + // 4. 准备所有指令 + let mut instructions = vec![]; + + instructions.push( + ComputeBudgetInstruction::set_compute_unit_limit(DEFAULT_COMPUTE_UNIT_LIMIT) + ); + + // 5. 添加 ATA 创建指令 + instructions.push( + create_associated_token_account( + &buyer.pubkey(), // payer + &buyer.pubkey(), // wallet + &mint, // mint + &spl_token::id(), // token program + ), + ); + + // 6. 添加买入指令 + instructions.push( + self.create_buy_instruction( + &buyer.pubkey(), + mint, + global_account.fee_recipient, + buy_amount, + max_sol_cost, + bonding_curve_address, + )?, + ); + + // 7. 创建并发送交易 + let tx_start = std::time::Instant::now(); + let recent_blockhash = self.client.get_latest_blockhash()?; + let transaction = Transaction::new_signed_with_payer( + &instructions, + Some(&buyer.pubkey()), + &[buyer], + recent_blockhash, + ); + + let signature = self.client + .send_transaction(&transaction) + .map_err(|e| InfrastructureError::Other(format!("Transaction failed: {}", e)))?; + println!("Transaction sent and confirmed: {:?}ms", tx_start.elapsed().as_millis()); + + println!("Total buy operation time: {:?}ms", start_time.elapsed().as_millis()); + Ok(signature) + } + + // Sell token for SOL + pub async fn sell( + &self, + seller: &Keypair, + mint: Pubkey, + sell_amount: u64, + slippage_basis_points: Option, + ) -> Result> { + let slippage = slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE); + + // 1. 获取账户信息 + let bonding_curve_address = self.get_bonding_curve_pda(&mint)?; + let bonding_curve = self.get_bonding_curve_account(&bonding_curve_address).await?; + let global_account = self.get_global_account().await?; + + // 2. 计算卖出价格和滑点 + let sell_price = bonding_curve.get_sell_price(sell_amount, global_account.fee_basis_points)?; + let min_out_amount = calculate_with_slippage_sell(sell_price, slippage); + + // 3. 构建交易指令 + let instructions = vec![ + // // 设置计算预算 + // ComputeBudgetInstruction::set_compute_unit_limit(DEFAULT_COMPUTE_UNIT_LIMIT), + // ComputeBudgetInstruction::set_compute_unit_price(DEFAULT_COMPUTE_UNIT_PRICE), + + + // 卖出指令 + self.create_sell_instruction( + &seller.pubkey(), + mint, + global_account.fee_recipient, + sell_amount, + min_out_amount, + bonding_curve_address, + )?, + ]; + + // 5. 发送交易 + let recent_blockhash = self.client.get_latest_blockhash()?; + let transaction = Transaction::new_signed_with_payer( + &instructions, + Some(&seller.pubkey()), + &[seller], + recent_blockhash, + ); + + let signature = self.client.send_transaction(&transaction)?; + println!("Transaction sent: {}", signature); + + Ok(signature) + } + + // 辅助函数:获取 bonding curve PDA + fn get_bonding_curve_pda(&self, mint: &Pubkey) -> InfrastructureResult { + let seeds: &[&[u8]] = &[b"bonding-curve", mint.as_ref()]; + let (pda, _) = Pubkey::find_program_address( + seeds, + &Pubkey::from_str(PROGRAM_ID).unwrap(), + ); + Ok(pda) + } + + // Helper functions + fn create_buy_instruction( + &self, + buyer: &Pubkey, + mint: Pubkey, + fee_recipient: Pubkey, + amount: u64, + max_sol_cost: u64, + bonding_curve_address: Pubkey, + ) -> InfrastructureResult { + let associated_bonding_curve = get_associated_token_address(&bonding_curve_address, &mint); + let associated_user = get_associated_token_address(buyer, &mint); + let global_seeds: &[&[u8]] = &[b"global"]; + let (global_pda, _) = Pubkey::find_program_address( + global_seeds, + &Pubkey::from_str(PROGRAM_ID).unwrap(), + ); + + let accounts = vec![ + AccountMeta::new_readonly(global_pda, false), + AccountMeta::new(fee_recipient, false), + AccountMeta::new_readonly(mint, false), + AccountMeta::new(bonding_curve_address, false), + AccountMeta::new(associated_bonding_curve, false), + AccountMeta::new(associated_user, false), + AccountMeta::new(*buyer, true), + AccountMeta::new_readonly(system_program::id(), false), + AccountMeta::new_readonly(spl_token::id(), false), + AccountMeta::new_readonly(sysvar::rent::id(), false), + AccountMeta::new_readonly(Pubkey::from_str("Ce6TQqeHC9p8KetsN6JsjHK7UTZk7nasjjnr7XxXp9F1").unwrap(), false), + AccountMeta::new_readonly(Pubkey::from_str(PROGRAM_ID).unwrap(), false), + ]; + + let mut data = Vec::with_capacity(8 + 8 + 8); + data.extend_from_slice(&[102, 6, 61, 18, 1, 218, 235, 234]); // discriminator + data.extend_from_slice(&amount.to_le_bytes()); + data.extend_from_slice(&max_sol_cost.to_le_bytes()); + + Ok(Instruction { + program_id: Pubkey::from_str(PROGRAM_ID).unwrap(), + accounts, + data, + }) + } + + fn create_sell_instruction( + &self, + seller: &Pubkey, + mint: Pubkey, + fee_recipient: Pubkey, + amount: u64, + min_sol_output: u64, + bonding_curve_address: Pubkey, + ) -> InfrastructureResult { + let associated_bonding_curve = get_associated_token_address(&bonding_curve_address, &mint); + let associated_user = get_associated_token_address(seller, &mint); + let global_seeds: &[&[u8]] = &[b"global"]; + let (global_pda, _) = Pubkey::find_program_address( + global_seeds, + &Pubkey::from_str(PROGRAM_ID).unwrap(), + ); + + let accounts = vec![ + AccountMeta::new_readonly(global_pda, false), + AccountMeta::new(fee_recipient, false), + AccountMeta::new_readonly(mint, false), + AccountMeta::new(bonding_curve_address, false), + AccountMeta::new(associated_bonding_curve, false), + AccountMeta::new(associated_user, false), + AccountMeta::new(*seller, true), + AccountMeta::new_readonly(system_program::id(), false), + AccountMeta::new_readonly(spl_associated_token_account::id(), false), + AccountMeta::new_readonly(spl_token::id(), false), + AccountMeta::new_readonly(Pubkey::from_str("Ce6TQqeHC9p8KetsN6JsjHK7UTZk7nasjjnr7XxXp9F1").unwrap(), false), + AccountMeta::new_readonly(Pubkey::from_str(PROGRAM_ID).unwrap(), false), + ]; + + let mut data = Vec::with_capacity(8 + 8 + 8); + data.extend_from_slice(&[51, 230, 133, 164, 1, 127, 131, 173]); // discriminator + data.extend_from_slice(&amount.to_le_bytes()); + data.extend_from_slice(&min_sol_output.to_le_bytes()); + + Ok(Instruction { + program_id: Pubkey::from_str(PROGRAM_ID).unwrap(), + accounts, + data, + }) + } + + pub async fn get_global_account(&self) -> InfrastructureResult { + let seeds: &[&[u8]] = &[b"global"]; + let (global_account_pda, _) = Pubkey::find_program_address( + seeds, + &Pubkey::from_str(PROGRAM_ID).unwrap(), + ); + + let account = self.client.get_account(&global_account_pda) + .map_err(|e| InfrastructureError::Other(e.to_string()))?; + GlobalAccount::from_buffer(&account.data) + .map_err(|e| InfrastructureError::Other(e.to_string())) + } + + pub async fn get_bonding_curve_account(&self, bonding_curve_address: &Pubkey) -> InfrastructureResult { + let account = self.client.get_account(&bonding_curve_address) + .map_err(|e| InfrastructureError::Other(e.to_string()))?; + BondingCurveAccount::from_buffer(&account.data) + .map_err(|e| InfrastructureError::Other(e.to_string())) + } + + /// 使用 Jito MEV 发送买入交易 + pub async fn buy_via_jito( + &self, + buyer: &Keypair, + mint: Pubkey, + buy_amount_sol: u64, + slippage_basis_points: Option, + ) -> InfrastructureResult { + let jito_client = self.jito_client.as_ref() + .ok_or_else(|| InfrastructureError::Other("Jito client not configured".to_string()))?; + + let start_time = std::time::Instant::now(); + println!("Starting Jito buy operation..."); + + // 1. 获取 bonding curve 账户 + let bonding_curve_address = self.get_bonding_curve_pda(&mint)?; + let bonding_curve = self.get_bonding_curve_account(&bonding_curve_address).await?; + + // 2. 计算买入数量和滑点 + let buy_amount = bonding_curve.get_buy_price(buy_amount_sol)?; + let slippage = slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE); + let max_sol_cost = calculate_with_slippage_buy(buy_amount_sol, slippage); + + // 3. 获取全局账户 + let global_account = self.get_global_account().await?; + + // 4. 获取优先费用估算 + let priority_fees = jito_client.estimate_priority_fees(&bonding_curve_address).await?; + + // 计算每计算单元的优先费用(使用 Extreme 级别) + let priority_fee_per_cu = priority_fees.per_compute_unit.extreme; + + // 完整的单位转换过程 + let total_priority_fee_microlamports = priority_fee_per_cu as u128 * DEFAULT_COMPUTE_UNIT_LIMIT as u128; + let total_priority_fee_lamports = total_priority_fee_microlamports / 1_000_000; + let total_priority_fee_sol = total_priority_fee_lamports as f64 / 1_000_000_000.0; + + println!("Priority fee details:"); + println!(" Per CU (microlamports): {}", priority_fee_per_cu); + println!(" Total (lamports): {}", total_priority_fee_lamports); + println!(" Total (SOL): {:.9}", total_priority_fee_sol); + + // 5. 获取 tip account + let tip_account = jito_client.get_tip_account().await?; + + // 6. 准备所有指令 + let mut instructions = vec![]; + + // 添加计算预算指令,包括优先费用 + instructions.push( + ComputeBudgetInstruction::set_compute_unit_limit(DEFAULT_COMPUTE_UNIT_LIMIT) + ); + + // 添加 ATA 创建指令 + instructions.push( + create_associated_token_account( + &buyer.pubkey(), + &buyer.pubkey(), + &mint, + &spl_token::id(), + ), + ); + + // 添加买入指令 + instructions.push( + self.create_buy_instruction( + &buyer.pubkey(), + mint, + global_account.fee_recipient, + buy_amount, + max_sol_cost, + bonding_curve_address, + )?, + ); + + // 添加 tip 指令 + instructions.push( + system_instruction::transfer( + &buyer.pubkey(), + &tip_account, + total_priority_fee_lamports as u64, + ), + ); + + // 7. 创建并发送交易 + let recent_blockhash = self.client.get_latest_blockhash()?; + let transaction = Transaction::new_signed_with_payer( + &instructions, + Some(&buyer.pubkey()), + &[buyer], + recent_blockhash, + ); + + // 8. 通过 Jito 发送交易 + let signature = jito_client.send_transaction(&transaction).await?; + println!("Total Jito buy operation time: {:?}ms", start_time.elapsed().as_millis()); + + Ok(signature) + } + + pub fn get_jito_client(&self) -> Option<&JitoClient> { + self.jito_client.as_ref() + } + + // 添加基于 Jito 的卖出方法 + pub async fn sell_via_jito( + &self, + seller: &Keypair, + mint: Pubkey, + sell_amount: u64, + slippage_basis_points: Option, + ) -> InfrastructureResult { + let jito_client = self.jito_client.as_ref() + .ok_or_else(|| InfrastructureError::Other("Jito client not configured".to_string()))?; + + let start_time = std::time::Instant::now(); + println!("Starting Jito sell operation..."); + + // 1. 获取账户信息 + let bonding_curve_address = self.get_bonding_curve_pda(&mint)?; + let bonding_curve = self.get_bonding_curve_account(&bonding_curve_address).await?; + let global_account = self.get_global_account().await?; + + // 2. 计算卖出价格和滑点 + let sell_price = bonding_curve.get_sell_price(sell_amount, global_account.fee_basis_points)?; + let slippage = slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE); + let min_out_amount = calculate_with_slippage_sell(sell_price, slippage); + + // 3. 获取优先费用估算 + let priority_fees = jito_client.estimate_priority_fees(&bonding_curve_address).await?; + let priority_fee_per_cu = priority_fees.per_compute_unit.extreme; + + // 完整的单位转换过程 + let total_priority_fee_microlamports = priority_fee_per_cu as u128 * DEFAULT_COMPUTE_UNIT_LIMIT as u128; + let total_priority_fee_lamports = total_priority_fee_microlamports / 1_000_000; + let total_priority_fee_sol = total_priority_fee_lamports as f64 / 1_000_000_000.0; + + println!("Priority fee details:"); + println!(" Per CU (microlamports): {}", priority_fee_per_cu); + println!(" Total (lamports): {}", total_priority_fee_lamports); + println!(" Total (SOL): {:.9}", total_priority_fee_sol); + + // 4. 获取 tip account + let tip_account = jito_client.get_tip_account().await?; + + // 5. 准备所有指令 + let mut instructions = vec![ + // 添加计算预算指令 + ComputeBudgetInstruction::set_compute_unit_limit(DEFAULT_COMPUTE_UNIT_LIMIT), + + // 卖出指令 + self.create_sell_instruction( + &seller.pubkey(), + mint, + global_account.fee_recipient, + sell_amount, + min_out_amount, + bonding_curve_address, + )?, + + // 添加 tip 指令 + system_instruction::transfer( + &seller.pubkey(), + &tip_account, + total_priority_fee_lamports as u64, + ), + ]; + + // 6. 创建并发送交易 + let recent_blockhash = self.client.get_latest_blockhash()?; + let transaction = Transaction::new_signed_with_payer( + &instructions, + Some(&seller.pubkey()), + &[seller], + recent_blockhash, + ); + + // 7. 通过 Jito 发送交易 + let signature = jito_client.send_transaction(&transaction).await?; + println!("Total Jito sell operation time: {:?}ms", start_time.elapsed().as_millis()); + + Ok(signature) + } +} + +fn calculate_with_slippage_sell(amount: u64, basis_points: u64) -> u64 { + amount.saturating_sub( + amount.saturating_mul(basis_points) + .saturating_div(10000) + ) +} + +// 添加滑点计算函数 +fn calculate_with_slippage_buy(amount: u64, basis_points: u64) -> u64 { + amount.saturating_add( + amount.saturating_mul(basis_points) + .saturating_div(10000) + ) +} \ No newline at end of file diff --git a/crates/pumpfun/src/utils/mod.rs b/crates/pumpfun/src/utils/mod.rs old mode 100644 new mode 100755