From 165335af11ac0cd964b5043d1183f2129bd80995 Mon Sep 17 00:00:00 2001 From: sgxiang Date: Thu, 12 Jun 2025 00:00:30 +0800 Subject: [PATCH] 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 --- src/common/pumpswap/logs_data.rs | 26 ++ src/common/pumpswap/logs_parser.rs | 16 +- src/lib.rs | 36 +++ src/main.rs | 282 +++++++++++++-------- src/pumpswap/buy.rs | 289 ++++++++++++++++----- src/pumpswap/common.rs | 19 ++ src/pumpswap/pool.rs | 2 +- src/pumpswap/sell.rs | 393 ++++++++++++++++++++++++----- 8 files changed, 828 insertions(+), 235 deletions(-) diff --git a/src/common/pumpswap/logs_data.rs b/src/common/pumpswap/logs_data.rs index 4c3cd26..58a67d1 100644 --- a/src/common/pumpswap/logs_data.rs +++ b/src/common/pumpswap/logs_data.rs @@ -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, } /// 创建池子事件 diff --git a/src/common/pumpswap/logs_parser.rs b/src/common/pumpswap/logs_parser.rs index 961945c..e62eb84 100755 --- a/src/common/pumpswap/logs_parser.rs +++ b/src/common/pumpswap/logs_parser.rs @@ -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() })) }, diff --git a/src/lib.rs b/src/lib.rs index 2f19e10..17cb4ca 100755 --- a/src/lib.rs +++ b/src/lib.rs @@ -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)) diff --git a/src/main.rs b/src/main.rs index 9cc1c50..1badebf 100755 --- a/src/main.rs +++ b/src/main.rs @@ -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> { // 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> { - 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> { 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> { async fn test_pumpfun_with_grpc() -> Result<(), Box> { 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> { // 使用 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> { 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> { 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(()) -} \ No newline at end of file +} diff --git a/src/pumpswap/buy.rs b/src/pumpswap/buy.rs index f06f5ad..94516c4 100644 --- a/src/pumpswap/buy.rs +++ b/src/pumpswap/buy.rs @@ -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, payer: &Keypair) -> Result<(), anyhow::Error> { +fn add_nonce_instruction( + instructions: &mut Vec, + 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, 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, payer: Arc, mint: Pubkey, + creator: Pubkey, amount_sol: u64, slippage_basis_points: Option, priority_fee: PriorityFee, lookup_table_key: Option, + // 可选(必须全部传) + pool: Option, + pool_base_token_account: Option, + pool_quote_token_account: Option, + user_base_token_account: Option, + user_quote_token_account: Option, ) -> 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>, payer: Arc, mint: Pubkey, + creator: Pubkey, amount_sol: u64, slippage_basis_points: Option, priority_fee: PriorityFee, lookup_table_key: Option, + // 可选(必须全部传) + pool: Option, + pool_base_token_account: Option, + pool_quote_token_account: Option, + user_base_token_account: Option, + user_quote_token_account: Option, ) -> 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, ) -> Result { 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, ) -> Result { 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, payer: Arc, mint: Arc, + creator: Arc, amount_sol: u64, slippage_basis_points: Option, ) -> Result, 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, + payer: Arc, + pool: Arc, + pool_base_token_account: Arc, + pool_quote_token_account: Arc, + user_base_token_account: Arc, + user_quote_token_account: Arc, + mint: Arc, + creator: Arc, + amount_sol: u64, + slippage_basis_points: Option, +) -> Result, 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) } diff --git a/src/pumpswap/common.rs b/src/pumpswap/common.rs index 38c663b..8f3f3a6 100644 --- a/src/pumpswap/common.rs +++ b/src/pumpswap/common.rs @@ -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 +} \ No newline at end of file diff --git a/src/pumpswap/pool.rs b/src/pumpswap/pool.rs index c91952e..23d5b38 100644 --- a/src/pumpswap/pool.rs +++ b/src/pumpswap/pool.rs @@ -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()), ), diff --git a/src/pumpswap/sell.rs b/src/pumpswap/sell.rs index a9755b2..60391c5 100644 --- a/src/pumpswap/sell.rs +++ b/src/pumpswap/sell.rs @@ -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, payer: Arc, mint: Pubkey, + creator: Pubkey, amount_token: Option, slippage_basis_points: Option, priority_fee: PriorityFee, - lookup_table_key: Option + lookup_table_key: Option, + // 可选(必须全部传) + pool: Option, + pool_base_token_account: Option, + pool_quote_token_account: Option, + user_base_token_account: Option, + user_quote_token_account: Option, ) -> 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, payer: Arc, mint: Pubkey, + creator: Pubkey, percent: u64, slippage_basis_points: Option, priority_fee: PriorityFee, - lookup_table_key: Option + lookup_table_key: Option, + // 可选(必须全部传) + pool: Option, + pool_base_token_account: Option, + pool_quote_token_account: Option, + user_base_token_account: Option, + user_quote_token_account: Option, ) -> 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, payer: Arc, mint: Pubkey, + creator: Pubkey, amount: u64, slippage_basis_points: Option, priority_fee: PriorityFee, - lookup_table_key: Option + lookup_table_key: Option, + // 可选(必须全部传) + pool: Option, + pool_base_token_account: Option, + pool_quote_token_account: Option, + user_base_token_account: Option, + user_quote_token_account: Option, ) -> 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>, payer: Arc, mint: Pubkey, + creator: Pubkey, amount_token: Option, slippage_basis_points: Option, priority_fee: PriorityFee, - lookup_table_key: Option + lookup_table_key: Option, + // 可选(必须全部传) + pool: Option, + pool_base_token_account: Option, + pool_quote_token_account: Option, + user_base_token_account: Option, + user_quote_token_account: Option, ) -> 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>, payer: Arc, mint: Pubkey, + creator: Pubkey, percent: u64, slippage_basis_points: Option, priority_fee: PriorityFee, - lookup_table_key: Option + lookup_table_key: Option, + // 可选(必须全部传) + pool: Option, + pool_base_token_account: Option, + pool_quote_token_account: Option, + user_base_token_account: Option, + user_quote_token_account: Option, ) -> 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>, payer: Arc, mint: Pubkey, + creator: Pubkey, amount: u64, slippage_basis_points: Option, priority_fee: PriorityFee, - lookup_table_key: Option + lookup_table_key: Option, + // 可选(必须全部传) + pool: Option, + pool_base_token_account: Option, + pool_quote_token_account: Option, + user_base_token_account: Option, + user_quote_token_account: Option, ) -> 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 { 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 { 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, payer: Arc, mint: Pubkey, + creator: Pubkey, amount_token: Option, slippage_basis_points: Option, ) -> Result, 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, + payer: Arc, + pool: Arc, + pool_base_token_account: Arc, + pool_quote_token_account: Arc, + user_base_token_account: Arc, + user_quote_token_account: Arc, + mint: Arc, + creator: Arc, + amount_token: Option, + slippage_basis_points: Option, +) -> Result, 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) }