update error

This commit is contained in:
William
2025-02-12 22:46:09 +08:00
parent 2b69bfc2a5
commit ca00680215
3 changed files with 77 additions and 68 deletions
+4 -4
View File
@@ -3,6 +3,7 @@ use std::env;
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use reqwest::Proxy; use reqwest::Proxy;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use std::convert::TryFrom; use std::convert::TryFrom;
use super::TipPercentileData; use super::TipPercentileData;
@@ -45,17 +46,16 @@ pub async fn get_tip_accounts(block_engine_url: &str) -> Result<RpcResponse> {
println!("result: {:?}", result); println!("result: {:?}", result);
Ok(result) Ok(result)
} }
/// tip accounts /// tip accounts
#[derive(Debug)] #[derive(Debug)]
pub struct TipAccountResult { pub struct TipAccountResult {
pub accounts: Vec<String>, pub accounts: Vec<String>,
} }
impl TryFrom<RpcResponse> for TipAccountResult { impl TipAccountResult {
type Error = anyhow::Error; pub fn from(value: Value) -> Result<Self> {
fn try_from(value: RpcResponse) -> Result<Self, Self::Error> {
let accounts = value let accounts = value
.result
.as_array() .as_array()
.context("expected 'result' to be an array")? .context("expected 'result' to be an array")?
.iter() .iter()
+26 -16
View File
@@ -1,7 +1,7 @@
use std::{convert::TryInto, future::Future, str::FromStr, time::Duration, fmt}; use std::{convert::TryInto, future::Future, str::FromStr, time::Duration, fmt};
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use api::{get_tip_accounts, TipAccountResult}; use api::TipAccountResult;
use rand::{rng, seq::IteratorRandom}; use rand::{rng, seq::IteratorRandom};
use reqwest::Client; use reqwest::Client;
use serde::Deserialize; use serde::Deserialize;
@@ -95,7 +95,19 @@ impl JitoClient {
} }
} }
async fn send_request(&self, endpoint: &str, method: &str, params: Option<Value>) -> ClientResult<Value> { pub async fn get_tip_accounts(&self) -> Result<TipAccountResult> {
let endpoint = if let Some(uuid) = &self.uuid {
format!("/bundles?uuid={}", uuid)
} else {
"/bundles".to_string()
};
let result = self.send_request(&endpoint, "getTipAccounts", None).await?;
let tip_accounts = TipAccountResult::from(result).map_err(|e| anyhow!(e))?;
Ok(tip_accounts)
}
async fn send_request(&self, endpoint: &str, method: &str, params: Option<Value>) -> Result<Value> {
let url = format!("{}{}", self.base_url, endpoint); let url = format!("{}{}", self.base_url, endpoint);
let data = json!({ let data = json!({
@@ -111,16 +123,16 @@ impl JitoClient {
.json(&data) .json(&data)
.send() .send()
.await .await
.map_err(|e| ClientError::Other(format!("Request failed: {}", e)))?; .map_err(|e| anyhow!(format!("Request failed: {}", e)))?;
let body = response.json::<Value>().await let body = response.json::<Value>().await
.map_err(|e| ClientError::Other(format!("Failed to parse response: {}", e)))?; .map_err(|e| anyhow!(format!("Failed to parse response: {}", e)))?;
Ok(body) Ok(body)
} }
pub async fn init_tip_accounts(&self) -> Result<()> { pub async fn init_tip_accounts(&self) -> Result<()> {
let accounts: TipAccountResult = get_tip_accounts(&self.base_url).await?.try_into()?; let accounts = self.get_tip_accounts().await?;
let mut tip_accounts = self.tip_accounts.write().await; let mut tip_accounts = self.tip_accounts.write().await;
accounts accounts
@@ -186,10 +198,10 @@ impl JitoClient {
pub async fn send_transaction( pub async fn send_transaction(
&self, &self,
transaction: &Transaction, transaction: &Transaction,
) -> ClientResult<String> { ) -> Result<String, anyhow::Error> {
let wire_transaction = bincode::serialize(transaction).map_err(|e| { let wire_transaction = bincode::serialize(transaction).map_err(|e| {
ClientError::Parse( anyhow!(
"Transaction serialization failed".to_string(), "Transaction serialization failed: {}",
e.to_string(), e.to_string(),
) )
})?; })?;
@@ -208,13 +220,10 @@ impl JitoClient {
response["result"] response["result"]
.as_str() .as_str()
.map(|s| s.to_string()) .map(|s| s.to_string())
.ok_or_else(|| ClientError::Parse( .ok_or_else(|| anyhow!("Invalid response format: missing result field"))
"Invalid response format".to_string(),
"Missing result field".to_string(),
))
} }
pub async fn send_bundle(&self, params: Option<Value>, uuid: Option<String>) -> ClientResult<Value> { pub async fn send_bundle(&self, params: Option<Value>, uuid: Option<String>) -> Result<Value> {
let mut endpoint = "/bundles".to_string(); let mut endpoint = "/bundles".to_string();
if let Some(uuid) = uuid { if let Some(uuid) = uuid {
@@ -225,14 +234,14 @@ impl JitoClient {
let transactions = match params { let transactions = match params {
Some(Value::Array(transactions)) => { Some(Value::Array(transactions)) => {
if transactions.is_empty() { if transactions.is_empty() {
return Err(ClientError::Other("Bundle must contain at least one transaction".to_string())); return Err(anyhow!("Bundle must contain at least one transaction"));
} }
if transactions.len() > 5 { if transactions.len() > 5 {
return Err(ClientError::Other("Bundle can contain at most 5 transactions".to_string())); return Err(anyhow!("Bundle can contain at most 5 transactions"));
} }
transactions transactions
}, },
_ => return Err(ClientError::Other("Invalid bundle format: expected an array of transactions".to_string())), _ => return Err(anyhow!("Invalid bundle format: expected an array of transactions")),
}; };
// Wrap the transactions array in another array // Wrap the transactions array in another array
@@ -241,6 +250,7 @@ impl JitoClient {
// Send the wrapped transactions array // Send the wrapped transactions array
self.send_request(&endpoint, "sendBundle", Some(params)) self.send_request(&endpoint, "sendBundle", Some(params))
.await .await
.map_err(|e| anyhow!(e))
} }
pub async fn wait_for_bundle_confirmation<F, Fut>( pub async fn wait_for_bundle_confirmation<F, Fut>(
+47 -48
View File
@@ -32,7 +32,6 @@ use std::sync::Arc;
use std::time::Instant; use std::time::Instant;
use crate::jito::JitoClient; use crate::jito::JitoClient;
use crate::error::ClientError;
use borsh::BorshDeserialize; use borsh::BorshDeserialize;
@@ -102,10 +101,10 @@ impl PumpFun {
mint: &Keypair, mint: &Keypair,
metadata: utils::CreateTokenMetadata, metadata: utils::CreateTokenMetadata,
priority_fee: Option<PriorityFee>, priority_fee: Option<PriorityFee>,
) -> Result<Signature, ClientError> { ) -> Result<Signature, anyhow::Error> {
let ipfs = utils::create_token_metadata(metadata) let ipfs = utils::create_token_metadata(metadata)
.await .await
.map_err(ClientError::UploadMetadataError)?; .map_err(|_| anyhow!("Failed to upload metadata"))?;
let mut instructions = self.create_priority_fee_instructions(priority_fee); let mut instructions = self.create_priority_fee_instructions(priority_fee);
@@ -140,10 +139,10 @@ impl PumpFun {
amount_sol: u64, amount_sol: u64,
slippage_basis_points: Option<u64>, slippage_basis_points: Option<u64>,
priority_fee: Option<PriorityFee>, priority_fee: Option<PriorityFee>,
) -> Result<Signature, ClientError> { ) -> Result<Signature, anyhow::Error> {
let ipfs = utils::create_token_metadata(metadata) let ipfs = utils::create_token_metadata(metadata)
.await .await
.map_err(ClientError::UploadMetadataError)?; .map_err(|e| anyhow!(e.to_string()))?;
let global_account = self.get_global_account()?; let global_account = self.get_global_account()?;
let buy_amount = global_account.get_initial_buy_price(amount_sol); let buy_amount = global_account.get_initial_buy_price(amount_sol);
@@ -202,12 +201,12 @@ impl PumpFun {
amount_sol: u64, amount_sol: u64,
slippage_basis_points: Option<u64>, slippage_basis_points: Option<u64>,
priority_fee: Option<PriorityFee>, priority_fee: Option<PriorityFee>,
) -> Result<Signature, ClientError> { ) -> Result<Signature, anyhow::Error> {
let global_account = self.get_global_account()?; let global_account = self.get_global_account()?;
let bonding_curve_account = self.get_bonding_curve_account(mint)?; let bonding_curve_account = self.get_bonding_curve_account(mint)?;
let buy_amount = bonding_curve_account let buy_amount = bonding_curve_account
.get_buy_price(amount_sol) .get_buy_price(amount_sol)
.map_err(ClientError::BondingCurveError)?; .map_err(|e| anyhow!(e))?;
let buy_amount_with_slippage = let buy_amount_with_slippage =
utils::calculate_with_slippage_buy(amount_sol, slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE)); utils::calculate_with_slippage_buy(amount_sol, slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE));
@@ -253,11 +252,11 @@ impl PumpFun {
max_sol_cost: u64, max_sol_cost: u64,
slippage_basis_points: Option<u64>, slippage_basis_points: Option<u64>,
jito_fee: Option<u64>, jito_fee: Option<u64>,
) -> Result<String, ClientError> { ) -> Result<String, anyhow::Error> {
let start_time = Instant::now(); let start_time = Instant::now();
let jito_client = self.jito_client.as_ref() let jito_client = self.jito_client.as_ref()
.ok_or_else(|| ClientError::Other("Jito client not found".to_string()))?; .ok_or_else(|| anyhow!("Jito client not found"))?;
let global_account = self.get_global_account()?; let global_account = self.get_global_account()?;
let buy_amount_with_slippage = let buy_amount_with_slippage =
@@ -315,22 +314,22 @@ impl PumpFun {
amount_token: Option<u64>, amount_token: Option<u64>,
slippage_basis_points: Option<u64>, slippage_basis_points: Option<u64>,
priority_fee: Option<PriorityFee>, priority_fee: Option<PriorityFee>,
) -> Result<Signature, ClientError> { ) -> Result<Signature, anyhow::Error> {
let ata = get_associated_token_address(&self.payer.pubkey(), mint); let ata = get_associated_token_address(&self.payer.pubkey(), mint);
let balance = self.rpc.get_token_account_balance(&ata)?; let balance = self.rpc.get_token_account_balance(&ata)?;
let balance_u64 = balance.amount.parse::<u64>() let balance_u64 = balance.amount.parse::<u64>()
.map_err(|_| ClientError::Other("Failed to parse token balance".to_string()))?; .map_err(|_| anyhow!("Failed to parse token balance"))?;
let amount = amount_token.unwrap_or(balance_u64); let amount = amount_token.unwrap_or(balance_u64);
if amount == 0 { if amount == 0 {
return Err(ClientError::Other("Balance is 0".to_string())); return Err(anyhow!("Balance is 0"));
} }
let global_account = self.get_global_account()?; let global_account = self.get_global_account()?;
let bonding_curve_account = self.get_bonding_curve_account(mint)?; let bonding_curve_account = self.get_bonding_curve_account(mint)?;
let min_sol_output = bonding_curve_account let min_sol_output = bonding_curve_account
.get_sell_price(amount, global_account.fee_basis_points) .get_sell_price(amount, global_account.fee_basis_points)
.map_err(ClientError::BondingCurveError)?; .map_err(|e| anyhow!(e))?;
let min_sol_output_with_slippage = utils::calculate_with_slippage_sell( let min_sol_output_with_slippage = utils::calculate_with_slippage_sell(
min_sol_output, min_sol_output,
slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE), slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE),
@@ -376,18 +375,18 @@ impl PumpFun {
percent: u64, percent: u64,
slippage_basis_points: Option<u64>, slippage_basis_points: Option<u64>,
priority_fee: Option<PriorityFee>, priority_fee: Option<PriorityFee>,
) -> Result<Signature, ClientError> { ) -> Result<Signature, anyhow::Error> {
if percent > 100 { if percent > 100 {
return Err(ClientError::Other("Percentage must be between 0 and 100".to_string())); return Err(anyhow!("Percentage must be between 0 and 100"));
} }
let ata = get_associated_token_address(&self.payer.pubkey(), mint); let ata = get_associated_token_address(&self.payer.pubkey(), mint);
let balance = self.rpc.get_token_account_balance(&ata)?; let balance = self.rpc.get_token_account_balance(&ata)?;
let balance_u64 = balance.amount.parse::<u64>() let balance_u64 = balance.amount.parse::<u64>()
.map_err(|_| ClientError::Other("Failed to parse token balance".to_string()))?; .map_err(|_| anyhow!("Failed to parse token balance"))?;
if balance_u64 == 0 { if balance_u64 == 0 {
return Err(ClientError::Other("Balance is 0".to_string())); return Err(anyhow!("Balance is 0"));
} }
let amount = balance_u64 * percent / 100; let amount = balance_u64 * percent / 100;
@@ -400,18 +399,18 @@ impl PumpFun {
percent: u64, percent: u64,
slippage_basis_points: Option<u64>, slippage_basis_points: Option<u64>,
jito_fee: Option<u64>, jito_fee: Option<u64>,
) -> Result<String, ClientError> { ) -> Result<String, anyhow::Error> {
if percent > 100 { if percent > 100 {
return Err(ClientError::Other("Percentage must be between 0 and 100".to_string())); return Err(anyhow!("Percentage must be between 0 and 100"));
} }
let ata = get_associated_token_address(&self.payer.pubkey(), mint); let ata = get_associated_token_address(&self.payer.pubkey(), mint);
let balance = self.rpc.get_token_account_balance(&ata)?; let balance = self.rpc.get_token_account_balance(&ata)?;
let balance_u64 = balance.amount.parse::<u64>() let balance_u64 = balance.amount.parse::<u64>()
.map_err(|_| ClientError::Other("Failed to parse token balance".to_string()))?; .map_err(|_| anyhow!("Failed to parse token balance"))?;
if balance_u64 == 0 { if balance_u64 == 0 {
return Err(ClientError::Other("Balance is 0".to_string())); return Err(anyhow!("Balance is 0"));
} }
let amount = balance_u64 * percent / 100; let amount = balance_u64 * percent / 100;
@@ -425,34 +424,34 @@ impl PumpFun {
amount_token: Option<u64>, amount_token: Option<u64>,
slippage_basis_points: Option<u64>, slippage_basis_points: Option<u64>,
jito_fee: Option<u64>, jito_fee: Option<u64>,
) -> Result<String, ClientError> { ) -> Result<String, anyhow::Error> {
let start_time = Instant::now(); let start_time = Instant::now();
let jito_client = self.jito_client.as_ref() let jito_client = self.jito_client.as_ref()
.ok_or_else(|| ClientError::Other("Jito client not found".to_string()))?; .ok_or_else(|| anyhow!("Jito client not found"))?;
let ata = get_associated_token_address(&self.payer.pubkey(), mint); let ata = get_associated_token_address(&self.payer.pubkey(), mint);
let balance = self.rpc.get_token_account_balance(&ata)?; let balance = self.rpc.get_token_account_balance(&ata)?;
let balance_u64 = balance.amount.parse::<u64>() let balance_u64 = balance.amount.parse::<u64>()
.map_err(|_| ClientError::Other("Failed to parse token balance".to_string()))?; .map_err(|_| anyhow!("Failed to parse token balance"))?;
let amount = amount_token.unwrap_or(balance_u64); let amount = amount_token.unwrap_or(balance_u64);
if amount == 0 { if amount == 0 {
return Err(ClientError::Other("Amount cannot be zero".to_string())); return Err(anyhow!("Amount cannot be zero"));
} }
let global_account = self.get_global_account()?; let global_account = self.get_global_account()?;
let bonding_curve_account = self.get_bonding_curve_account(mint)?; let bonding_curve_account = self.get_bonding_curve_account(mint)?;
let min_sol_output = bonding_curve_account let min_sol_output = bonding_curve_account
.get_sell_price(amount, global_account.fee_basis_points) .get_sell_price(amount, global_account.fee_basis_points)
.map_err(ClientError::BondingCurveError)?; .map_err(|e| anyhow!(e))?;
let min_sol_output_with_slippage = utils::calculate_with_slippage_sell( let min_sol_output_with_slippage = utils::calculate_with_slippage_sell(
min_sol_output, min_sol_output,
slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE), slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE),
); );
let mut instructions = vec![]; let mut instructions = vec![];
let tip_account = jito_client.get_tip_account().await.map_err(|e| ClientError::Other(e.to_string()))?; let tip_account = jito_client.get_tip_account().await.map_err(|e| anyhow!(e))?;
instructions.push(instruction::sell( instructions.push(instruction::sell(
&self.payer.clone(), &self.payer.clone(),
mint, mint,
@@ -512,7 +511,7 @@ impl PumpFun {
self.payer.pubkey() self.payer.pubkey()
} }
pub fn get_token_balance(&self, account: &Pubkey, mint: &Pubkey) -> Result<u64, ClientError> { pub fn get_token_balance(&self, account: &Pubkey, mint: &Pubkey) -> Result<u64, anyhow::Error> {
let ata = get_associated_token_address(account, mint); let ata = get_associated_token_address(account, mint);
if self.rpc.get_account(&ata).is_err() { if self.rpc.get_account(&ata).is_err() {
return Ok(0); return Ok(0);
@@ -520,18 +519,18 @@ impl PumpFun {
let balance = self.rpc.get_token_account_balance(&ata)?; let balance = self.rpc.get_token_account_balance(&ata)?;
balance.amount.parse::<u64>() balance.amount.parse::<u64>()
.map_err(|_| ClientError::Other("Failed to parse token balance".to_string())) .map_err(|_| anyhow!("Failed to parse token balance"))
} }
pub fn get_sol_balance(&self, account: &Pubkey) -> Result<u64, ClientError> { pub fn get_sol_balance(&self, account: &Pubkey) -> Result<u64, anyhow::Error> {
self.rpc.get_balance(account).map_err(ClientError::SolanaClientError) self.rpc.get_balance(account).map_err(|_| anyhow!("Failed to get SOL balance"))
} }
pub fn get_payer_token_balance(&self, mint: &Pubkey) -> Result<u64, ClientError> { pub fn get_payer_token_balance(&self, mint: &Pubkey) -> Result<u64, anyhow::Error> {
self.get_token_balance(&self.payer.pubkey(), mint) self.get_token_balance(&self.payer.pubkey(), mint)
} }
pub fn get_payer_sol_balance(&self) -> Result<u64, ClientError> { pub fn get_payer_sol_balance(&self) -> Result<u64, anyhow::Error> {
self.get_sol_balance(&self.payer.pubkey()) self.get_sol_balance(&self.payer.pubkey())
} }
@@ -563,22 +562,22 @@ impl PumpFun {
} }
// Account related methods // Account related methods
pub fn get_global_account(&self) -> Result<accounts::GlobalAccount, ClientError> { pub fn get_global_account(&self) -> Result<accounts::GlobalAccount, anyhow::Error> {
let global = Self::get_global_pda(); let global = Self::get_global_pda();
let account = self.rpc.get_account(&global)?; let account = self.rpc.get_account(&global)?;
accounts::GlobalAccount::try_from_slice(&account.data) accounts::GlobalAccount::try_from_slice(&account.data)
.map_err(ClientError::BorshError) .map_err(|e| anyhow!(e))
} }
pub fn get_bonding_curve_account( pub fn get_bonding_curve_account(
&self, &self,
mint: &Pubkey, mint: &Pubkey,
) -> Result<accounts::BondingCurveAccount, ClientError> { ) -> Result<accounts::BondingCurveAccount, anyhow::Error> {
let bonding_curve_pda = Self::get_bonding_curve_pda(mint) let bonding_curve_pda = Self::get_bonding_curve_pda(mint)
.ok_or(ClientError::BondingCurveNotFound)?; .ok_or(anyhow!("Bonding curve not found"))?;
let account = self.rpc.get_account(&bonding_curve_pda)?; let account = self.rpc.get_account(&bonding_curve_pda)?;
accounts::BondingCurveAccount::try_from_slice(&account.data) accounts::BondingCurveAccount::try_from_slice(&account.data)
.map_err(ClientError::BorshError) .map_err(|e| anyhow!(e))
} }
// Subscription related methods // Subscription related methods
@@ -636,7 +635,7 @@ impl PumpFun {
}) })
} }
pub async fn get_token_price_in_usdc(&self, token_amount: f64) -> Result<f64, ClientError> { pub async fn get_token_price_in_usdc(&self, token_amount: f64) -> Result<f64, anyhow::Error> {
if token_amount == 0.0 { if token_amount == 0.0 {
return Ok(0.0); return Ok(0.0);
} }
@@ -644,39 +643,39 @@ impl PumpFun {
let url = "https://api.jup.ag/price/v2?ids=So11111111111111111111111111111111111111112"; let url = "https://api.jup.ag/price/v2?ids=So11111111111111111111111111111111111111112";
let response: serde_json::Value = reqwest::get(url) let response: serde_json::Value = reqwest::get(url)
.await .await
.map_err(|e: reqwest::Error| ClientError::Other(Box::new(e).to_string()))? .map_err(|e: reqwest::Error| anyhow!(e))?
.json() .json()
.await .await
.map_err(|e: reqwest::Error| ClientError::Other(Box::new(e).to_string()))?; .map_err(|e: reqwest::Error| anyhow!(e))?;
let sol_price_str = response["data"]["So11111111111111111111111111111111111111112"]["price"] let sol_price_str = response["data"]["So11111111111111111111111111111111111111112"]["price"]
.as_str() .as_str()
.ok_or(ClientError::Other("Failed to find SOL price as a string".into()))?; .ok_or(anyhow!("Failed to find SOL price as a string"))?;
let sol_price_in_usdc: f64 = sol_price_str let sol_price_in_usdc: f64 = sol_price_str
.parse() .parse()
.map_err(|e: std::num::ParseFloatError| ClientError::Other(Box::new(e).to_string()))?; .map_err(|e: std::num::ParseFloatError| anyhow!(e))?;
let token_price_in_usdc = sol_price_in_usdc * token_amount; let token_price_in_usdc = sol_price_in_usdc * token_amount;
Ok(token_price_in_usdc) Ok(token_price_in_usdc)
} }
pub async fn get_sol_price_in_usdc(&self) -> Result<f64, ClientError> { pub async fn get_sol_price_in_usdc(&self) -> Result<f64, anyhow::Error> {
let url = "https://api.jup.ag/price/v2?ids=So11111111111111111111111111111111111111112"; let url = "https://api.jup.ag/price/v2?ids=So11111111111111111111111111111111111111112";
let response: serde_json::Value = reqwest::get(url) let response: serde_json::Value = reqwest::get(url)
.await .await
.map_err(|_| ClientError::Anyhow("Failed to install crypto provider"))? .map_err(|_| anyhow!("Failed to install crypto provider"))?
.json() .json()
.await .await
.map_err(|_| ClientError::Anyhow("Failed to install crypto provider"))?; .map_err(|_| anyhow!("Failed to install crypto provider"))?;
let sol_price_str = response["data"]["So11111111111111111111111111111111111111112"]["price"] let sol_price_str = response["data"]["So11111111111111111111111111111111111111112"]["price"]
.as_str() .as_str()
.ok_or(ClientError::Anyhow("Failed to find SOL price as a string"))?; .ok_or(anyhow!("Failed to find SOL price as a string"))?;
let sol_price_in_usdc: f64 = sol_price_str let sol_price_in_usdc: f64 = sol_price_str
.parse() .parse()
.map_err(|_| ClientError::Anyhow("Failed to parse SOL price as a string"))?; .map_err(|_| anyhow!("Failed to parse SOL price as a string"))?;
Ok(sol_price_in_usdc) Ok(sol_price_in_usdc)
} }