feat(pumpswap): enhance PumpSwap trading functionality and logs parsing system

- Expand buy/sell trading logic with optimized execution flow
- Improve logs data structures and parser performance
- Enhance common utilities and pool management features
- Refactor main program architecture for better maintainability
- Update library export interfaces with improved API design
This commit is contained in:
sgxiang
2025-06-12 00:00:30 +08:00
parent 6f819be4e0
commit 165335af11
8 changed files with 828 additions and 235 deletions
+26
View File
@@ -48,6 +48,19 @@ pub struct BuyEvent {
pub coin_creator_fee: u64,
#[borsh(skip)]
pub signature: String,
#[borsh(skip)]
pub base_mint: Pubkey,
#[borsh(skip)]
pub quote_mint: Pubkey,
#[borsh(skip)]
pub pool_base_token_account: Pubkey,
#[borsh(skip)]
pub pool_quote_token_account: Pubkey,
#[borsh(skip)]
pub coin_creator_vault_ata: Pubkey,
#[borsh(skip)]
pub coin_creator_vault_authority: Pubkey,
}
/// 卖出事件
@@ -80,6 +93,19 @@ pub struct SellEvent {
pub coin_creator_fee: u64,
#[borsh(skip)]
pub signature: String,
#[borsh(skip)]
pub base_mint: Pubkey,
#[borsh(skip)]
pub quote_mint: Pubkey,
#[borsh(skip)]
pub pool_base_token_account: Pubkey,
#[borsh(skip)]
pub pool_quote_token_account: Pubkey,
#[borsh(skip)]
pub coin_creator_vault_ata: Pubkey,
#[borsh(skip)]
pub coin_creator_vault_authority: Pubkey,
}
/// 创建池子事件
+15 -1
View File
@@ -113,7 +113,7 @@ pub fn parse_pumpswap_instruction(instruction: &CompiledInstruction, _accounts:
}
let base_amount_out = u64::from_le_bytes(data[0..8].try_into().ok()?);
let max_quote_amount_in = u64::from_le_bytes(data[8..16].try_into().ok()?);
Some(PumpSwapInstruction::Buy(BuyEvent {
base_amount_out,
max_quote_amount_in,
@@ -124,6 +124,13 @@ pub fn parse_pumpswap_instruction(instruction: &CompiledInstruction, _accounts:
protocol_fee_recipient: accounts[9],
protocol_fee_recipient_token_account: accounts[10],
timestamp: current_timestamp(),
base_mint: accounts[3],
quote_mint: accounts[4],
pool_base_token_account: accounts[7],
pool_quote_token_account: accounts[8],
coin_creator_vault_ata: if accounts.len() > 17 { accounts[17] } else { Pubkey::default() },
coin_creator_vault_authority: if accounts.len() > 18 { accounts[18] } else { Pubkey::default() },
..Default::default()
}))
},
@@ -148,6 +155,13 @@ pub fn parse_pumpswap_instruction(instruction: &CompiledInstruction, _accounts:
protocol_fee_recipient: accounts[9],
protocol_fee_recipient_token_account: accounts[10],
timestamp: current_timestamp(),
base_mint: accounts[3],
quote_mint: accounts[4],
pool_base_token_account: accounts[7],
pool_quote_token_account: accounts[8],
coin_creator_vault_ata: if accounts.len() > 17 { accounts[17] } else { Pubkey::default() },
coin_creator_vault_authority: if accounts.len() > 18 { accounts[18] } else { Pubkey::default() },
..Default::default()
}))
},
+36
View File
@@ -252,10 +252,16 @@ impl PumpFun {
self.rpc.clone(),
self.payer.clone(),
mint,
creator,
buy_sol_cost,
slippage_basis_points,
self.priority_fee.clone(),
self.cluster.clone().lookup_table_key,
None,
None,
None,
None,
None,
).await
} else {
Err(anyhow::anyhow!("Unsupported trade platform: {}", trade_platform))
@@ -321,10 +327,16 @@ impl PumpFun {
self.fee_clients.clone(),
self.payer.clone(),
mint,
creator,
buy_sol_cost,
slippage_basis_points,
self.priority_fee.clone(),
self.cluster.clone().lookup_table_key,
None,
None,
None,
None,
None,
).await
} else {
Err(anyhow::anyhow!("Unsupported trade platform: {}", trade_platform))
@@ -378,10 +390,16 @@ impl PumpFun {
self.rpc.clone(),
self.payer.clone(),
mint.clone(),
creator,
percent,
None,
self.priority_fee.clone(),
self.cluster.clone().lookup_table_key,
None,
None,
None,
None,
None,
).await
} else {
Err(anyhow::anyhow!("Unsupported trade platform: {}", trade_platform))
@@ -413,10 +431,16 @@ impl PumpFun {
self.rpc.clone(),
self.payer.clone(),
mint.clone(),
creator,
amount,
None,
self.priority_fee.clone(),
self.cluster.clone().lookup_table_key,
None,
None,
None,
None,
None,
).await
} else {
Err(anyhow::anyhow!("Unsupported trade platform: {}", trade_platform))
@@ -450,10 +474,16 @@ impl PumpFun {
self.fee_clients.clone(),
self.payer.clone(),
mint,
creator,
percent,
None,
self.priority_fee.clone(),
self.cluster.clone().lookup_table_key,
None,
None,
None,
None,
None,
).await
} else {
Err(anyhow::anyhow!("Unsupported trade platform: {}", trade_platform))
@@ -485,10 +515,16 @@ impl PumpFun {
self.fee_clients.clone(),
self.payer.clone(),
mint,
creator,
amount,
None,
self.priority_fee.clone(),
self.cluster.clone().lookup_table_key,
None,
None,
None,
None,
None,
).await
} else {
Err(anyhow::anyhow!("Unsupported trade platform: {}", trade_platform))
+174 -108
View File
@@ -1,24 +1,38 @@
use pumpfun_sdk::{common::{
pumpfun::{logs_events::PumpfunEvent, logs_subscribe::{stop_subscription, tokens_subscription}}, pumpswap::{self, PumpSwapEvent}, AnyResult
}, grpc::{ShredStreamGrpc, YellowstoneGrpc}};
use solana_sdk::{commitment_config::CommitmentConfig, transaction::VersionedTransaction};
use std::{str::FromStr, sync::Arc};
use pumpfun_sdk::{
common::{
pumpfun::{
self,
logs_events::PumpfunEvent,
logs_subscribe::{stop_subscription, tokens_subscription},
},
pumpswap::{self, PumpSwapEvent},
AnyResult, Cluster, PriorityFee,
},
grpc::{ShredStreamGrpc, YellowstoneGrpc},
PumpFun,
};
use solana_hash::Hash;
use solana_sdk::{
commitment_config::CommitmentConfig, pubkey::Pubkey, signature::Keypair,
transaction::VersionedTransaction,
};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// test_pumpfun_with_shreds().await?;
// test_pumpfun_with_grpc().await?;
// test_pumpswap_with_shreds().await?;
test_pumpswap_with_grpc().await?;
Ok(())
// test_pumpswap_with_grpc().await?;
test_sell().await?;
Ok(())
}
async fn test_pumpfun_with_shreds() -> Result<(), Box<dyn std::error::Error>> {
let grpc = ShredStreamGrpc::new(
"http://127.0.0.1:10000".to_string(),
).await?;
let grpc = ShredStreamGrpc::new("http://127.0.0.1:10000".to_string()).await?;
let callback = |event: PumpfunEvent| {
// TradeInfo 的 sol_amount 不是真实线上消费/获取的数量
// 当 is_buy 为 true 时,sol_amount = max_sol_cost,代表用户愿意支付的最大金额
// 当 is_buy 为 false 时,sol_amount = min_sol_output,代表用户愿意接受的最小金额
@@ -33,16 +47,16 @@ async fn test_pumpfun_with_shreds() -> Result<(), Box<dyn std::error::Error>> {
match event {
PumpfunEvent::NewDevTrade(trade_info) => {
println!("Received new dev trade event: {:?}", trade_info);
},
}
PumpfunEvent::NewToken(token_info) => {
println!("Received new token event: {:?}", token_info);
},
}
PumpfunEvent::NewUserTrade(trade_info) => {
println!("Received new trade event: {:?}", trade_info);
},
}
PumpfunEvent::NewBotTrade(trade_info) => {
println!("Received new bot trade event: {:?}", trade_info);
},
}
PumpfunEvent::Error(err) => {
println!("Received error: {}", err);
}
@@ -56,71 +70,66 @@ async fn test_pumpfun_with_shreds() -> Result<(), Box<dyn std::error::Error>> {
async fn test_pumpfun_with_grpc() -> Result<(), Box<dyn std::error::Error>> {
let grpc = YellowstoneGrpc::new(
"https://solana-yellowstone-grpc.publicnode.com:443".to_string(),
"https://solana-yellowstone-grpc.publicnode.com:443".to_string(),
None,
)?;
let callback = |event: PumpfunEvent| {
match event {
PumpfunEvent::NewDevTrade(trade_info) => {
println!("Received new dev trade event: {:?}", trade_info);
},
PumpfunEvent::NewToken(token_info) => {
println!("Received new token event: {:?}", token_info);
},
PumpfunEvent::NewUserTrade(trade_info) => {
println!("Received new trade event: {:?}", trade_info);
},
PumpfunEvent::NewBotTrade(trade_info) => {
println!("Received new bot trade event: {:?}", trade_info);
},
PumpfunEvent::Error(err) => {
println!("Received error: {}", err);
}
let callback = |event: PumpfunEvent| match event {
PumpfunEvent::NewDevTrade(trade_info) => {
println!("Received new dev trade event: {:?}", trade_info);
}
PumpfunEvent::NewToken(token_info) => {
println!("Received new token event: {:?}", token_info);
}
PumpfunEvent::NewUserTrade(trade_info) => {
println!("Received new trade event: {:?}", trade_info);
}
PumpfunEvent::NewBotTrade(trade_info) => {
println!("Received new bot trade event: {:?}", trade_info);
}
PumpfunEvent::Error(err) => {
println!("Received error: {}", err);
}
};
grpc.subscribe_pumpfun(callback, None).await?;
Ok(())
Ok(())
}
async fn test_pumpswap_with_shreds() -> Result<(), Box<dyn std::error::Error>> {
// 使用 ShredStream 客户端订阅 PumpSwap 事件
println!("正在订阅 PumpSwap ShredStream 事件...");
let grpc_client = ShredStreamGrpc::new(
"http://127.0.0.1:10000".to_string(),
).await?;
let grpc_client = ShredStreamGrpc::new("http://140.82.2.197:10800".to_string()).await?;
// 定义回调函数处理 PumpSwap 事件
let callback = |event: PumpSwapEvent| {
match event {
PumpSwapEvent::Buy(buy_event) => {
println!("buy_event: {:?}", buy_event);
},
// println!("buy_event: {:?}", buy_event);
}
PumpSwapEvent::Sell(sell_event) => {
println!("sell_event: {:?}", sell_event);
},
}
PumpSwapEvent::CreatePool(create_event) => {
println!("create_event: {:?}", create_event);
},
// println!("create_event: {:?}", create_event);
}
PumpSwapEvent::Deposit(deposit_event) => {
println!("deposit_event: {:?}", deposit_event);
},
// println!("deposit_event: {:?}", deposit_event);
}
PumpSwapEvent::Withdraw(withdraw_event) => {
println!("withdraw_event: {:?}", withdraw_event);
},
// println!("withdraw_event: {:?}", withdraw_event);
}
PumpSwapEvent::Disable(disable_event) => {
println!("disable_event: {:?}", disable_event);
},
// println!("disable_event: {:?}", disable_event);
}
PumpSwapEvent::UpdateAdmin(update_admin_event) => {
println!("update_admin_event: {:?}", update_admin_event);
},
// println!("update_admin_event: {:?}", update_admin_event);
}
PumpSwapEvent::UpdateFeeConfig(update_fee_event) => {
println!("update_fee_event: {:?}", update_fee_event);
},
// println!("update_fee_event: {:?}", update_fee_event);
}
PumpSwapEvent::Error(err) => {
println!("error: {}", err);
}
@@ -140,39 +149,37 @@ async fn test_pumpswap_with_grpc() -> Result<(), Box<dyn std::error::Error>> {
let grpc = YellowstoneGrpc::new(
"https://solana-yellowstone-grpc.publicnode.com:443".to_string(),
None
None,
)?;
// 定义回调函数处理 PumpSwap 事件
let callback = |event: PumpSwapEvent| {
match event {
PumpSwapEvent::Buy(buy_event) => {
println!("buy_event: {:?}", buy_event);
},
PumpSwapEvent::Sell(sell_event) => {
println!("sell_event: {:?}", sell_event);
},
PumpSwapEvent::CreatePool(create_event) => {
println!("create_event: {:?}", create_event);
},
PumpSwapEvent::Deposit(deposit_event) => {
println!("deposit_event: {:?}", deposit_event);
},
PumpSwapEvent::Withdraw(withdraw_event) => {
println!("withdraw_event: {:?}", withdraw_event);
},
PumpSwapEvent::Disable(disable_event) => {
println!("disable_event: {:?}", disable_event);
},
PumpSwapEvent::UpdateAdmin(update_admin_event) => {
println!("update_admin_event: {:?}", update_admin_event);
},
PumpSwapEvent::UpdateFeeConfig(update_fee_event) => {
println!("update_fee_event: {:?}", update_fee_event);
},
PumpSwapEvent::Error(err) => {
println!("error: {}", err);
}
let callback = |event: PumpSwapEvent| match event {
PumpSwapEvent::Buy(buy_event) => {
println!("buy_event: {:?}", buy_event);
}
PumpSwapEvent::Sell(sell_event) => {
println!("sell_event: {:?}", sell_event);
}
PumpSwapEvent::CreatePool(create_event) => {
println!("create_event: {:?}", create_event);
}
PumpSwapEvent::Deposit(deposit_event) => {
println!("deposit_event: {:?}", deposit_event);
}
PumpSwapEvent::Withdraw(withdraw_event) => {
println!("withdraw_event: {:?}", withdraw_event);
}
PumpSwapEvent::Disable(disable_event) => {
println!("disable_event: {:?}", disable_event);
}
PumpSwapEvent::UpdateAdmin(update_admin_event) => {
println!("update_admin_event: {:?}", update_admin_event);
}
PumpSwapEvent::UpdateFeeConfig(update_fee_event) => {
println!("update_fee_event: {:?}", update_fee_event);
}
PumpSwapEvent::Error(err) => {
println!("error: {}", err);
}
};
// 订阅 PumpSwap 事件
@@ -183,43 +190,102 @@ async fn test_pumpswap_with_grpc() -> Result<(), Box<dyn std::error::Error>> {
Ok(())
}
async fn test_sell() -> AnyResult<()> {
let payer = Keypair::new();
// Define cluster configuration
let cluster = Cluster {
rpc_url: "https://mainnet.helius-rpc.com/?api-key=f2f194bb-6bd6-4f20-9a94-7fe0799ade0b"
.to_string(),
commitment: CommitmentConfig::confirmed(),
priority_fee: PriorityFee::default(),
use_jito: false,
use_zeroslot: false,
use_nozomi: false,
use_nextblock: false,
block_engine_url: "".to_string(),
zeroslot_url: "".to_string(),
zeroslot_auth_token: "".to_string(),
nozomi_url: "".to_string(),
nozomi_auth_token: "".to_string(),
nextblock_url: "".to_string(),
nextblock_auth_token: "".to_string(),
lookup_table_key: None,
use_rpc: true,
};
let pumpswap = PumpFun::new(Arc::new(payer), &cluster).await;
let creator = Pubkey::from_str("8BtoThi2ZoXnF7QQK1Wjmh2JuBw9FjVvhnGMVZ2vpump")?;
let dev_buy_token = 0;
let dev_sol_cost = 0;
let buy_sol_cost = 100_000_000;
let slippage_basis_points = Some(100);
let recent_blockhash = Hash::default();
let trade_platform = "pumpswap".to_string();
let mint_pubkey = Pubkey::from_str("8BtoThi2ZoXnF7QQK1Wjmh2JuBw9FjVvhnGMVZ2vpump")?;
println!("Buying tokens from PumpSwap...");
pumpswap
.copy_buy(
mint_pubkey,
creator,
dev_buy_token,
dev_sol_cost,
buy_sol_cost,
slippage_basis_points,
recent_blockhash,
trade_platform,
)
.await?;
// 需要先转sol到wsol才能buy
// pumpswap
// .buy(
// mint_pubkey,
// 10_000_000, // 0.01 SOL
// Some(100), // 1% slippage
// )
// .await?;
// println!("Selling tokens to PumpSwap...");
// pumpswap
// .sell_by_percent(
// mint_pubkey,
// 100, // Sell 100% of tokens
// Some(500), // 5% slippage
// )
// .await?;
Ok(())
}
async fn test_wss() -> AnyResult<()> {
println!("Starting token subscription\n");
let ws_url = "wss://api.mainnet-beta.solana.com";
// Set commitment
let commitment = CommitmentConfig::confirmed();
// Define callback function
let callback = |event: PumpfunEvent| {
match event {
PumpfunEvent::NewDevTrade(trade_info) => {
println!("Received new dev trade event: {:?}", trade_info);
},
PumpfunEvent::NewToken(token_info) => {
println!("Received new token event: {:?}", token_info);
},
PumpfunEvent::NewUserTrade(trade_info) => {
println!("Received new trade event: {:?}", trade_info);
},
PumpfunEvent::NewBotTrade(trade_info) => {
println!("Received new bot trade event: {:?}", trade_info);
},
PumpfunEvent::Error(err) => {
println!("Received error: {}", err);
}
let callback = |event: PumpfunEvent| match event {
PumpfunEvent::NewDevTrade(trade_info) => {
println!("Received new dev trade event: {:?}", trade_info);
}
PumpfunEvent::NewToken(token_info) => {
println!("Received new token event: {:?}", token_info);
}
PumpfunEvent::NewUserTrade(trade_info) => {
println!("Received new trade event: {:?}", trade_info);
}
PumpfunEvent::NewBotTrade(trade_info) => {
println!("Received new bot trade event: {:?}", trade_info);
}
PumpfunEvent::Error(err) => {
println!("Received error: {}", err);
}
};
// Start subscription
let subscription = tokens_subscription(
ws_url,
commitment,
callback,
None
).await.unwrap();
let subscription = tokens_subscription(ws_url, commitment, callback, None)
.await
.unwrap();
// Wait for a while to receive events
tokio::time::sleep(tokio::time::Duration::from_secs(60)).await;
@@ -228,4 +294,4 @@ async fn test_wss() -> AnyResult<()> {
stop_subscription(subscription).await;
Ok(())
}
}
+231 -58
View File
@@ -1,24 +1,31 @@
use std::sync::Arc;
use std::time::Instant;
use std::str::FromStr;
use anyhow::anyhow;
use chrono;
use solana_sdk::{
compute_budget::ComputeBudgetInstruction,
instruction::{AccountMeta, Instruction},
message::{v0, AddressLookupTableAccount, VersionedMessage},
native_token::sol_to_lamports,
pubkey::Pubkey,
signature::{Keypair, Signer},
system_instruction,
transaction::VersionedTransaction,
native_token::sol_to_lamports,
};
use spl_associated_token_account::instruction::create_associated_token_account_idempotent;
use std::str::FromStr;
use std::sync::Arc;
use std::time::Instant;
use crate::common::{address_lookup_cache::get_address_lookup_table_account, nonce_cache::{self, NonceCache}, PriorityFee, SolanaRpcClient};
use crate::pumpswap::common::{calculate_with_slippage_buy, find_pool, get_buy_token_amount};
use crate::constants::pumpswap::{accounts, trade::DEFAULT_SLIPPAGE, BUY_DISCRIMINATOR};
use crate::pumpswap::common::{calculate_with_slippage_buy, find_pool, get_buy_token_amount};
use crate::swqos::FeeClient;
use crate::{
common::{
address_lookup_cache::get_address_lookup_table_account,
nonce_cache::{self, NonceCache},
PriorityFee, SolanaRpcClient,
},
pumpswap::common::{coin_creator_vault_ata, coin_creator_vault_authority},
};
// Constants for compute budget
// Increased from 64KB to 256KB to handle larger transactions
@@ -29,7 +36,10 @@ const MAX_LOADED_ACCOUNTS_DATA_SIZE_LIMIT: u32 = 256 * 1024;
/// 只有当同时提供了nonce_pubkey和nonce_program_id时才使用nonce功能
/// 如果nonce被锁定、已使用或未准备好,将返回错误
/// 成功时会锁定并标记nonce为已使用
fn add_nonce_instruction(instructions: &mut Vec<Instruction>, payer: &Keypair) -> Result<(), anyhow::Error> {
fn add_nonce_instruction(
instructions: &mut Vec<Instruction>,
payer: &Keypair,
) -> Result<(), anyhow::Error> {
let nonce_cache = NonceCache::get_instance();
let nonce_info = nonce_cache.get_nonce_info();
if let Some(nonce_pubkey) = nonce_info.nonce_account {
@@ -41,7 +51,9 @@ fn add_nonce_instruction(instructions: &mut Vec<Instruction>, payer: &Keypair) -
if nonce_info.used {
return Err(anyhow!("Nonce is used"));
}
if nonce_info.next_buy_time == 0 || chrono::Utc::now().timestamp() < nonce_info.next_buy_time {
if nonce_info.next_buy_time == 0
|| chrono::Utc::now().timestamp() < nonce_info.next_buy_time
{
return Err(anyhow!("Nonce is not ready"));
}
// 加锁 - 暂不加锁
@@ -94,14 +106,62 @@ pub async fn buy(
rpc: Arc<SolanaRpcClient>,
payer: Arc<Keypair>,
mint: Pubkey,
creator: Pubkey,
amount_sol: u64,
slippage_basis_points: Option<u64>,
priority_fee: PriorityFee,
lookup_table_key: Option<Pubkey>,
// 可选(必须全部传)
pool: Option<Pubkey>,
pool_base_token_account: Option<Pubkey>,
pool_quote_token_account: Option<Pubkey>,
user_base_token_account: Option<Pubkey>,
user_quote_token_account: Option<Pubkey>,
) -> Result<(), anyhow::Error> {
let start_time = Instant::now();
let mint = Arc::new(mint.clone());
let instructions = build_buy_instructions(rpc.clone(), payer.clone(), mint.clone(), amount_sol, slippage_basis_points).await?;
let creator = Arc::new(creator.clone());
let instructions = match (
pool,
pool_base_token_account,
pool_quote_token_account,
user_base_token_account,
user_quote_token_account,
) {
(
Some(pool),
Some(pool_base_token_account),
Some(pool_quote_token_account),
Some(user_base_token_account),
Some(user_quote_token_account),
) => {
build_buy_instructions_with_accounts(
rpc.clone(),
payer.clone(),
Arc::new(pool),
Arc::new(pool_base_token_account),
Arc::new(pool_quote_token_account),
Arc::new(user_base_token_account),
Arc::new(user_quote_token_account),
mint.clone(),
creator.clone(),
amount_sol,
slippage_basis_points,
)
.await?
}
_ => {
build_buy_instructions(
rpc.clone(),
payer.clone(),
mint.clone(),
creator.clone(),
amount_sol,
slippage_basis_points,
)
.await?
}
};
println!(" Buy transaction instructions: {:?}", start_time.elapsed());
let start_time = Instant::now();
@@ -111,7 +171,8 @@ pub async fn buy(
priority_fee.clone(),
instructions,
lookup_table_key,
).await?;
)
.await?;
println!(" Buy transaction signature: {:?}", start_time.elapsed());
let start_time = Instant::now();
@@ -127,14 +188,62 @@ pub async fn buy_with_tip(
fee_clients: Vec<Arc<FeeClient>>,
payer: Arc<Keypair>,
mint: Pubkey,
creator: Pubkey,
amount_sol: u64,
slippage_basis_points: Option<u64>,
priority_fee: PriorityFee,
lookup_table_key: Option<Pubkey>,
// 可选(必须全部传)
pool: Option<Pubkey>,
pool_base_token_account: Option<Pubkey>,
pool_quote_token_account: Option<Pubkey>,
user_base_token_account: Option<Pubkey>,
user_quote_token_account: Option<Pubkey>,
) -> Result<(), anyhow::Error> {
let start_time = Instant::now();
let mint = Arc::new(mint.clone());
let instructions = build_buy_instructions(rpc.clone(), payer.clone(), mint.clone(), amount_sol, slippage_basis_points).await?;
let creator = Arc::new(creator.clone());
let instructions = match (
pool,
pool_base_token_account,
pool_quote_token_account,
user_base_token_account,
user_quote_token_account,
) {
(
Some(pool),
Some(pool_base_token_account),
Some(pool_quote_token_account),
Some(user_base_token_account),
Some(user_quote_token_account),
) => {
build_buy_instructions_with_accounts(
rpc.clone(),
payer.clone(),
Arc::new(pool),
Arc::new(pool_base_token_account),
Arc::new(pool_quote_token_account),
Arc::new(user_base_token_account),
Arc::new(user_quote_token_account),
mint.clone(),
creator.clone(),
amount_sol,
slippage_basis_points,
)
.await?
}
_ => {
build_buy_instructions(
rpc.clone(),
payer.clone(),
mint.clone(),
creator.clone(),
amount_sol,
slippage_basis_points,
)
.await?
}
};
println!(" Buy transaction instructions: {:?}", start_time.elapsed());
let start_time = Instant::now();
@@ -151,7 +260,8 @@ pub async fn buy_with_tip(
priority_fee.clone(),
instructions.clone(),
lookup_table_key,
).await?;
)
.await?;
transactions.push(transaction);
}
@@ -164,7 +274,9 @@ pub async fn buy_with_tip(
let fee_client = fee_client.clone();
let handle = tokio::spawn(async move {
fee_client.send_transaction(crate::swqos::TradeType::Buy, &transaction).await
fee_client
.send_transaction(crate::swqos::TradeType::Buy, &transaction)
.await
});
handles.push(handle);
@@ -188,7 +300,9 @@ pub async fn build_buy_transaction(
lookup_table_key: Option<Pubkey>,
) -> Result<VersionedTransaction, anyhow::Error> {
let mut instructions = vec![
ComputeBudgetInstruction::set_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE_LIMIT),
ComputeBudgetInstruction::set_loaded_accounts_data_size_limit(
MAX_LOADED_ACCOUNTS_DATA_SIZE_LIMIT,
),
ComputeBudgetInstruction::set_compute_unit_price(priority_fee.unit_price),
ComputeBudgetInstruction::set_compute_unit_limit(priority_fee.unit_limit),
];
@@ -206,7 +320,10 @@ pub async fn build_buy_transaction(
for instruction in &instructions {
for account_meta in &instruction.accounts {
if account_meta.is_signer && account_meta.pubkey != payer.pubkey() {
return Err(anyhow!("Transaction requires a signature from an account other than the payer: {}", account_meta.pubkey));
return Err(anyhow!(
"Transaction requires a signature from an account other than the payer: {}",
account_meta.pubkey
));
}
}
}
@@ -222,7 +339,8 @@ pub async fn build_buy_transaction(
&instructions,
&address_lookup_table_accounts,
blockhash,
).map_err(|e| anyhow!(e))?;
)
.map_err(|e| anyhow!(e))?;
let versioned_message = VersionedMessage::V0(v0_message.clone());
let transaction = VersionedTransaction::try_new(versioned_message, &[&payer])?;
@@ -243,7 +361,9 @@ pub async fn build_buy_transaction_with_tip(
lookup_table_key: Option<Pubkey>,
) -> Result<VersionedTransaction, anyhow::Error> {
let mut instructions = vec![
ComputeBudgetInstruction::set_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE_LIMIT),
ComputeBudgetInstruction::set_loaded_accounts_data_size_limit(
MAX_LOADED_ACCOUNTS_DATA_SIZE_LIMIT,
),
ComputeBudgetInstruction::set_compute_unit_price(priority_fee.unit_price),
ComputeBudgetInstruction::set_compute_unit_limit(priority_fee.unit_limit),
system_instruction::transfer(
@@ -266,7 +386,10 @@ pub async fn build_buy_transaction_with_tip(
for instruction in &instructions {
for account_meta in &instruction.accounts {
if account_meta.is_signer && account_meta.pubkey != payer.pubkey() {
return Err(anyhow!("Transaction requires a signature from an account other than the payer: {}", account_meta.pubkey));
return Err(anyhow!(
"Transaction requires a signature from an account other than the payer: {}",
account_meta.pubkey
));
}
}
}
@@ -282,7 +405,8 @@ pub async fn build_buy_transaction_with_tip(
&instructions,
&address_lookup_table_accounts,
blockhash,
).map_err(|e| anyhow!(e))?;
)
.map_err(|e| anyhow!(e))?;
let versioned_message = VersionedMessage::V0(v0_message.clone());
let transaction = VersionedTransaction::try_new(versioned_message, &[&payer])?;
@@ -298,6 +422,7 @@ pub async fn build_buy_instructions(
rpc: Arc<SolanaRpcClient>,
payer: Arc<Keypair>,
mint: Arc<Pubkey>,
creator: Arc<Pubkey>,
amount_sol: u64,
slippage_basis_points: Option<u64>,
) -> Result<Vec<Instruction>, anyhow::Error> {
@@ -308,61 +433,111 @@ pub async fn build_buy_instructions(
// Find the pool for this mint
let pool = find_pool(rpc.as_ref(), mint.as_ref()).await?;
// Create the user's token account if it doesn't exist
let user_base_token_account =
spl_associated_token_account::get_associated_token_address(&payer.pubkey(), mint.as_ref());
let user_quote_token_account = spl_associated_token_account::get_associated_token_address(
&payer.pubkey(),
&accounts::WSOL_TOKEN_ACCOUNT,
);
// Get pool token accounts
let pool_base_token_account =
spl_associated_token_account::get_associated_token_address_with_program_id(
&pool,
mint.as_ref(),
&accounts::TOKEN_PROGRAM,
);
let pool_quote_token_account =
spl_associated_token_account::get_associated_token_address_with_program_id(
&pool,
&accounts::WSOL_TOKEN_ACCOUNT,
&accounts::TOKEN_PROGRAM,
);
let instructions = build_buy_instructions_with_accounts(
rpc,
payer,
Arc::new(pool),
Arc::new(pool_base_token_account),
Arc::new(pool_quote_token_account),
Arc::new(user_base_token_account),
Arc::new(user_quote_token_account),
mint,
creator,
amount_sol,
slippage_basis_points,
)
.await?;
Ok(instructions)
}
pub async fn build_buy_instructions_with_accounts(
rpc: Arc<SolanaRpcClient>,
payer: Arc<Keypair>,
pool: Arc<Pubkey>,
pool_base_token_account: Arc<Pubkey>,
pool_quote_token_account: Arc<Pubkey>,
user_base_token_account: Arc<Pubkey>,
user_quote_token_account: Arc<Pubkey>,
mint: Arc<Pubkey>,
creator: Arc<Pubkey>,
amount_sol: u64,
slippage_basis_points: Option<u64>,
) -> Result<Vec<Instruction>, anyhow::Error> {
if amount_sol == 0 {
return Err(anyhow!("Amount cannot be zero"));
}
// Calculate the expected token amount
let token_amount = get_buy_token_amount(rpc.as_ref(), &pool, amount_sol).await?;
// Calculate the maximum SOL amount with slippage
let max_sol_amount = calculate_with_slippage_buy(amount_sol, slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE));
// Create the user's token account if it doesn't exist
let user_base_token_account = spl_associated_token_account::get_associated_token_address(&payer.pubkey(), mint.as_ref());
let user_quote_token_account = spl_associated_token_account::get_associated_token_address(&payer.pubkey(), &accounts::WSOL_TOKEN_ACCOUNT);
// Get pool token accounts
let pool_base_token_account = spl_associated_token_account::get_associated_token_address_with_program_id(
&pool,
mint.as_ref(),
&accounts::TOKEN_PROGRAM,
);
let pool_quote_token_account = spl_associated_token_account::get_associated_token_address_with_program_id(
&pool,
&accounts::WSOL_TOKEN_ACCOUNT,
&accounts::TOKEN_PROGRAM,
let max_sol_amount = calculate_with_slippage_buy(
amount_sol,
slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE),
);
let mut instructions = vec![];
// Create the user's base token account if it doesn't exist
instructions.push(
create_associated_token_account_idempotent(
&payer.pubkey(),
&payer.pubkey(),
mint.as_ref(),
&accounts::TOKEN_PROGRAM,
)
);
instructions.push(create_associated_token_account_idempotent(
&payer.pubkey(),
&payer.pubkey(),
mint.as_ref(),
&accounts::TOKEN_PROGRAM,
));
let coin_creator_vault_ata = coin_creator_vault_ata(*creator.as_ref());
let coin_creator_vault_authority = coin_creator_vault_authority(*creator.as_ref());
// Create the buy instruction
// 注意:账户顺序必须与JavaScript SDK匹配
let accounts = vec![
solana_sdk::instruction::AccountMeta::new_readonly(pool, false), // pool_id (readonly)
solana_sdk::instruction::AccountMeta::new(payer.pubkey(), true), // user (signer)
solana_sdk::instruction::AccountMeta::new_readonly(*pool, false), // pool_id (readonly)
solana_sdk::instruction::AccountMeta::new(payer.pubkey(), true), // user (signer)
solana_sdk::instruction::AccountMeta::new_readonly(accounts::GLOBAL_ACCOUNT, false), // global (readonly)
solana_sdk::instruction::AccountMeta::new_readonly(*mint, false), // mint (readonly)
solana_sdk::instruction::AccountMeta::new_readonly(accounts::WSOL_TOKEN_ACCOUNT, false), // WSOL_TOKEN_ACCOUNT (readonly)
solana_sdk::instruction::AccountMeta::new(user_base_token_account, false), // user_base_token_account
solana_sdk::instruction::AccountMeta::new(user_quote_token_account, false), // user_quote_token_account
solana_sdk::instruction::AccountMeta::new(pool_base_token_account, false), // pool_base_token_account
solana_sdk::instruction::AccountMeta::new(pool_quote_token_account, false), // pool_quote_token_account
solana_sdk::instruction::AccountMeta::new(*user_base_token_account, false), // user_base_token_account
solana_sdk::instruction::AccountMeta::new(*user_quote_token_account, false), // user_quote_token_account
solana_sdk::instruction::AccountMeta::new(*pool_base_token_account, false), // pool_base_token_account
solana_sdk::instruction::AccountMeta::new(*pool_quote_token_account, false), // pool_quote_token_account
solana_sdk::instruction::AccountMeta::new_readonly(accounts::FEE_RECIPIENT, false), // fee_recipient (readonly)
solana_sdk::instruction::AccountMeta::new(accounts::FEE_RECIPIENT_ATA, false), // fee_recipient_ata
solana_sdk::instruction::AccountMeta::new_readonly(accounts::TOKEN_PROGRAM, false), // TOKEN_PROGRAM_ID (readonly)
solana_sdk::instruction::AccountMeta::new_readonly(accounts::TOKEN_PROGRAM, false), // TOKEN_PROGRAM_ID (readonly, duplicated as in JS)
solana_sdk::instruction::AccountMeta::new_readonly(accounts::SYSTEM_PROGRAM, false), // System Program (readonly)
solana_sdk::instruction::AccountMeta::new_readonly(accounts::ASSOCIATED_TOKEN_PROGRAM, false), // ASSOCIATED_TOKEN_PROGRAM_ID (readonly)
solana_sdk::instruction::AccountMeta::new_readonly(
accounts::ASSOCIATED_TOKEN_PROGRAM,
false,
), // ASSOCIATED_TOKEN_PROGRAM_ID (readonly)
solana_sdk::instruction::AccountMeta::new_readonly(accounts::EVENT_AUTHORITY, false), // event_authority (readonly)
solana_sdk::instruction::AccountMeta::new_readonly(accounts::AMM_PROGRAM, false), // PUMP_AMM_PROGRAM_ID (readonly)
solana_sdk::instruction::AccountMeta::new(coin_creator_vault_ata, false), // coin_creator_vault_ata
solana_sdk::instruction::AccountMeta::new_readonly(coin_creator_vault_authority, false), // coin_creator_vault_authority (readonly)
];
// Create the instruction data
@@ -371,13 +546,11 @@ pub async fn build_buy_instructions(
data.extend_from_slice(&token_amount.to_le_bytes());
data.extend_from_slice(&max_sol_amount.to_le_bytes());
instructions.push(
Instruction {
program_id: accounts::AMM_PROGRAM,
accounts,
data,
}
);
instructions.push(Instruction {
program_id: accounts::AMM_PROGRAM,
accounts,
data,
});
Ok(instructions)
}
+19
View File
@@ -64,3 +64,22 @@ pub async fn get_sell_sol_amount(
let pool_data = crate::pumpswap::pool::Pool::fetch(rpc, pool).await?;
pool_data.calculate_sell_amount(rpc, token_amount).await
}
pub(crate) fn coin_creator_vault_authority(coin_creator: Pubkey) -> Pubkey {
let (pump_pool_authority, _) = Pubkey::find_program_address(
&[b"creator_vault", &coin_creator.to_bytes()],
&crate::constants::pumpswap::accounts::AMM_PROGRAM,
);
pump_pool_authority
}
pub(crate) fn coin_creator_vault_ata(coin_creator: Pubkey) -> Pubkey {
let creator_vault_authority = coin_creator_vault_authority(coin_creator);
let associated_token_creator_vault_authority =
spl_associated_token_account::get_associated_token_address_with_program_id(
&creator_vault_authority,
&crate::constants::pumpswap::accounts::WSOL_TOKEN_ACCOUNT,
&crate::constants::pumpswap::accounts::TOKEN_PROGRAM,
);
associated_token_creator_vault_authority
}
+1 -1
View File
@@ -73,7 +73,7 @@ impl Pool {
) -> Result<(Pubkey, Self), anyhow::Error> {
// 使用getProgramAccounts查找给定mint的池子
let filters = vec![
solana_rpc_client_api::filter::RpcFilterType::DataSize(211), // Pool账户的大小
// solana_rpc_client_api::filter::RpcFilterType::DataSize(211), // Pool账户的大小
solana_rpc_client_api::filter::RpcFilterType::Memcmp(
solana_client::rpc_filter::Memcmp::new_base58_encoded(43, &mint.to_bytes()),
),
+326 -67
View File
@@ -1,22 +1,29 @@
use std::sync::Arc;
use std::time::Instant;
use std::str::FromStr;
use anyhow::anyhow;
use solana_sdk::{
compute_budget::ComputeBudgetInstruction,
instruction::Instruction,
native_token::sol_to_lamports,
pubkey::Pubkey,
signature::{Keypair, Signer},
system_instruction,
transaction::VersionedTransaction,
native_token::sol_to_lamports,
};
use spl_associated_token_account::instruction::create_associated_token_account_idempotent;
use std::str::FromStr;
use std::sync::Arc;
use std::time::Instant;
use crate::common::{address_lookup_cache::get_address_lookup_table_account, PriorityFee, SolanaRpcClient};
use crate::pumpswap::common::{calculate_with_slippage_sell, find_pool, get_sell_sol_amount, get_token_balance};
use crate::constants::pumpswap::{accounts, trade::DEFAULT_SLIPPAGE, SELL_DISCRIMINATOR};
use crate::pumpswap::common::{
calculate_with_slippage_sell, find_pool, get_sell_sol_amount, get_token_balance,
};
use crate::swqos::FeeClient;
use crate::{
common::{
address_lookup_cache::get_address_lookup_table_account, PriorityFee, SolanaRpcClient,
},
pumpswap::common::{coin_creator_vault_ata, coin_creator_vault_authority},
};
// Constants for compute budget
// Increased from 64KB to 256KB to handle larger transactions
@@ -27,13 +34,60 @@ pub async fn sell(
rpc: Arc<SolanaRpcClient>,
payer: Arc<Keypair>,
mint: Pubkey,
creator: Pubkey,
amount_token: Option<u64>,
slippage_basis_points: Option<u64>,
priority_fee: PriorityFee,
lookup_table_key: Option<Pubkey>
lookup_table_key: Option<Pubkey>,
// 可选(必须全部传)
pool: Option<Pubkey>,
pool_base_token_account: Option<Pubkey>,
pool_quote_token_account: Option<Pubkey>,
user_base_token_account: Option<Pubkey>,
user_quote_token_account: Option<Pubkey>,
) -> Result<(), anyhow::Error> {
let start_time = Instant::now();
let instructions = build_sell_instructions(rpc.clone(), payer.clone(), mint.clone(), amount_token, slippage_basis_points).await?;
let instructions = match (
pool,
pool_base_token_account,
pool_quote_token_account,
user_base_token_account,
user_quote_token_account,
) {
(
Some(pool),
Some(pool_base_token_account),
Some(pool_quote_token_account),
Some(user_base_token_account),
Some(user_quote_token_account),
) => {
build_sell_instructions_with_accounts(
rpc.clone(),
payer.clone(),
Arc::new(pool),
Arc::new(pool_base_token_account),
Arc::new(pool_quote_token_account),
Arc::new(user_base_token_account),
Arc::new(user_quote_token_account),
Arc::new(mint),
Arc::new(creator),
amount_token,
slippage_basis_points,
)
.await?
}
_ => {
build_sell_instructions(
rpc.clone(),
payer.clone(),
mint.clone(),
creator.clone(),
amount_token,
slippage_basis_points,
)
.await?
}
};
println!(" Sell transaction instructions: {:?}", start_time.elapsed());
let start_time = Instant::now();
@@ -44,8 +98,9 @@ pub async fn sell(
priority_fee,
instructions,
lookup_table_key,
recent_blockhash
).await?;
recent_blockhash,
)
.await?;
println!(" Sell transaction signature: {:?}", start_time.elapsed());
let start_time = Instant::now();
@@ -59,10 +114,17 @@ pub async fn sell_by_percent(
rpc: Arc<SolanaRpcClient>,
payer: Arc<Keypair>,
mint: Pubkey,
creator: Pubkey,
percent: u64,
slippage_basis_points: Option<u64>,
priority_fee: PriorityFee,
lookup_table_key: Option<Pubkey>
lookup_table_key: Option<Pubkey>,
// 可选(必须全部传)
pool: Option<Pubkey>,
pool_base_token_account: Option<Pubkey>,
pool_quote_token_account: Option<Pubkey>,
user_base_token_account: Option<Pubkey>,
user_quote_token_account: Option<Pubkey>,
) -> Result<(), anyhow::Error> {
if percent == 0 || percent > 100 {
return Err(anyhow!("Percentage must be between 1 and 100"));
@@ -70,7 +132,22 @@ pub async fn sell_by_percent(
let (balance_u64, _) = get_token_balance(rpc.as_ref(), payer.as_ref(), &mint).await?;
let amount = balance_u64 * percent / 100;
sell(rpc, payer, mint, Some(amount), slippage_basis_points, priority_fee, lookup_table_key).await
sell(
rpc,
payer,
mint,
creator,
Some(amount),
slippage_basis_points,
priority_fee,
lookup_table_key,
pool,
pool_base_token_account,
pool_quote_token_account,
user_base_token_account,
user_quote_token_account,
)
.await
}
/// Sell tokens by amount
@@ -78,16 +155,38 @@ pub async fn sell_by_amount(
rpc: Arc<SolanaRpcClient>,
payer: Arc<Keypair>,
mint: Pubkey,
creator: Pubkey,
amount: u64,
slippage_basis_points: Option<u64>,
priority_fee: PriorityFee,
lookup_table_key: Option<Pubkey>
lookup_table_key: Option<Pubkey>,
// 可选(必须全部传)
pool: Option<Pubkey>,
pool_base_token_account: Option<Pubkey>,
pool_quote_token_account: Option<Pubkey>,
user_base_token_account: Option<Pubkey>,
user_quote_token_account: Option<Pubkey>,
) -> Result<(), anyhow::Error> {
if amount == 0 {
return Err(anyhow!("Amount must be greater than 0"));
}
sell(rpc, payer, mint, Some(amount), slippage_basis_points, priority_fee, lookup_table_key).await
sell(
rpc,
payer,
mint,
creator,
Some(amount),
slippage_basis_points,
priority_fee,
lookup_table_key,
pool,
pool_base_token_account,
pool_quote_token_account,
user_base_token_account,
user_quote_token_account,
)
.await
}
// Sell tokens using a MEV service
@@ -96,13 +195,60 @@ pub async fn sell_with_tip(
fee_clients: Vec<Arc<FeeClient>>,
payer: Arc<Keypair>,
mint: Pubkey,
creator: Pubkey,
amount_token: Option<u64>,
slippage_basis_points: Option<u64>,
priority_fee: PriorityFee,
lookup_table_key: Option<Pubkey>
lookup_table_key: Option<Pubkey>,
// 可选(必须全部传)
pool: Option<Pubkey>,
pool_base_token_account: Option<Pubkey>,
pool_quote_token_account: Option<Pubkey>,
user_base_token_account: Option<Pubkey>,
user_quote_token_account: Option<Pubkey>,
) -> Result<(), anyhow::Error> {
let mut transactions = vec![];
let instructions = build_sell_instructions(rpc.clone(), payer.clone(), mint.clone(), amount_token, slippage_basis_points).await?;
let instructions = match (
pool,
pool_base_token_account,
pool_quote_token_account,
user_base_token_account,
user_quote_token_account,
) {
(
Some(pool),
Some(pool_base_token_account),
Some(pool_quote_token_account),
Some(user_base_token_account),
Some(user_quote_token_account),
) => {
build_sell_instructions_with_accounts(
rpc.clone(),
payer.clone(),
Arc::new(pool),
Arc::new(pool_base_token_account),
Arc::new(pool_quote_token_account),
Arc::new(user_base_token_account),
Arc::new(user_quote_token_account),
Arc::new(mint),
Arc::new(creator),
amount_token,
slippage_basis_points,
)
.await?
}
_ => {
build_sell_instructions(
rpc.clone(),
payer.clone(),
mint.clone(),
creator.clone(),
amount_token,
slippage_basis_points,
)
.await?
}
};
let recent_blockhash = rpc.get_latest_blockhash().await?;
for fee_client in fee_clients.clone() {
@@ -117,7 +263,8 @@ pub async fn sell_with_tip(
instructions.clone(),
lookup_table_key,
recent_blockhash,
).await?;
)
.await?;
transactions.push(transaction);
}
@@ -128,7 +275,9 @@ pub async fn sell_with_tip(
let fee_client = fee_client.clone();
let handle = tokio::spawn(async move {
fee_client.send_transaction(crate::swqos::TradeType::Sell, &transaction).await
fee_client
.send_transaction(crate::swqos::TradeType::Sell, &transaction)
.await
});
handles.push(handle);
@@ -147,10 +296,17 @@ pub async fn sell_by_percent_with_tip(
fee_clients: Vec<Arc<FeeClient>>,
payer: Arc<Keypair>,
mint: Pubkey,
creator: Pubkey,
percent: u64,
slippage_basis_points: Option<u64>,
priority_fee: PriorityFee,
lookup_table_key: Option<Pubkey>
lookup_table_key: Option<Pubkey>,
// 可选(必须全部传)
pool: Option<Pubkey>,
pool_base_token_account: Option<Pubkey>,
pool_quote_token_account: Option<Pubkey>,
user_base_token_account: Option<Pubkey>,
user_quote_token_account: Option<Pubkey>,
) -> Result<(), anyhow::Error> {
if percent == 0 || percent > 100 {
return Err(anyhow!("Percentage must be between 1 and 100"));
@@ -158,7 +314,23 @@ pub async fn sell_by_percent_with_tip(
let (balance_u64, _) = get_token_balance(rpc.as_ref(), payer.as_ref(), &mint).await?;
let amount = balance_u64 * percent / 100;
sell_with_tip(rpc, fee_clients, payer, mint, Some(amount), slippage_basis_points, priority_fee, lookup_table_key).await
sell_with_tip(
rpc,
fee_clients,
payer,
mint,
creator,
Some(amount),
slippage_basis_points,
priority_fee,
lookup_table_key,
pool,
pool_base_token_account,
pool_quote_token_account,
user_base_token_account,
user_quote_token_account,
)
.await
}
// Sell tokens by amount using a MEV service
@@ -167,16 +339,39 @@ pub async fn sell_by_amount_with_tip(
fee_clients: Vec<Arc<FeeClient>>,
payer: Arc<Keypair>,
mint: Pubkey,
creator: Pubkey,
amount: u64,
slippage_basis_points: Option<u64>,
priority_fee: PriorityFee,
lookup_table_key: Option<Pubkey>
lookup_table_key: Option<Pubkey>,
// 可选(必须全部传)
pool: Option<Pubkey>,
pool_base_token_account: Option<Pubkey>,
pool_quote_token_account: Option<Pubkey>,
user_base_token_account: Option<Pubkey>,
user_quote_token_account: Option<Pubkey>,
) -> Result<(), anyhow::Error> {
if amount == 0 {
return Err(anyhow!("Amount must be greater than 0"));
}
sell_with_tip(rpc, fee_clients, payer, mint, Some(amount), slippage_basis_points, priority_fee, lookup_table_key).await
sell_with_tip(
rpc,
fee_clients,
payer,
mint,
creator,
Some(amount),
slippage_basis_points,
priority_fee,
lookup_table_key,
pool,
pool_base_token_account,
pool_quote_token_account,
user_base_token_account,
user_quote_token_account,
)
.await
}
// Build a transaction for selling tokens
@@ -189,7 +384,9 @@ pub async fn build_sell_transaction(
recent_blockhash: solana_sdk::hash::Hash,
) -> Result<VersionedTransaction, anyhow::Error> {
let mut instructions = vec![
ComputeBudgetInstruction::set_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE_LIMIT),
ComputeBudgetInstruction::set_loaded_accounts_data_size_limit(
MAX_LOADED_ACCOUNTS_DATA_SIZE_LIMIT,
),
ComputeBudgetInstruction::set_compute_unit_price(priority_fee.unit_price),
ComputeBudgetInstruction::set_compute_unit_limit(priority_fee.unit_limit),
];
@@ -200,7 +397,10 @@ pub async fn build_sell_transaction(
for instruction in &instructions {
for account_meta in &instruction.accounts {
if account_meta.is_signer && account_meta.pubkey != payer.pubkey() {
return Err(anyhow!("Transaction requires a signature from an account other than the payer: {}", account_meta.pubkey));
return Err(anyhow!(
"Transaction requires a signature from an account other than the payer: {}",
account_meta.pubkey
));
}
}
}
@@ -216,7 +416,8 @@ pub async fn build_sell_transaction(
&instructions,
&address_lookup_table_accounts,
recent_blockhash,
).map_err(|e| anyhow!(e))?;
)
.map_err(|e| anyhow!(e))?;
let versioned_message = solana_sdk::message::VersionedMessage::V0(v0_message);
let transaction = VersionedTransaction::try_new(versioned_message, &[&payer])?;
@@ -235,7 +436,9 @@ pub async fn build_sell_transaction_with_tip(
recent_blockhash: solana_sdk::hash::Hash,
) -> Result<VersionedTransaction, anyhow::Error> {
let mut instructions = vec![
ComputeBudgetInstruction::set_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE_LIMIT),
ComputeBudgetInstruction::set_loaded_accounts_data_size_limit(
MAX_LOADED_ACCOUNTS_DATA_SIZE_LIMIT,
),
ComputeBudgetInstruction::set_compute_unit_price(priority_fee.unit_price),
ComputeBudgetInstruction::set_compute_unit_limit(priority_fee.unit_limit),
system_instruction::transfer(
@@ -251,7 +454,10 @@ pub async fn build_sell_transaction_with_tip(
for instruction in &instructions {
for account_meta in &instruction.accounts {
if account_meta.is_signer && account_meta.pubkey != payer.pubkey() {
return Err(anyhow!("Transaction requires a signature from an account other than the payer: {}", account_meta.pubkey));
return Err(anyhow!(
"Transaction requires a signature from an account other than the payer: {}",
account_meta.pubkey
));
}
}
}
@@ -267,7 +473,8 @@ pub async fn build_sell_transaction_with_tip(
&instructions,
&address_lookup_table_accounts,
recent_blockhash,
).map_err(|e| anyhow!(e))?;
)
.map_err(|e| anyhow!(e))?;
let versioned_message = solana_sdk::message::VersionedMessage::V0(v0_message);
let transaction = VersionedTransaction::try_new(versioned_message, &[&payer])?;
@@ -280,6 +487,7 @@ pub async fn build_sell_instructions(
rpc: Arc<SolanaRpcClient>,
payer: Arc<Keypair>,
mint: Pubkey,
creator: Pubkey,
amount_token: Option<u64>,
slippage_basis_points: Option<u64>,
) -> Result<Vec<Instruction>, anyhow::Error> {
@@ -293,61 +501,114 @@ pub async fn build_sell_instructions(
// Find the pool for this mint
let pool = find_pool(rpc.as_ref(), &mint).await?;
// Get token accounts
let user_base_token_account =
spl_associated_token_account::get_associated_token_address(&payer.pubkey(), &mint);
let user_quote_token_account = spl_associated_token_account::get_associated_token_address(
&payer.pubkey(),
&accounts::WSOL_TOKEN_ACCOUNT,
);
// Get pool token accounts
let pool_base_token_account =
spl_associated_token_account::get_associated_token_address_with_program_id(
&pool,
&mint,
&accounts::TOKEN_PROGRAM,
);
let pool_quote_token_account =
spl_associated_token_account::get_associated_token_address_with_program_id(
&pool,
&accounts::WSOL_TOKEN_ACCOUNT,
&accounts::TOKEN_PROGRAM,
);
let instructions = build_sell_instructions_with_accounts(
rpc,
payer,
Arc::new(pool),
Arc::new(pool_base_token_account),
Arc::new(pool_quote_token_account),
Arc::new(user_base_token_account),
Arc::new(user_quote_token_account),
Arc::new(mint),
Arc::new(creator),
amount_token,
slippage_basis_points,
)
.await?;
Ok(instructions)
}
pub async fn build_sell_instructions_with_accounts(
rpc: Arc<SolanaRpcClient>,
payer: Arc<Keypair>,
pool: Arc<Pubkey>,
pool_base_token_account: Arc<Pubkey>,
pool_quote_token_account: Arc<Pubkey>,
user_base_token_account: Arc<Pubkey>,
user_quote_token_account: Arc<Pubkey>,
mint: Arc<Pubkey>,
creator: Arc<Pubkey>,
amount_token: Option<u64>,
slippage_basis_points: Option<u64>,
) -> Result<Vec<Instruction>, anyhow::Error> {
let (balance_u64, _) = get_token_balance(rpc.as_ref(), payer.as_ref(), &mint).await?;
let amount = amount_token.unwrap_or(balance_u64);
if amount == 0 {
return Err(anyhow!("Amount cannot be zero"));
}
// Calculate the expected SOL amount
let sol_amount = get_sell_sol_amount(rpc.as_ref(), &pool, amount).await?;
// Calculate the minimum SOL amount with slippage
let min_sol_amount = calculate_with_slippage_sell(sol_amount, slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE));
// Get token accounts
let user_base_token_account = spl_associated_token_account::get_associated_token_address(&payer.pubkey(), &mint);
let user_quote_token_account = spl_associated_token_account::get_associated_token_address(&payer.pubkey(), &accounts::WSOL_TOKEN_ACCOUNT);
// Get pool token accounts
let pool_base_token_account = spl_associated_token_account::get_associated_token_address_with_program_id(
&pool,
&mint,
&accounts::TOKEN_PROGRAM,
let min_sol_amount = calculate_with_slippage_sell(
sol_amount,
slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE),
);
let pool_quote_token_account = spl_associated_token_account::get_associated_token_address_with_program_id(
&pool,
&accounts::WSOL_TOKEN_ACCOUNT,
&accounts::TOKEN_PROGRAM,
);
let coin_creator_vault_ata = coin_creator_vault_ata(*creator.as_ref());
let coin_creator_vault_authority = coin_creator_vault_authority(*creator.as_ref());
let mut instructions = vec![];
// Create the user's token account if it doesn't exist
instructions.push(
create_associated_token_account_idempotent(
&payer.pubkey(),
&payer.pubkey(),
&mint,
&accounts::TOKEN_PROGRAM,
)
);
instructions.push(create_associated_token_account_idempotent(
&payer.pubkey(),
&payer.pubkey(),
&mint,
&accounts::TOKEN_PROGRAM,
));
// Create the sell instruction
// 注意:账户顺序必须与JavaScript SDK匹配
let accounts = vec![
solana_sdk::instruction::AccountMeta::new_readonly(pool, false), // pool_id (readonly)
solana_sdk::instruction::AccountMeta::new(payer.pubkey(), true), // user (signer)
solana_sdk::instruction::AccountMeta::new_readonly(*pool, false), // pool_id (readonly)
solana_sdk::instruction::AccountMeta::new(payer.pubkey(), true), // user (signer)
solana_sdk::instruction::AccountMeta::new_readonly(accounts::GLOBAL_ACCOUNT, false), // global (readonly)
solana_sdk::instruction::AccountMeta::new_readonly(mint, false), // mint (readonly)
solana_sdk::instruction::AccountMeta::new_readonly(*mint, false), // mint (readonly)
solana_sdk::instruction::AccountMeta::new_readonly(accounts::WSOL_TOKEN_ACCOUNT, false), // WSOL_TOKEN_ACCOUNT (readonly)
solana_sdk::instruction::AccountMeta::new(user_base_token_account, false), // user_base_token_account
solana_sdk::instruction::AccountMeta::new(user_quote_token_account, false), // user_quote_token_account
solana_sdk::instruction::AccountMeta::new(pool_base_token_account, false), // pool_base_token_account
solana_sdk::instruction::AccountMeta::new(pool_quote_token_account, false), // pool_quote_token_account
solana_sdk::instruction::AccountMeta::new(*user_base_token_account, false), // user_base_token_account
solana_sdk::instruction::AccountMeta::new(*user_quote_token_account, false), // user_quote_token_account
solana_sdk::instruction::AccountMeta::new(*pool_base_token_account, false), // pool_base_token_account
solana_sdk::instruction::AccountMeta::new(*pool_quote_token_account, false), // pool_quote_token_account
solana_sdk::instruction::AccountMeta::new_readonly(accounts::FEE_RECIPIENT, false), // fee_recipient (readonly)
solana_sdk::instruction::AccountMeta::new(accounts::FEE_RECIPIENT_ATA, false), // fee_recipient_ata
solana_sdk::instruction::AccountMeta::new_readonly(accounts::TOKEN_PROGRAM, false), // TOKEN_PROGRAM_ID (readonly)
solana_sdk::instruction::AccountMeta::new_readonly(accounts::TOKEN_PROGRAM, false), // TOKEN_PROGRAM_ID (readonly, duplicated as in JS)
solana_sdk::instruction::AccountMeta::new_readonly(accounts::SYSTEM_PROGRAM, false), // System Program (readonly)
solana_sdk::instruction::AccountMeta::new_readonly(accounts::ASSOCIATED_TOKEN_PROGRAM, false), // ASSOCIATED_TOKEN_PROGRAM_ID (readonly)
solana_sdk::instruction::AccountMeta::new_readonly(
accounts::ASSOCIATED_TOKEN_PROGRAM,
false,
), // ASSOCIATED_TOKEN_PROGRAM_ID (readonly)
solana_sdk::instruction::AccountMeta::new_readonly(accounts::EVENT_AUTHORITY, false), // event_authority (readonly)
solana_sdk::instruction::AccountMeta::new_readonly(accounts::AMM_PROGRAM, false), // PUMP_AMM_PROGRAM_ID (readonly)
solana_sdk::instruction::AccountMeta::new(coin_creator_vault_ata, false), // coin_creator_vault_ata
solana_sdk::instruction::AccountMeta::new_readonly(coin_creator_vault_authority, false), // coin_creator_vault_authority (readonly)
];
// Create the instruction data
@@ -356,13 +617,11 @@ pub async fn build_sell_instructions(
data.extend_from_slice(&amount.to_le_bytes());
data.extend_from_slice(&min_sol_amount.to_le_bytes());
instructions.push(
Instruction {
program_id: accounts::AMM_PROGRAM,
accounts,
data,
}
);
instructions.push(Instruction {
program_id: accounts::AMM_PROGRAM,
accounts,
data,
});
Ok(instructions)
}