mirror of
https://github.com/0xfnzero/solana-streamer.git
synced 2026-08-23 13:58:06 +00:00
fix buy
This commit is contained in:
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
+14
@@ -24,6 +24,7 @@ use anchor_client::solana_client::{
|
|||||||
client_error::ClientError as SolanaClientError,
|
client_error::ClientError as SolanaClientError,
|
||||||
pubsub_client::PubsubClientError
|
pubsub_client::PubsubClientError
|
||||||
};
|
};
|
||||||
|
use solana_sdk::pubkey::ParsePubkeyError;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum ClientError {
|
pub enum ClientError {
|
||||||
@@ -58,6 +59,8 @@ pub enum ClientError {
|
|||||||
|
|
||||||
Parse(String, String),
|
Parse(String, String),
|
||||||
|
|
||||||
|
Pubkey(String, String),
|
||||||
|
|
||||||
Jito(String, String),
|
Jito(String, String),
|
||||||
|
|
||||||
Join(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::Jito(msg, details) => write!(f, "Jito error: {}, details: {}", msg, details),
|
||||||
Self::Redis(msg, details) => write!(f, "Redis 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::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::Subscribe(msg, details) => write!(f, "Subscribe error: {}, details: {}", msg, details),
|
||||||
Self::Send(msg, details) => write!(f, "Send error: {}, details: {}", msg, details),
|
Self::Send(msg, details) => write!(f, "Send error: {}, details: {}", msg, details),
|
||||||
Self::Other(msg) => write!(f, "Other error: {}", msg),
|
Self::Other(msg) => write!(f, "Other error: {}", msg),
|
||||||
@@ -129,6 +133,7 @@ impl std::error::Error for ClientError {
|
|||||||
Self::Parse(_, _) => None,
|
Self::Parse(_, _) => None,
|
||||||
Self::Jito(_, _) => None,
|
Self::Jito(_, _) => None,
|
||||||
Self::Join(_) => None,
|
Self::Join(_) => None,
|
||||||
|
Self::Pubkey(_, _) => None,
|
||||||
Self::Subscribe(_, _) => None,
|
Self::Subscribe(_, _) => None,
|
||||||
Self::Send(_, _) => None,
|
Self::Send(_, _) => None,
|
||||||
Self::Other(_) => None,
|
Self::Other(_) => None,
|
||||||
@@ -161,6 +166,15 @@ impl From<PubsubClientError> for ClientError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<ParsePubkeyError> for ClientError {
|
||||||
|
fn from(error: ParsePubkeyError) -> Self {
|
||||||
|
ClientError::Pubkey(
|
||||||
|
"Pubkey error".to_string(),
|
||||||
|
error.to_string(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<Error> for ClientError {
|
impl From<Error> for ClientError {
|
||||||
fn from(err: Error) -> Self {
|
fn from(err: Error) -> Self {
|
||||||
ClientError::Parse(
|
ClientError::Parse(
|
||||||
|
|||||||
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
+56
-50
@@ -1,16 +1,22 @@
|
|||||||
use {
|
use rand::Rng;
|
||||||
bincode, bs58, reqwest, serde::Deserialize, serde_json::{json, Value},
|
use bincode;
|
||||||
std::{str::FromStr, time::Duration}
|
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::{
|
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;
|
use crate::error::ClientError;
|
||||||
|
|
||||||
pub const MAX_RETRIES: u8 = 3;
|
pub const MAX_RETRIES: u8 = 3;
|
||||||
pub const RETRY_DELAY: Duration = Duration::from_millis(200);
|
pub const RETRY_DELAY: Duration = Duration::from_millis(200);
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct TransactionConfig {
|
pub struct TransactionConfig {
|
||||||
@@ -31,7 +37,7 @@ impl Default for TransactionConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct JitoClient {
|
pub struct JitoClient {
|
||||||
endpoint: String,
|
endpoint: String,
|
||||||
client: reqwest::Client,
|
client: reqwest::Client,
|
||||||
@@ -49,26 +55,25 @@ impl JitoClient {
|
|||||||
|
|
||||||
pub async fn get_tip_account(&self) -> Result<Pubkey, ClientError> {
|
pub async fn get_tip_account(&self) -> Result<Pubkey, ClientError> {
|
||||||
let response = self.send_request("getTipAccounts", json!([])).await?;
|
let response = self.send_request("getTipAccounts", json!([])).await?;
|
||||||
|
|
||||||
if let Some(accounts) = response["result"].as_array() {
|
if let Some(accounts) = response["result"].as_array() {
|
||||||
if accounts.is_empty() {
|
if accounts.is_empty() {
|
||||||
return Err(ClientError::Other(
|
return Err(ClientError::Other("No JITO tip accounts found".to_string()));
|
||||||
"No JITO tip accounts found".to_string()
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let random_index = rand::random::<usize>() % accounts.len();
|
let random_index = rand::rngs::OsRng.gen_range(0..accounts.len());
|
||||||
if let Some(account) = accounts.get(random_index) {
|
if let Some(account) = accounts.get(random_index) {
|
||||||
if let Some(address) = account.as_str() {
|
if let Some(address) = account.as_str() {
|
||||||
return Pubkey::from_str(address)
|
return Pubkey::from_str(address).map_err(|e| {
|
||||||
.map_err(|e| ClientError::Parse(
|
ClientError::Parse(
|
||||||
"Invalid tip account address".to_string(),
|
"Invalid tip account address".to_string(),
|
||||||
e.to_string()
|
e.to_string(),
|
||||||
));
|
)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(ClientError::Other("Failed to get Tip Account".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?;
|
let response = self.send_request("qn_estimatePriorityFees", params).await?;
|
||||||
|
|
||||||
if let Some(result) = response.get("result") {
|
if let Some(result) = response.get("result") {
|
||||||
let estimate: PriorityFeeEstimate = serde_json::from_value(result.clone())
|
let estimate: PriorityFeeEstimate = serde_json::from_value(result.clone()).map_err(|e| {
|
||||||
.map_err(|e| ClientError::Parse(
|
ClientError::Parse(
|
||||||
"Failed to parse priority fee estimate".to_string(),
|
"Failed to parse priority fee estimate".to_string(),
|
||||||
e.to_string()
|
e.to_string(),
|
||||||
))?;
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
Ok(estimate)
|
Ok(estimate)
|
||||||
} else {
|
} else {
|
||||||
Err(ClientError::Parse(
|
Err(ClientError::Parse(
|
||||||
"Invalid response format".to_string(),
|
"Invalid response format".to_string(),
|
||||||
"Missing result field".to_string()
|
"Missing result field".to_string(),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -104,23 +110,25 @@ impl JitoClient {
|
|||||||
&self,
|
&self,
|
||||||
transaction: &Transaction,
|
transaction: &Transaction,
|
||||||
) -> Result<Signature, ClientError> {
|
) -> Result<Signature, ClientError> {
|
||||||
let wire_transaction = bincode::serialize(transaction).map_err(|e|
|
let wire_transaction = bincode::serialize(transaction).map_err(|e| {
|
||||||
ClientError::Parse(
|
ClientError::Parse(
|
||||||
"Transaction serialization failed".to_string(),
|
"Transaction serialization failed".to_string(),
|
||||||
e.to_string()
|
e.to_string(),
|
||||||
))?;
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
let encoded_tx = bs58::encode(&wire_transaction).into_string();
|
let encoded_tx = bs58::encode(&wire_transaction).into_string();
|
||||||
|
|
||||||
for retry in 0..MAX_RETRIES {
|
for retry in 0..MAX_RETRIES {
|
||||||
match self.try_send_transaction(&encoded_tx).await {
|
match self.try_send_transaction(&encoded_tx).await {
|
||||||
Ok(signature) => {
|
Ok(signature) => {
|
||||||
return Ok(Signature::from_str(&signature).map_err(|e|
|
return Ok(Signature::from_str(&signature).map_err(|e| {
|
||||||
ClientError::Parse(
|
ClientError::Parse(
|
||||||
"Invalid signature".to_string(),
|
"Invalid signature".to_string(),
|
||||||
e.to_string()
|
e.to_string(),
|
||||||
))?);
|
)
|
||||||
},
|
})?);
|
||||||
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
println!("Retry {} failed: {:?}", retry, e);
|
println!("Retry {} failed: {:?}", retry, e);
|
||||||
if retry == MAX_RETRIES - 1 {
|
if retry == MAX_RETRIES - 1 {
|
||||||
@@ -130,7 +138,7 @@ impl JitoClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(ClientError::Other("Max retries exceeded".to_string()))
|
Err(ClientError::Other("Max retries exceeded".to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,17 +154,14 @@ impl JitoClient {
|
|||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|
||||||
let response = self.send_request(
|
let response = self.send_request("sendTransaction", params).await?;
|
||||||
"sendTransaction",
|
|
||||||
params
|
|
||||||
).await?;
|
|
||||||
|
|
||||||
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(|| ClientError::Parse(
|
||||||
"Invalid response format".to_string(),
|
"Invalid response format".to_string(),
|
||||||
"Missing result field".to_string()
|
"Missing result field".to_string(),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,19 +181,20 @@ impl JitoClient {
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| ClientError::Solana(
|
.map_err(|e| ClientError::Solana(
|
||||||
"Request failed".to_string(),
|
"Request failed".to_string(),
|
||||||
e.to_string()
|
e.to_string(),
|
||||||
))?;
|
))?;
|
||||||
|
|
||||||
let response_data: Value = response.json().await
|
let response_data: Value = response.json().await.map_err(|e| {
|
||||||
.map_err(|e| ClientError::Parse(
|
ClientError::Parse(
|
||||||
"Invalid JSON response".to_string(),
|
"Invalid JSON response".to_string(),
|
||||||
e.to_string()
|
e.to_string(),
|
||||||
))?;
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
if let Some(error) = response_data.get("error") {
|
if let Some(error) = response_data.get("error") {
|
||||||
return Err(ClientError::Solana(
|
return Err(ClientError::Solana(
|
||||||
"RPC error".to_string(),
|
"RPC error".to_string(),
|
||||||
error.to_string()
|
error.to_string(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -209,4 +215,4 @@ pub struct PriorityFeeLevel {
|
|||||||
pub high: u64, // 80th percentile
|
pub high: u64, // 80th percentile
|
||||||
pub medium: u64, // 60th percentile
|
pub medium: u64, // 60th percentile
|
||||||
pub low: u64, // 40th percentile
|
pub low: u64, // 40th percentile
|
||||||
}
|
}
|
||||||
Regular → Executable
+149
-151
@@ -1,4 +1,4 @@
|
|||||||
// #![doc = include_str!("../RUSTDOC.md")]
|
#![doc = include_str!("../RUSTDOC.md")]
|
||||||
|
|
||||||
pub mod accounts;
|
pub mod accounts;
|
||||||
pub mod constants;
|
pub mod constants;
|
||||||
@@ -19,8 +19,8 @@ use anchor_client::{
|
|||||||
compute_budget::ComputeBudgetInstruction,
|
compute_budget::ComputeBudgetInstruction,
|
||||||
transaction::Transaction,
|
transaction::Transaction,
|
||||||
},
|
},
|
||||||
Client, Cluster, Program,
|
|
||||||
};
|
};
|
||||||
|
use anchor_client::Cluster;
|
||||||
use anchor_spl::associated_token::{
|
use anchor_spl::associated_token::{
|
||||||
get_associated_token_address,
|
get_associated_token_address,
|
||||||
spl_associated_token_account::instruction::create_associated_token_account,
|
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::jito::JitoClient;
|
||||||
use crate::error::ClientError;
|
use crate::error::ClientError;
|
||||||
|
|
||||||
// 常量定义
|
// Constants
|
||||||
const DEFAULT_SLIPPAGE: u64 = 500; // 10%
|
const DEFAULT_SLIPPAGE: u64 = 500; // 10%
|
||||||
const DEFAULT_COMPUTE_UNIT_LIMIT: u32 = 68_000;
|
const DEFAULT_COMPUTE_UNIT_LIMIT: u32 = 68_000;
|
||||||
const DEFAULT_COMPUTE_UNIT_PRICE: u64 = 400_000;
|
const DEFAULT_COMPUTE_UNIT_PRICE: u64 = 400_000;
|
||||||
|
|
||||||
/// 优先费用配置
|
/// Priority fee configuration
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub struct PriorityFee {
|
pub struct PriorityFee {
|
||||||
pub limit: Option<u32>,
|
pub limit: Option<u32>,
|
||||||
pub price: Option<u64>,
|
pub price: Option<u64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// PumpFun 客户端
|
|
||||||
pub struct PumpFun {
|
pub struct PumpFun {
|
||||||
pub rpc: RpcClient,
|
pub rpc: RpcClient,
|
||||||
pub payer: Arc<Keypair>,
|
pub payer: Arc<Keypair>,
|
||||||
pub client: Client<Arc<Keypair>>,
|
|
||||||
pub jito_client: Option<JitoClient>,
|
pub jito_client: Option<JitoClient>,
|
||||||
pub program: Program<Arc<Keypair>>,
|
}
|
||||||
|
|
||||||
|
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 {
|
impl PumpFun {
|
||||||
// 创建新实例
|
/// Create a new PumpFun client instance
|
||||||
pub fn new(
|
pub fn new(
|
||||||
cluster: Cluster,
|
cluster: Cluster,
|
||||||
jito_url: Option<String>,
|
commitment: Option<CommitmentConfig>,
|
||||||
payer: Arc<Keypair>,
|
payer: Arc<Keypair>,
|
||||||
options: Option<CommitmentConfig>,
|
jito_url: Option<String>,
|
||||||
ws: Option<bool>,
|
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let rpc = RpcClient::new(if ws.unwrap_or(false) {
|
let rpc = RpcClient::new_with_commitment(
|
||||||
cluster.ws_url()
|
cluster.url(),
|
||||||
} else {
|
commitment.unwrap_or(CommitmentConfig::confirmed())
|
||||||
cluster.url()
|
);
|
||||||
});
|
|
||||||
|
|
||||||
let jito_client = jito_url.map(|url| JitoClient::new(&url));
|
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 {
|
Self {
|
||||||
rpc,
|
rpc,
|
||||||
payer,
|
payer,
|
||||||
jito_client,
|
jito_client,
|
||||||
client,
|
|
||||||
program,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建代币
|
/// Create a new token
|
||||||
pub async fn create(
|
pub async fn create(
|
||||||
&self,
|
&self,
|
||||||
mint: &Keypair,
|
mint: &Keypair,
|
||||||
metadata: utils::CreateTokenMetadata,
|
metadata: utils::CreateTokenMetadata,
|
||||||
priority_fee: Option<PriorityFee>,
|
priority_fee: Option<PriorityFee>,
|
||||||
) -> Result<Signature, error::ClientError> {
|
) -> Result<Signature, ClientError> {
|
||||||
let ipfs = utils::create_token_metadata(metadata)
|
let ipfs = utils::create_token_metadata(metadata)
|
||||||
.await
|
.await
|
||||||
.map_err(error::ClientError::UploadMetadataError)?;
|
.map_err(ClientError::UploadMetadataError)?;
|
||||||
|
|
||||||
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(
|
instructions.push(instruction::create(
|
||||||
&self.payer.clone().as_ref(),
|
self.payer.as_ref(),
|
||||||
mint,
|
mint,
|
||||||
cpi::instruction::Create {
|
cpi::instruction::Create {
|
||||||
_name: ipfs.metadata.name,
|
_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
|
let signature = self.rpc.send_and_confirm_transaction(&transaction)?;
|
||||||
.send()
|
|
||||||
.await
|
|
||||||
.map_err(error::ClientError::AnchorClientError)?;
|
|
||||||
|
|
||||||
Ok(signature)
|
Ok(signature)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建并购买代币
|
/// Create and buy tokens in one transaction
|
||||||
pub async fn create_and_buy(
|
pub async fn create_and_buy(
|
||||||
&self,
|
&self,
|
||||||
mint: &Keypair,
|
mint: &Keypair,
|
||||||
@@ -134,22 +134,20 @@ 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, error::ClientError> {
|
) -> Result<Signature, ClientError> {
|
||||||
let ipfs = utils::create_token_metadata(metadata)
|
let ipfs = utils::create_token_metadata(metadata)
|
||||||
.await
|
.await
|
||||||
.map_err(error::ClientError::UploadMetadataError)?;
|
.map_err(ClientError::UploadMetadataError)?;
|
||||||
|
|
||||||
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);
|
||||||
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));
|
||||||
|
|
||||||
let mut request = self.program.request();
|
let mut instructions = self.create_priority_fee_instructions(priority_fee);
|
||||||
|
|
||||||
request = self.add_priority_fee(request, priority_fee);
|
instructions.push(instruction::create(
|
||||||
|
self.payer.as_ref(),
|
||||||
request = request.instruction(instruction::create(
|
|
||||||
&self.payer.clone().as_ref(),
|
|
||||||
mint,
|
mint,
|
||||||
cpi::instruction::Create {
|
cpi::instruction::Create {
|
||||||
_name: ipfs.metadata.name,
|
_name: ipfs.metadata.name,
|
||||||
@@ -160,7 +158,7 @@ impl PumpFun {
|
|||||||
|
|
||||||
let ata = get_associated_token_address(&self.payer.pubkey(), &mint.pubkey());
|
let ata = get_associated_token_address(&self.payer.pubkey(), &mint.pubkey());
|
||||||
if self.rpc.get_account(&ata).is_err() {
|
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(),
|
||||||
&self.payer.pubkey(),
|
&self.payer.pubkey(),
|
||||||
&mint.pubkey(),
|
&mint.pubkey(),
|
||||||
@@ -168,8 +166,8 @@ impl PumpFun {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
request = request.instruction(instruction::buy(
|
instructions.push(instruction::buy(
|
||||||
&self.payer.clone().as_ref(),
|
self.payer.as_ref(),
|
||||||
&mint.pubkey(),
|
&mint.pubkey(),
|
||||||
&global_account.fee_recipient,
|
&global_account.fee_recipient,
|
||||||
cpi::instruction::Buy {
|
cpi::instruction::Buy {
|
||||||
@@ -178,39 +176,40 @@ impl PumpFun {
|
|||||||
},
|
},
|
||||||
));
|
));
|
||||||
|
|
||||||
let signature = request
|
let recent_blockhash = self.rpc.get_latest_blockhash()?;
|
||||||
.signer(&self.payer)
|
let transaction = Transaction::new_signed_with_payer(
|
||||||
.signer(mint)
|
&instructions,
|
||||||
.send()
|
Some(&self.payer.pubkey()),
|
||||||
.await
|
&[&self.payer.clone(), mint],
|
||||||
.map_err(error::ClientError::AnchorClientError)?;
|
recent_blockhash,
|
||||||
|
);
|
||||||
|
|
||||||
|
let signature = self.rpc.send_and_confirm_transaction(&transaction)?;
|
||||||
|
|
||||||
Ok(signature)
|
Ok(signature)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 购买代币
|
/// Buy tokens
|
||||||
pub async fn buy(
|
pub async fn buy(
|
||||||
&self,
|
&self,
|
||||||
mint: &Pubkey,
|
mint: &Pubkey,
|
||||||
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, error::ClientError> {
|
) -> Result<Signature, ClientError> {
|
||||||
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(error::ClientError::BondingCurveError)?;
|
.map_err(ClientError::BondingCurveError)?;
|
||||||
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));
|
||||||
|
|
||||||
let mut request = self.program.request();
|
let mut instructions = self.create_priority_fee_instructions(priority_fee);
|
||||||
|
|
||||||
request = self.add_priority_fee(request, priority_fee);
|
|
||||||
|
|
||||||
let ata = get_associated_token_address(&self.payer.pubkey(), mint);
|
let ata = get_associated_token_address(&self.payer.pubkey(), mint);
|
||||||
if self.rpc.get_account(&ata).is_err() {
|
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(),
|
||||||
&self.payer.pubkey(),
|
&self.payer.pubkey(),
|
||||||
mint,
|
mint,
|
||||||
@@ -218,8 +217,8 @@ impl PumpFun {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
request = request.instruction(instruction::buy(
|
instructions.push(instruction::buy(
|
||||||
&self.payer.clone().as_ref(),
|
&self.payer.clone(),
|
||||||
mint,
|
mint,
|
||||||
&global_account.fee_recipient,
|
&global_account.fee_recipient,
|
||||||
cpi::instruction::Buy {
|
cpi::instruction::Buy {
|
||||||
@@ -228,35 +227,39 @@ impl PumpFun {
|
|||||||
},
|
},
|
||||||
));
|
));
|
||||||
|
|
||||||
let signature = request
|
let recent_blockhash = self.rpc.get_latest_blockhash()?;
|
||||||
.signer(&self.payer)
|
let transaction = Transaction::new_signed_with_payer(
|
||||||
.send()
|
&instructions,
|
||||||
.await
|
Some(&self.payer.pubkey()),
|
||||||
.map_err(error::ClientError::AnchorClientError)?;
|
&[&self.payer.clone()],
|
||||||
|
recent_blockhash,
|
||||||
|
);
|
||||||
|
|
||||||
|
let signature = self.rpc.send_transaction(&transaction)?;
|
||||||
|
|
||||||
Ok(signature)
|
Ok(signature)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用 Jito 购买代币
|
/// Buy tokens using Jito
|
||||||
pub async fn buy_with_jito(
|
pub async fn buy_with_jito(
|
||||||
&self,
|
&self,
|
||||||
mint: &Pubkey,
|
mint: &Pubkey,
|
||||||
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, error::ClientError> {
|
) -> Result<Signature, ClientError> {
|
||||||
let start_time = Instant::now();
|
let start_time = Instant::now();
|
||||||
|
|
||||||
let jito_client = self.jito_client.as_ref().ok_or_else(||
|
let jito_client = self.jito_client.as_ref()
|
||||||
ClientError::Other("Jito client not found".to_string())
|
.ok_or_else(|| ClientError::Other("Jito client not found".to_string()))?;
|
||||||
)?;
|
|
||||||
|
|
||||||
let global_account = self.get_global_account()?;
|
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 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(error::ClientError::BondingCurveError)?;
|
.map_err(ClientError::BondingCurveError)?;
|
||||||
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));
|
||||||
|
|
||||||
@@ -277,7 +280,7 @@ impl PumpFun {
|
|||||||
}
|
}
|
||||||
|
|
||||||
instructions.push(instruction::buy(
|
instructions.push(instruction::buy(
|
||||||
&self.payer.clone().as_ref(),
|
self.payer.as_ref(),
|
||||||
mint,
|
mint,
|
||||||
&global_account.fee_recipient,
|
&global_account.fee_recipient,
|
||||||
cpi::instruction::Buy {
|
cpi::instruction::Buy {
|
||||||
@@ -310,17 +313,18 @@ impl PumpFun {
|
|||||||
Ok(signature)
|
Ok(signature)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 出售代币
|
/// Sell tokens
|
||||||
pub async fn sell(
|
pub async fn sell(
|
||||||
&self,
|
&self,
|
||||||
mint: &Pubkey,
|
mint: &Pubkey,
|
||||||
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, error::ClientError> {
|
) -> Result<Signature, ClientError> {
|
||||||
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>().unwrap();
|
let balance_u64 = balance.amount.parse::<u64>()
|
||||||
|
.map_err(|_| ClientError::Other("Failed to parse token balance".to_string()))?;
|
||||||
let amount = amount_token.unwrap_or(balance_u64);
|
let amount = amount_token.unwrap_or(balance_u64);
|
||||||
|
|
||||||
if amount == 0 {
|
if amount == 0 {
|
||||||
@@ -331,18 +335,16 @@ impl PumpFun {
|
|||||||
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(error::ClientError::BondingCurveError)?;
|
.map_err(ClientError::BondingCurveError)?;
|
||||||
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 request = self.program.request();
|
let mut instructions = self.create_priority_fee_instructions(priority_fee);
|
||||||
|
|
||||||
request = self.add_priority_fee(request, priority_fee);
|
instructions.push(instruction::sell(
|
||||||
|
self.payer.as_ref(),
|
||||||
request = request.instruction(instruction::sell(
|
|
||||||
&self.payer.clone().as_ref(),
|
|
||||||
mint,
|
mint,
|
||||||
&global_account.fee_recipient,
|
&global_account.fee_recipient,
|
||||||
cpi::instruction::Sell {
|
cpi::instruction::Sell {
|
||||||
@@ -351,26 +353,35 @@ impl PumpFun {
|
|||||||
},
|
},
|
||||||
));
|
));
|
||||||
|
|
||||||
let signature = request
|
let recent_blockhash = self.rpc.get_latest_blockhash()?;
|
||||||
.signer(&self.payer)
|
let transaction = Transaction::new_signed_with_payer(
|
||||||
.send()
|
&instructions,
|
||||||
.await
|
Some(&self.payer.pubkey()),
|
||||||
.map_err(error::ClientError::AnchorClientError)?;
|
&[&self.payer.clone()],
|
||||||
|
recent_blockhash,
|
||||||
|
);
|
||||||
|
|
||||||
|
let signature = self.rpc.send_and_confirm_transaction(&transaction)?;
|
||||||
|
|
||||||
Ok(signature)
|
Ok(signature)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 按百分比出售代币
|
/// Sell tokens by percentage
|
||||||
pub async fn sell_by_percent(
|
pub async fn sell_by_percent(
|
||||||
&self,
|
&self,
|
||||||
mint: &Pubkey,
|
mint: &Pubkey,
|
||||||
percent: u64,
|
percent: u64,
|
||||||
slippage_basis_points: Option<u64>,
|
slippage_basis_points: Option<u64>,
|
||||||
priority_fee: Option<PriorityFee>,
|
priority_fee: Option<PriorityFee>,
|
||||||
) -> Result<Signature, error::ClientError> {
|
) -> Result<Signature, ClientError> {
|
||||||
|
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 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>().unwrap();
|
let balance_u64 = balance.amount.parse::<u64>()
|
||||||
|
.map_err(|_| ClientError::Other("Failed to parse token balance".to_string()))?;
|
||||||
|
|
||||||
if balance_u64 == 0 {
|
if balance_u64 == 0 {
|
||||||
return Err(ClientError::Other("Balance is 0".to_string()));
|
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
|
self.sell(mint, Some(amount), slippage_basis_points, priority_fee).await
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用 Jito 出售代币
|
/// Sell tokens using Jito
|
||||||
pub async fn sell_with_jito(
|
pub async fn sell_with_jito(
|
||||||
&self,
|
&self,
|
||||||
mint: &Pubkey,
|
mint: &Pubkey,
|
||||||
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, error::ClientError> {
|
) -> Result<Signature, ClientError> {
|
||||||
let start_time = Instant::now();
|
let start_time = Instant::now();
|
||||||
|
|
||||||
let jito_client = self.jito_client.as_ref().ok_or_else(||
|
let jito_client = self.jito_client.as_ref()
|
||||||
ClientError::Other("Jito client not found".to_string())
|
.ok_or_else(|| ClientError::Other("Jito client not found".to_string()))?;
|
||||||
)?;
|
|
||||||
|
|
||||||
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>().unwrap();
|
let balance_u64 = balance.amount.parse::<u64>()
|
||||||
|
.map_err(|_| ClientError::Other("Failed to parse token balance".to_string()))?;
|
||||||
let amount = amount_token.unwrap_or(balance_u64);
|
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 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 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(error::ClientError::BondingCurveError)?;
|
.map_err(ClientError::BondingCurveError)?;
|
||||||
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),
|
||||||
@@ -417,7 +433,7 @@ impl PumpFun {
|
|||||||
let tip_account = jito_client.get_tip_account().await?;
|
let tip_account = jito_client.get_tip_account().await?;
|
||||||
|
|
||||||
instructions.push(instruction::sell(
|
instructions.push(instruction::sell(
|
||||||
&self.payer.clone().as_ref(),
|
self.payer.as_ref(),
|
||||||
mint,
|
mint,
|
||||||
&global_account.fee_recipient,
|
&global_account.fee_recipient,
|
||||||
cpi::instruction::Sell {
|
cpi::instruction::Sell {
|
||||||
@@ -450,34 +466,7 @@ impl PumpFun {
|
|||||||
Ok(signature)
|
Ok(signature)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 辅助方法
|
// Helper methods
|
||||||
fn add_priority_fee<'a>(
|
|
||||||
&self,
|
|
||||||
request: anchor_client::RequestBuilder<'a, Arc<Keypair>>,
|
|
||||||
priority_fee: Option<PriorityFee>,
|
|
||||||
) -> anchor_client::RequestBuilder<'a, Arc<Keypair>> {
|
|
||||||
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<PriorityFee>) -> (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 create_priority_fee_instructions(&self, priority_fee: Option<PriorityFee>) -> Vec<Instruction> {
|
fn create_priority_fee_instructions(&self, priority_fee: Option<PriorityFee>) -> Vec<Instruction> {
|
||||||
let mut instructions = Vec::new();
|
let mut instructions = Vec::new();
|
||||||
if let Some(fee) = priority_fee {
|
if let Some(fee) = priority_fee {
|
||||||
@@ -491,35 +480,46 @@ impl PumpFun {
|
|||||||
instructions
|
instructions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_compute_units(&self, priority_fee: Option<PriorityFee>) -> (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 {
|
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;
|
let total_priority_fee_microlamports = priority_fee_per_cu as u128 * unit_limit as u128;
|
||||||
(total_priority_fee_microlamports / 1_000_000) as u64
|
(total_priority_fee_microlamports / 1_000_000) as u64
|
||||||
}
|
}
|
||||||
|
|
||||||
// 公共接口方法
|
// Public interface methods
|
||||||
pub fn get_payer_pubkey(&self) -> Pubkey {
|
pub fn get_payer_pubkey(&self) -> Pubkey {
|
||||||
self.payer.pubkey()
|
self.payer.pubkey()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_token_balance(&self, account: &Pubkey, mint: &Pubkey) -> Result<u64, error::ClientError> {
|
pub fn get_token_balance(&self, account: &Pubkey, mint: &Pubkey) -> Result<u64, ClientError> {
|
||||||
let ata = get_associated_token_address(account, mint);
|
let ata = get_associated_token_address(account, mint);
|
||||||
let balance = self.rpc.get_token_account_balance(&ata)?;
|
let balance = self.rpc.get_token_account_balance(&ata)?;
|
||||||
Ok(balance.amount.parse::<u64>().unwrap())
|
balance.amount.parse::<u64>()
|
||||||
|
.map_err(|_| ClientError::Other("Failed to parse token balance".to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_sol_balance(&self, account: &Pubkey) -> Result<u64, error::ClientError> {
|
pub fn get_sol_balance(&self, account: &Pubkey) -> Result<u64, ClientError> {
|
||||||
self.rpc.get_balance(account).map_err(error::ClientError::SolanaClientError)
|
self.rpc.get_balance(account).map_err(ClientError::SolanaClientError)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_payer_token_balance(&self, mint: &Pubkey) -> Result<u64, error::ClientError> {
|
pub fn get_payer_token_balance(&self, mint: &Pubkey) -> Result<u64, ClientError> {
|
||||||
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, error::ClientError> {
|
pub fn get_payer_sol_balance(&self) -> Result<u64, ClientError> {
|
||||||
self.get_sol_balance(&self.payer.pubkey())
|
self.get_sol_balance(&self.payer.pubkey())
|
||||||
}
|
}
|
||||||
|
|
||||||
// PDA 相关方法
|
// PDA related methods
|
||||||
pub fn get_global_pda() -> Pubkey {
|
pub fn get_global_pda() -> Pubkey {
|
||||||
Pubkey::find_program_address(&[constants::seeds::GLOBAL_SEED], &cpi::ID).0
|
Pubkey::find_program_address(&[constants::seeds::GLOBAL_SEED], &cpi::ID).0
|
||||||
}
|
}
|
||||||
@@ -546,28 +546,26 @@ impl PumpFun {
|
|||||||
).0
|
).0
|
||||||
}
|
}
|
||||||
|
|
||||||
// 账户相关方法
|
// Account related methods
|
||||||
pub fn get_global_account(&self) -> Result<accounts::GlobalAccount, error::ClientError> {
|
pub fn get_global_account(&self) -> Result<accounts::GlobalAccount, ClientError> {
|
||||||
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)?;
|
||||||
.map_err(error::ClientError::SolanaClientError)?;
|
|
||||||
accounts::GlobalAccount::try_from_slice(&account.data)
|
accounts::GlobalAccount::try_from_slice(&account.data)
|
||||||
.map_err(error::ClientError::BorshError)
|
.map_err(ClientError::BorshError)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_bonding_curve_account(
|
pub fn get_bonding_curve_account(
|
||||||
&self,
|
&self,
|
||||||
mint: &Pubkey,
|
mint: &Pubkey,
|
||||||
) -> Result<accounts::BondingCurveAccount, error::ClientError> {
|
) -> Result<accounts::BondingCurveAccount, ClientError> {
|
||||||
let bonding_curve_pda = Self::get_bonding_curve_pda(mint)
|
let bonding_curve_pda = Self::get_bonding_curve_pda(mint)
|
||||||
.ok_or(error::ClientError::BondingCurveNotFound)?;
|
.ok_or(ClientError::BondingCurveNotFound)?;
|
||||||
let account = self.rpc.get_account(&bonding_curve_pda)
|
let account = self.rpc.get_account(&bonding_curve_pda)?;
|
||||||
.map_err(error::ClientError::SolanaClientError)?;
|
|
||||||
accounts::BondingCurveAccount::try_from_slice(&account.data)
|
accounts::BondingCurveAccount::try_from_slice(&account.data)
|
||||||
.map_err(error::ClientError::BorshError)
|
.map_err(ClientError::BorshError)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 订阅相关方法
|
// Subscription related methods
|
||||||
pub async fn tokens_subscription<F>(
|
pub async fn tokens_subscription<F>(
|
||||||
&self,
|
&self,
|
||||||
ws_url: &str,
|
ws_url: &str,
|
||||||
@@ -593,7 +591,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_new_client() {
|
fn test_new_client() {
|
||||||
let payer = Arc::new(Keypair::new());
|
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());
|
assert_eq!(client.payer.pubkey(), payer.pubkey());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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<JitoClient>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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> {
|
||||||
|
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<u64> {
|
||||||
|
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<u64> {
|
||||||
|
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<u64, Box<dyn std::error::Error>> {
|
||||||
|
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<u64, Box<dyn std::error::Error>> {
|
||||||
|
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, Box<dyn std::error::Error>> {
|
||||||
|
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<u64> {
|
||||||
|
// 获取用户代币账户地址
|
||||||
|
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::<u64>()
|
||||||
|
.map_err(|e| InfrastructureError::Other(e.to_string()))
|
||||||
|
},
|
||||||
|
Err(_) => Ok(0) // 如果账户不存在,返回 None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加公共方法来获取 SOL 余额
|
||||||
|
pub async fn get_sol_balance(&self, address: &Pubkey) -> Result<u64, Box<dyn std::error::Error>> {
|
||||||
|
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<u64>,
|
||||||
|
) -> InfrastructureResult<Signature> {
|
||||||
|
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<u64>,
|
||||||
|
) -> Result<Signature, Box<dyn std::error::Error>> {
|
||||||
|
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<Pubkey> {
|
||||||
|
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<Instruction> {
|
||||||
|
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<Instruction> {
|
||||||
|
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<GlobalAccount> {
|
||||||
|
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<BondingCurveAccount> {
|
||||||
|
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<u64>,
|
||||||
|
) -> InfrastructureResult<Signature> {
|
||||||
|
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<u64>,
|
||||||
|
) -> InfrastructureResult<Signature> {
|
||||||
|
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)
|
||||||
|
)
|
||||||
|
}
|
||||||
Regular → Executable
Reference in New Issue
Block a user