2025-07-09 22:29:29 +08:00
|
|
|
|
pub mod common;
|
2024-12-31 16:51:58 +08:00
|
|
|
|
pub mod constants;
|
2025-07-09 22:29:29 +08:00
|
|
|
|
pub mod instruction;
|
2025-10-06 23:40:27 +08:00
|
|
|
|
pub mod perf;
|
2025-07-10 18:14:21 +08:00
|
|
|
|
pub mod swqos;
|
2025-06-17 23:32:20 +08:00
|
|
|
|
pub mod trading;
|
2025-07-10 18:14:21 +08:00
|
|
|
|
pub mod utils;
|
2025-09-20 14:00:45 +08:00
|
|
|
|
use crate::common::nonce_cache::DurableNonceInfo;
|
2025-10-07 23:12:37 +08:00
|
|
|
|
use crate::common::GasFeeStrategy;
|
2025-09-22 00:05:20 +08:00
|
|
|
|
use crate::common::TradeConfig;
|
2025-09-07 17:22:58 +08:00
|
|
|
|
use crate::constants::trade::trade::DEFAULT_SLIPPAGE;
|
2025-09-22 00:05:20 +08:00
|
|
|
|
use crate::constants::SOL_TOKEN_ACCOUNT;
|
2025-09-22 01:09:41 +08:00
|
|
|
|
use crate::constants::USD1_TOKEN_ACCOUNT;
|
2025-10-12 12:01:21 +08:00
|
|
|
|
use crate::constants::USDC_TOKEN_ACCOUNT;
|
2025-11-24 22:52:56 +08:00
|
|
|
|
use crate::constants::WSOL_TOKEN_ACCOUNT;
|
|
|
|
|
|
use crate::swqos::common::TradeError;
|
2025-09-19 00:49:45 +08:00
|
|
|
|
use crate::swqos::SwqosClient;
|
2025-09-17 11:14:15 +08:00
|
|
|
|
use crate::swqos::SwqosConfig;
|
2025-09-22 01:25:32 +08:00
|
|
|
|
use crate::swqos::TradeType;
|
2025-07-10 18:14:21 +08:00
|
|
|
|
use crate::trading::core::params::BonkParams;
|
2025-10-05 22:27:04 +08:00
|
|
|
|
use crate::trading::core::params::MeteoraDammV2Params;
|
2025-07-10 18:14:21 +08:00
|
|
|
|
use crate::trading::core::params::PumpFunParams;
|
|
|
|
|
|
use crate::trading::core::params::PumpSwapParams;
|
2025-08-19 18:07:21 +08:00
|
|
|
|
use crate::trading::core::params::RaydiumAmmV4Params;
|
2025-07-16 22:54:23 +08:00
|
|
|
|
use crate::trading::core::params::RaydiumCpmmParams;
|
2025-12-08 01:35:40 +08:00
|
|
|
|
use crate::trading::core::params::DexParamEnum;
|
2025-07-10 22:15:53 +08:00
|
|
|
|
use crate::trading::factory::DexType;
|
2025-09-19 00:49:45 +08:00
|
|
|
|
use crate::trading::MiddlewareManager;
|
2025-09-22 00:05:20 +08:00
|
|
|
|
use crate::trading::SwapParams;
|
2025-07-10 18:14:21 +08:00
|
|
|
|
use crate::trading::TradeFactory;
|
2025-09-19 00:49:45 +08:00
|
|
|
|
use common::SolanaRpcClient;
|
2025-09-09 17:02:24 +08:00
|
|
|
|
use parking_lot::Mutex;
|
2025-07-10 18:14:21 +08:00
|
|
|
|
use rustls::crypto::{ring::default_provider, CryptoProvider};
|
|
|
|
|
|
use solana_sdk::hash::Hash;
|
2025-10-07 20:56:02 +08:00
|
|
|
|
use solana_sdk::message::AddressLookupTableAccount;
|
2025-09-19 00:49:45 +08:00
|
|
|
|
use solana_sdk::signer::Signer;
|
2025-09-09 12:03:42 +08:00
|
|
|
|
use solana_sdk::{pubkey::Pubkey, signature::Keypair, signature::Signature};
|
2025-02-21 20:47:28 +08:00
|
|
|
|
use std::sync::Arc;
|
2025-01-14 19:18:48 +08:00
|
|
|
|
|
2025-09-22 23:18:53 +08:00
|
|
|
|
/// Type of the token to buy
|
|
|
|
|
|
#[derive(Clone, PartialEq)]
|
|
|
|
|
|
pub enum TradeTokenType {
|
|
|
|
|
|
SOL,
|
|
|
|
|
|
WSOL,
|
|
|
|
|
|
USD1,
|
2025-10-12 12:01:21 +08:00
|
|
|
|
USDC,
|
2025-09-22 23:18:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-19 00:49:45 +08:00
|
|
|
|
/// Main trading client for Solana DeFi protocols
|
|
|
|
|
|
///
|
2025-12-08 01:35:40 +08:00
|
|
|
|
/// `SolTradingSDK` provides a unified interface for trading across multiple Solana DEXs
|
2025-09-19 00:49:45 +08:00
|
|
|
|
/// including PumpFun, PumpSwap, Bonk, Raydium AMM V4, and Raydium CPMM.
|
|
|
|
|
|
/// It manages RPC connections, transaction signing, and SWQOS (Solana Web Quality of Service) settings.
|
2025-12-08 01:35:40 +08:00
|
|
|
|
pub struct TradingClient {
|
2025-09-19 00:49:45 +08:00
|
|
|
|
/// The keypair used for signing all transactions
|
2024-12-31 16:51:58 +08:00
|
|
|
|
pub payer: Arc<Keypair>,
|
2025-09-19 00:49:45 +08:00
|
|
|
|
/// RPC client for blockchain interactions
|
2025-04-02 13:39:59 +08:00
|
|
|
|
pub rpc: Arc<SolanaRpcClient>,
|
2025-09-19 00:49:45 +08:00
|
|
|
|
/// SWQOS clients for transaction priority and routing
|
2025-09-17 11:14:15 +08:00
|
|
|
|
pub swqos_clients: Vec<Arc<SwqosClient>>,
|
2025-09-19 00:49:45 +08:00
|
|
|
|
/// Optional middleware manager for custom transaction processing
|
2025-08-19 18:07:21 +08:00
|
|
|
|
pub middleware_manager: Option<Arc<MiddlewareManager>>,
|
2025-11-07 16:57:28 +08:00
|
|
|
|
/// Whether to use seed optimization for all ATA operations (default: true)
|
|
|
|
|
|
/// Applies to all token account creations across buy and sell operations
|
|
|
|
|
|
pub use_seed_optimize: bool,
|
2025-04-02 13:39:59 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-08 01:35:40 +08:00
|
|
|
|
static INSTANCE: Mutex<Option<Arc<TradingClient>>> = Mutex::new(None);
|
2025-06-07 23:16:46 +08:00
|
|
|
|
|
2025-12-08 01:35:40 +08:00
|
|
|
|
/// 🔄 向后兼容:SolanaTrade 别名
|
|
|
|
|
|
pub type SolanaTrade = TradingClient;
|
|
|
|
|
|
|
|
|
|
|
|
impl Clone for TradingClient {
|
2025-04-02 13:39:59 +08:00
|
|
|
|
fn clone(&self) -> Self {
|
|
|
|
|
|
Self {
|
|
|
|
|
|
payer: self.payer.clone(),
|
|
|
|
|
|
rpc: self.rpc.clone(),
|
2025-09-17 11:14:15 +08:00
|
|
|
|
swqos_clients: self.swqos_clients.clone(),
|
2025-08-19 18:07:21 +08:00
|
|
|
|
middleware_manager: self.middleware_manager.clone(),
|
2025-11-07 16:57:28 +08:00
|
|
|
|
use_seed_optimize: self.use_seed_optimize,
|
2025-04-02 13:39:59 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-01-09 00:12:43 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-19 00:49:45 +08:00
|
|
|
|
/// Parameters for executing buy orders across different DEX protocols
|
|
|
|
|
|
///
|
|
|
|
|
|
/// Contains all necessary configuration for purchasing tokens, including
|
|
|
|
|
|
/// protocol-specific settings, account management options, and transaction preferences.
|
|
|
|
|
|
#[derive(Clone)]
|
|
|
|
|
|
pub struct TradeBuyParams {
|
|
|
|
|
|
// Trading configuration
|
|
|
|
|
|
/// The DEX protocol to use for the trade
|
|
|
|
|
|
pub dex_type: DexType,
|
2025-09-22 23:18:53 +08:00
|
|
|
|
/// Type of the token to buy
|
|
|
|
|
|
pub input_token_type: TradeTokenType,
|
2025-09-19 00:49:45 +08:00
|
|
|
|
/// Public key of the token to purchase
|
|
|
|
|
|
pub mint: Pubkey,
|
2025-09-22 23:18:53 +08:00
|
|
|
|
/// Amount of tokens to buy (in smallest token units)
|
|
|
|
|
|
pub input_token_amount: u64,
|
2025-09-19 00:49:45 +08:00
|
|
|
|
/// Optional slippage tolerance in basis points (e.g., 100 = 1%)
|
|
|
|
|
|
pub slippage_basis_points: Option<u64>,
|
|
|
|
|
|
/// Recent blockhash for transaction validity
|
2025-09-20 14:00:45 +08:00
|
|
|
|
pub recent_blockhash: Option<Hash>,
|
2025-09-19 00:49:45 +08:00
|
|
|
|
/// Protocol-specific parameters (PumpFun, Raydium, etc.)
|
2025-12-08 01:35:40 +08:00
|
|
|
|
pub extension_params: DexParamEnum,
|
2025-09-19 00:49:45 +08:00
|
|
|
|
// Extended configuration
|
|
|
|
|
|
/// Optional address lookup table for transaction size optimization
|
2025-10-07 20:56:02 +08:00
|
|
|
|
pub address_lookup_table_account: Option<AddressLookupTableAccount>,
|
2025-09-19 00:49:45 +08:00
|
|
|
|
/// Whether to wait for transaction confirmation before returning
|
|
|
|
|
|
pub wait_transaction_confirmed: bool,
|
2025-09-22 23:18:53 +08:00
|
|
|
|
/// Whether to create input token associated token account
|
|
|
|
|
|
pub create_input_token_ata: bool,
|
|
|
|
|
|
/// Whether to close input token associated token account after trade
|
|
|
|
|
|
pub close_input_token_ata: bool,
|
2025-09-19 00:49:45 +08:00
|
|
|
|
/// Whether to create token mint associated token account
|
|
|
|
|
|
pub create_mint_ata: bool,
|
2025-09-20 14:00:45 +08:00
|
|
|
|
/// Durable nonce information
|
|
|
|
|
|
pub durable_nonce: Option<DurableNonceInfo>,
|
2025-10-05 22:27:04 +08:00
|
|
|
|
/// Optional fixed output token amount (If this value is set, it will be directly assigned to the output amount instead of being calculated)
|
|
|
|
|
|
pub fixed_output_token_amount: Option<u64>,
|
2025-10-07 23:12:37 +08:00
|
|
|
|
/// Gas fee strategy
|
|
|
|
|
|
pub gas_fee_strategy: GasFeeStrategy,
|
2025-10-30 22:11:55 +08:00
|
|
|
|
/// Whether to simulate the transaction instead of executing it
|
|
|
|
|
|
pub simulate: bool,
|
2025-09-19 00:49:45 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Parameters for executing sell orders across different DEX protocols
|
|
|
|
|
|
///
|
|
|
|
|
|
/// Contains all necessary configuration for selling tokens, including
|
|
|
|
|
|
/// protocol-specific settings, tip preferences, account management options, and transaction preferences.
|
|
|
|
|
|
#[derive(Clone)]
|
|
|
|
|
|
pub struct TradeSellParams {
|
|
|
|
|
|
// Trading configuration
|
|
|
|
|
|
/// The DEX protocol to use for the trade
|
|
|
|
|
|
pub dex_type: DexType,
|
2025-09-22 23:18:53 +08:00
|
|
|
|
/// Type of the token to sell
|
|
|
|
|
|
pub output_token_type: TradeTokenType,
|
2025-09-19 00:49:45 +08:00
|
|
|
|
/// Public key of the token to sell
|
|
|
|
|
|
pub mint: Pubkey,
|
|
|
|
|
|
/// Amount of tokens to sell (in smallest token units)
|
2025-09-22 23:18:53 +08:00
|
|
|
|
pub input_token_amount: u64,
|
2025-09-19 00:49:45 +08:00
|
|
|
|
/// Optional slippage tolerance in basis points (e.g., 100 = 1%)
|
|
|
|
|
|
pub slippage_basis_points: Option<u64>,
|
|
|
|
|
|
/// Recent blockhash for transaction validity
|
2025-09-20 14:00:45 +08:00
|
|
|
|
pub recent_blockhash: Option<Hash>,
|
2025-09-19 00:49:45 +08:00
|
|
|
|
/// Whether to include tip for transaction priority
|
|
|
|
|
|
pub with_tip: bool,
|
|
|
|
|
|
/// Protocol-specific parameters (PumpFun, Raydium, etc.)
|
2025-12-08 01:35:40 +08:00
|
|
|
|
pub extension_params: DexParamEnum,
|
2025-09-19 00:49:45 +08:00
|
|
|
|
// Extended configuration
|
|
|
|
|
|
/// Optional address lookup table for transaction size optimization
|
2025-10-07 20:56:02 +08:00
|
|
|
|
pub address_lookup_table_account: Option<AddressLookupTableAccount>,
|
2025-09-19 00:49:45 +08:00
|
|
|
|
/// Whether to wait for transaction confirmation before returning
|
|
|
|
|
|
pub wait_transaction_confirmed: bool,
|
2025-09-22 23:18:53 +08:00
|
|
|
|
/// Whether to create output token associated token account
|
|
|
|
|
|
pub create_output_token_ata: bool,
|
|
|
|
|
|
/// Whether to close output token associated token account after trade
|
|
|
|
|
|
pub close_output_token_ata: bool,
|
2025-10-26 23:55:28 +08:00
|
|
|
|
/// Whether to close mint token associated token account after trade
|
|
|
|
|
|
pub close_mint_token_ata: bool,
|
2025-09-20 14:00:45 +08:00
|
|
|
|
/// Durable nonce information
|
|
|
|
|
|
pub durable_nonce: Option<DurableNonceInfo>,
|
2025-10-05 22:27:04 +08:00
|
|
|
|
/// Optional fixed output token amount (If this value is set, it will be directly assigned to the output amount instead of being calculated)
|
|
|
|
|
|
pub fixed_output_token_amount: Option<u64>,
|
2025-10-07 23:12:37 +08:00
|
|
|
|
/// Gas fee strategy
|
|
|
|
|
|
pub gas_fee_strategy: GasFeeStrategy,
|
2025-10-30 22:11:55 +08:00
|
|
|
|
/// Whether to simulate the transaction instead of executing it
|
|
|
|
|
|
pub simulate: bool,
|
2025-09-19 00:49:45 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-08 01:35:40 +08:00
|
|
|
|
impl TradingClient {
|
|
|
|
|
|
/// Creates a new SolTradingSDK instance with the specified configuration
|
2025-09-19 00:49:45 +08:00
|
|
|
|
///
|
|
|
|
|
|
/// This function initializes the trading system with RPC connection, SWQOS settings,
|
|
|
|
|
|
/// and sets up necessary components for trading operations.
|
|
|
|
|
|
///
|
|
|
|
|
|
/// # Arguments
|
|
|
|
|
|
/// * `payer` - The keypair used for signing transactions
|
|
|
|
|
|
/// * `rpc_url` - Solana RPC endpoint URL
|
|
|
|
|
|
/// * `commitment` - Transaction commitment level for RPC calls
|
|
|
|
|
|
/// * `swqos_settings` - List of SWQOS (Solana Web Quality of Service) configurations
|
|
|
|
|
|
///
|
|
|
|
|
|
/// # Returns
|
2025-12-08 01:35:40 +08:00
|
|
|
|
/// Returns a configured `SolTradingSDK` instance ready for trading operations
|
2025-02-15 18:53:17 +08:00
|
|
|
|
#[inline]
|
2025-09-17 11:14:15 +08:00
|
|
|
|
pub async fn new(payer: Arc<Keypair>, trade_config: TradeConfig) -> Self {
|
2025-09-08 17:12:29 +08:00
|
|
|
|
crate::common::fast_fn::fast_init(&payer.try_pubkey().unwrap());
|
|
|
|
|
|
|
2025-04-02 13:39:59 +08:00
|
|
|
|
if CryptoProvider::get_default().is_none() {
|
|
|
|
|
|
let _ = default_provider()
|
|
|
|
|
|
.install_default()
|
|
|
|
|
|
.map_err(|e| anyhow::anyhow!("Failed to install crypto provider: {:?}", e));
|
|
|
|
|
|
}
|
2024-12-31 16:51:58 +08:00
|
|
|
|
|
2025-09-17 11:14:15 +08:00
|
|
|
|
let rpc_url = trade_config.rpc_url.clone();
|
|
|
|
|
|
let swqos_configs = trade_config.swqos_configs.clone();
|
|
|
|
|
|
let commitment = trade_config.commitment.clone();
|
|
|
|
|
|
let mut swqos_clients: Vec<Arc<SwqosClient>> = vec![];
|
2025-04-02 13:39:59 +08:00
|
|
|
|
|
2025-09-17 11:14:15 +08:00
|
|
|
|
for swqos in swqos_configs {
|
|
|
|
|
|
let swqos_client =
|
|
|
|
|
|
SwqosConfig::get_swqos_client(rpc_url.clone(), commitment.clone(), swqos.clone());
|
|
|
|
|
|
swqos_clients.push(swqos_client);
|
2025-05-29 18:53:58 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-19 00:49:45 +08:00
|
|
|
|
let rpc =
|
|
|
|
|
|
Arc::new(SolanaRpcClient::new_with_commitment(rpc_url.clone(), commitment.clone()));
|
2025-09-09 17:02:24 +08:00
|
|
|
|
common::seed::update_rents(&rpc).await.unwrap();
|
|
|
|
|
|
common::seed::start_rent_updater(rpc.clone());
|
2025-07-07 03:28:02 +08:00
|
|
|
|
|
2025-11-07 16:57:28 +08:00
|
|
|
|
// 🔧 初始化WSOL ATA:如果配置为启动时创建,则检查并创建
|
|
|
|
|
|
if trade_config.create_wsol_ata_on_startup {
|
|
|
|
|
|
// 根据seed配置计算WSOL ATA地址
|
|
|
|
|
|
let wsol_ata =
|
|
|
|
|
|
crate::common::fast_fn::get_associated_token_address_with_program_id_fast(
|
|
|
|
|
|
&payer.pubkey(),
|
|
|
|
|
|
&WSOL_TOKEN_ACCOUNT,
|
|
|
|
|
|
&crate::constants::TOKEN_PROGRAM,
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// 查询账户是否存在
|
|
|
|
|
|
match rpc.get_account(&wsol_ata).await {
|
|
|
|
|
|
Ok(_) => {
|
|
|
|
|
|
// WSOL ATA已存在
|
|
|
|
|
|
println!("✅ WSOL ATA已存在: {}", wsol_ata);
|
|
|
|
|
|
}
|
|
|
|
|
|
Err(_) => {
|
|
|
|
|
|
// WSOL ATA不存在,创建它
|
|
|
|
|
|
println!("🔨 创建WSOL ATA: {}", wsol_ata);
|
|
|
|
|
|
// 使用seed优化创建WSOL ATA
|
|
|
|
|
|
let create_ata_ixs =
|
|
|
|
|
|
crate::trading::common::wsol_manager::create_wsol_ata(&payer.pubkey());
|
|
|
|
|
|
|
|
|
|
|
|
if !create_ata_ixs.is_empty() {
|
|
|
|
|
|
// 构建并发送交易
|
|
|
|
|
|
use solana_sdk::transaction::Transaction;
|
|
|
|
|
|
let recent_blockhash = rpc.get_latest_blockhash().await.unwrap();
|
|
|
|
|
|
let tx = Transaction::new_signed_with_payer(
|
|
|
|
|
|
&create_ata_ixs,
|
|
|
|
|
|
Some(&payer.pubkey()),
|
|
|
|
|
|
&[payer.as_ref()],
|
|
|
|
|
|
recent_blockhash,
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
match rpc.send_and_confirm_transaction(&tx).await {
|
|
|
|
|
|
Ok(signature) => {
|
|
|
|
|
|
println!("✅ WSOL ATA创建成功: {}", signature);
|
|
|
|
|
|
}
|
|
|
|
|
|
Err(e) => {
|
|
|
|
|
|
// 创建失败,检查是否是因为已存在
|
|
|
|
|
|
match rpc.get_account(&wsol_ata).await {
|
|
|
|
|
|
Ok(_) => {
|
|
|
|
|
|
println!(
|
|
|
|
|
|
"✅ WSOL ATA已存在(交易失败但账户存在): {}",
|
|
|
|
|
|
wsol_ata
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
Err(_) => {
|
|
|
|
|
|
// 账户不存在且创建失败 - 这是严重错误,应该让启动失败
|
|
|
|
|
|
panic!(
|
|
|
|
|
|
"❌ WSOL ATA创建失败且账户不存在: {}. 错误: {}",
|
|
|
|
|
|
wsol_ata, e
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
println!("ℹ️ WSOL ATA已存在(无需创建)");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let instance = Self {
|
|
|
|
|
|
payer,
|
|
|
|
|
|
rpc,
|
|
|
|
|
|
swqos_clients,
|
|
|
|
|
|
middleware_manager: None,
|
|
|
|
|
|
use_seed_optimize: trade_config.use_seed_optimize,
|
|
|
|
|
|
};
|
2025-06-07 23:16:46 +08:00
|
|
|
|
|
2025-09-08 17:12:29 +08:00
|
|
|
|
let mut current = INSTANCE.lock();
|
2025-06-07 23:16:46 +08:00
|
|
|
|
*current = Some(Arc::new(instance.clone()));
|
|
|
|
|
|
|
|
|
|
|
|
instance
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-19 00:49:45 +08:00
|
|
|
|
/// Adds a middleware manager to the SolanaTrade instance
|
|
|
|
|
|
///
|
|
|
|
|
|
/// Middleware managers can be used to implement custom logic that runs before or after trading operations,
|
|
|
|
|
|
/// such as logging, monitoring, or custom validation.
|
|
|
|
|
|
///
|
|
|
|
|
|
/// # Arguments
|
|
|
|
|
|
/// * `middleware_manager` - The middleware manager to attach
|
|
|
|
|
|
///
|
|
|
|
|
|
/// # Returns
|
|
|
|
|
|
/// Returns the modified SolanaTrade instance with middleware manager attached
|
2025-08-19 18:07:21 +08:00
|
|
|
|
pub fn with_middleware_manager(mut self, middleware_manager: MiddlewareManager) -> Self {
|
|
|
|
|
|
self.middleware_manager = Some(Arc::new(middleware_manager));
|
|
|
|
|
|
self
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-19 00:49:45 +08:00
|
|
|
|
/// Gets the RPC client instance for direct Solana blockchain interactions
|
|
|
|
|
|
///
|
|
|
|
|
|
/// This provides access to the underlying Solana RPC client that can be used
|
|
|
|
|
|
/// for custom blockchain operations outside of the trading framework.
|
|
|
|
|
|
///
|
|
|
|
|
|
/// # Returns
|
|
|
|
|
|
/// Returns a reference to the Arc-wrapped SolanaRpcClient instance
|
2025-06-07 23:16:46 +08:00
|
|
|
|
pub fn get_rpc(&self) -> &Arc<SolanaRpcClient> {
|
|
|
|
|
|
&self.rpc
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-19 00:49:45 +08:00
|
|
|
|
/// Gets the current globally shared SolanaTrade instance
|
|
|
|
|
|
///
|
|
|
|
|
|
/// This provides access to the singleton instance that was created with `new()`.
|
|
|
|
|
|
/// Useful for accessing the trading instance from different parts of the application.
|
|
|
|
|
|
///
|
|
|
|
|
|
/// # Returns
|
|
|
|
|
|
/// Returns the Arc-wrapped SolanaTrade instance
|
|
|
|
|
|
///
|
|
|
|
|
|
/// # Panics
|
|
|
|
|
|
/// Panics if no instance has been initialized yet. Make sure to call `new()` first.
|
2025-06-07 23:16:46 +08:00
|
|
|
|
pub fn get_instance() -> Arc<Self> {
|
2025-09-08 17:12:29 +08:00
|
|
|
|
let instance = INSTANCE.lock();
|
2025-07-09 22:29:29 +08:00
|
|
|
|
instance
|
|
|
|
|
|
.as_ref()
|
2025-09-19 00:49:45 +08:00
|
|
|
|
.expect("SolanaTrade instance not initialized. Please call new() first.")
|
2025-07-09 22:29:29 +08:00
|
|
|
|
.clone()
|
2024-12-31 16:51:58 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-10 18:14:21 +08:00
|
|
|
|
/// Execute a buy order for a specified token
|
|
|
|
|
|
///
|
2025-12-08 01:35:40 +08:00
|
|
|
|
/// 🔧 修复:返回Vec<Signature>支持多SWQOS并发交易
|
|
|
|
|
|
/// - bool: 是否至少有一个交易成功
|
|
|
|
|
|
/// - Vec<Signature>: 所有提交的交易签名(按SWQOS顺序)
|
|
|
|
|
|
/// - Option<TradeError>: 最后一个错误(如果全部失败)
|
|
|
|
|
|
///
|
2025-07-10 18:14:21 +08:00
|
|
|
|
/// # Arguments
|
|
|
|
|
|
///
|
2025-09-19 00:49:45 +08:00
|
|
|
|
/// * `params` - Buy trade parameters containing all necessary trading configuration
|
2025-07-10 18:14:21 +08:00
|
|
|
|
///
|
|
|
|
|
|
/// # Returns
|
|
|
|
|
|
///
|
2025-12-08 01:35:40 +08:00
|
|
|
|
/// Returns `Ok((bool, Vec<Signature>, Option<TradeError>))` with success flag and all transaction signatures,
|
2025-09-19 00:49:45 +08:00
|
|
|
|
/// or an error if the transaction fails.
|
2025-07-10 18:14:21 +08:00
|
|
|
|
///
|
|
|
|
|
|
/// # Errors
|
|
|
|
|
|
///
|
|
|
|
|
|
/// This function will return an error if:
|
2025-09-19 00:49:45 +08:00
|
|
|
|
/// - Invalid protocol parameters are provided for the specified DEX type
|
2025-07-10 18:14:21 +08:00
|
|
|
|
/// - The transaction fails to execute
|
|
|
|
|
|
/// - Network or RPC errors occur
|
|
|
|
|
|
/// - Insufficient SOL balance for the purchase
|
2025-09-19 00:49:45 +08:00
|
|
|
|
/// - Required accounts cannot be created or accessed
|
2025-12-08 01:35:40 +08:00
|
|
|
|
#[inline]
|
2025-11-24 22:52:56 +08:00
|
|
|
|
pub async fn buy(
|
|
|
|
|
|
&self,
|
|
|
|
|
|
params: TradeBuyParams,
|
2025-12-08 01:35:40 +08:00
|
|
|
|
) -> Result<(bool, Vec<Signature>, Option<TradeError>), anyhow::Error> {
|
|
|
|
|
|
#[cfg(feature = "perf-trace")]
|
2025-09-19 00:49:45 +08:00
|
|
|
|
if params.slippage_basis_points.is_none() {
|
2025-12-08 01:35:40 +08:00
|
|
|
|
log::debug!(
|
2025-09-07 17:22:58 +08:00
|
|
|
|
"slippage_basis_points is none, use default slippage basis points: {}",
|
|
|
|
|
|
DEFAULT_SLIPPAGE
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
2025-09-22 23:18:53 +08:00
|
|
|
|
if params.input_token_type == TradeTokenType::USD1 && params.dex_type != DexType::Bonk {
|
|
|
|
|
|
return Err(anyhow::anyhow!(
|
|
|
|
|
|
" Current version only support USD1 trading on Bonk protocols"
|
|
|
|
|
|
));
|
|
|
|
|
|
}
|
|
|
|
|
|
let input_token_mint = if params.input_token_type == TradeTokenType::SOL {
|
|
|
|
|
|
SOL_TOKEN_ACCOUNT
|
|
|
|
|
|
} else if params.input_token_type == TradeTokenType::WSOL {
|
|
|
|
|
|
WSOL_TOKEN_ACCOUNT
|
2025-10-12 12:01:21 +08:00
|
|
|
|
} else if params.input_token_type == TradeTokenType::USDC {
|
|
|
|
|
|
USDC_TOKEN_ACCOUNT
|
2025-09-22 23:18:53 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
USD1_TOKEN_ACCOUNT
|
|
|
|
|
|
};
|
2025-09-19 00:49:45 +08:00
|
|
|
|
let executor = TradeFactory::create_executor(params.dex_type.clone());
|
|
|
|
|
|
let protocol_params = params.extension_params;
|
2025-09-22 00:05:20 +08:00
|
|
|
|
let buy_params = SwapParams {
|
2025-07-10 18:14:21 +08:00
|
|
|
|
rpc: Some(self.rpc.clone()),
|
|
|
|
|
|
payer: self.payer.clone(),
|
2025-09-22 01:25:32 +08:00
|
|
|
|
trade_type: TradeType::Buy,
|
2025-09-22 23:18:53 +08:00
|
|
|
|
input_mint: input_token_mint,
|
2025-09-22 00:05:20 +08:00
|
|
|
|
output_mint: params.mint,
|
|
|
|
|
|
input_token_program: None,
|
|
|
|
|
|
output_token_program: None,
|
2025-09-22 23:18:53 +08:00
|
|
|
|
input_amount: Some(params.input_token_amount),
|
2025-09-19 00:49:45 +08:00
|
|
|
|
slippage_basis_points: params.slippage_basis_points,
|
2025-10-07 20:56:02 +08:00
|
|
|
|
address_lookup_table_account: params.address_lookup_table_account,
|
2025-09-19 00:49:45 +08:00
|
|
|
|
recent_blockhash: params.recent_blockhash,
|
2025-11-24 22:52:56 +08:00
|
|
|
|
data_size_limit: params
|
|
|
|
|
|
.gas_fee_strategy
|
|
|
|
|
|
.get_strategies(TradeType::Buy)
|
2025-11-09 21:20:55 +08:00
|
|
|
|
.get(0)
|
|
|
|
|
|
.map(|(_, _, v)| v.data_size_limit)
|
|
|
|
|
|
.unwrap_or(256 * 1024),
|
2025-09-19 00:49:45 +08:00
|
|
|
|
wait_transaction_confirmed: params.wait_transaction_confirmed,
|
2025-07-10 18:14:21 +08:00
|
|
|
|
protocol_params: protocol_params.clone(),
|
2025-11-07 16:57:28 +08:00
|
|
|
|
open_seed_optimize: self.use_seed_optimize, // 使用全局seed优化配置
|
2025-09-17 11:14:15 +08:00
|
|
|
|
swqos_clients: self.swqos_clients.clone(),
|
2025-09-09 17:02:24 +08:00
|
|
|
|
middleware_manager: self.middleware_manager.clone(),
|
2025-09-20 14:00:45 +08:00
|
|
|
|
durable_nonce: params.durable_nonce,
|
2025-09-22 00:05:20 +08:00
|
|
|
|
with_tip: true,
|
2025-09-22 23:18:53 +08:00
|
|
|
|
create_input_mint_ata: params.create_input_token_ata,
|
|
|
|
|
|
close_input_mint_ata: params.close_input_token_ata,
|
2025-09-22 00:05:20 +08:00
|
|
|
|
create_output_mint_ata: params.create_mint_ata,
|
|
|
|
|
|
close_output_mint_ata: false,
|
2025-10-05 22:27:04 +08:00
|
|
|
|
fixed_output_amount: params.fixed_output_token_amount,
|
2025-10-07 23:12:37 +08:00
|
|
|
|
gas_fee_strategy: params.gas_fee_strategy,
|
2025-10-30 22:11:55 +08:00
|
|
|
|
simulate: params.simulate,
|
2025-07-10 18:14:21 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Validate protocol params
|
2025-09-19 00:49:45 +08:00
|
|
|
|
let is_valid_params = match params.dex_type {
|
2025-08-19 18:07:21 +08:00
|
|
|
|
DexType::PumpFun => protocol_params.as_any().downcast_ref::<PumpFunParams>().is_some(),
|
|
|
|
|
|
DexType::PumpSwap => {
|
|
|
|
|
|
protocol_params.as_any().downcast_ref::<PumpSwapParams>().is_some()
|
|
|
|
|
|
}
|
|
|
|
|
|
DexType::Bonk => protocol_params.as_any().downcast_ref::<BonkParams>().is_some(),
|
|
|
|
|
|
DexType::RaydiumCpmm => {
|
|
|
|
|
|
protocol_params.as_any().downcast_ref::<RaydiumCpmmParams>().is_some()
|
|
|
|
|
|
}
|
|
|
|
|
|
DexType::RaydiumAmmV4 => {
|
|
|
|
|
|
protocol_params.as_any().downcast_ref::<RaydiumAmmV4Params>().is_some()
|
|
|
|
|
|
}
|
2025-10-05 22:27:04 +08:00
|
|
|
|
DexType::MeteoraDammV2 => {
|
|
|
|
|
|
protocol_params.as_any().downcast_ref::<MeteoraDammV2Params>().is_some()
|
|
|
|
|
|
}
|
2025-07-10 18:14:21 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
if !is_valid_params {
|
2025-07-09 22:29:29 +08:00
|
|
|
|
return Err(anyhow::anyhow!("Invalid protocol params for Trade"));
|
2025-06-24 20:56:15 +08:00
|
|
|
|
}
|
2025-07-10 18:14:21 +08:00
|
|
|
|
|
2025-11-24 22:52:56 +08:00
|
|
|
|
let swap_result = executor.swap(buy_params).await;
|
|
|
|
|
|
let result =
|
2025-12-08 01:35:40 +08:00
|
|
|
|
swap_result.map(|(success, sigs, err)| (success, sigs, err.map(TradeError::from)));
|
2025-11-24 22:52:56 +08:00
|
|
|
|
return result;
|
2025-06-24 20:56:15 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-10 18:14:21 +08:00
|
|
|
|
/// Execute a sell order for a specified token
|
|
|
|
|
|
///
|
2025-12-08 01:35:40 +08:00
|
|
|
|
/// 🔧 修复:返回Vec<Signature>支持多SWQOS并发交易
|
|
|
|
|
|
/// - bool: 是否至少有一个交易成功
|
|
|
|
|
|
/// - Vec<Signature>: 所有提交的交易签名(按SWQOS顺序)
|
|
|
|
|
|
/// - Option<TradeError>: 最后一个错误(如果全部失败)
|
|
|
|
|
|
///
|
2025-07-10 18:14:21 +08:00
|
|
|
|
/// # Arguments
|
|
|
|
|
|
///
|
2025-09-19 00:49:45 +08:00
|
|
|
|
/// * `params` - Sell trade parameters containing all necessary trading configuration
|
2025-07-10 18:14:21 +08:00
|
|
|
|
///
|
|
|
|
|
|
/// # Returns
|
|
|
|
|
|
///
|
2025-12-08 01:35:40 +08:00
|
|
|
|
/// Returns `Ok((bool, Vec<Signature>, Option<TradeError>))` with success flag and all transaction signatures,
|
2025-09-19 00:49:45 +08:00
|
|
|
|
/// or an error if the transaction fails.
|
2025-07-10 18:14:21 +08:00
|
|
|
|
///
|
|
|
|
|
|
/// # Errors
|
|
|
|
|
|
///
|
|
|
|
|
|
/// This function will return an error if:
|
2025-09-19 00:49:45 +08:00
|
|
|
|
/// - Invalid protocol parameters are provided for the specified DEX type
|
2025-07-10 18:14:21 +08:00
|
|
|
|
/// - The transaction fails to execute
|
|
|
|
|
|
/// - Network or RPC errors occur
|
|
|
|
|
|
/// - Insufficient token balance for the sale
|
|
|
|
|
|
/// - Token account doesn't exist or is not properly initialized
|
2025-09-19 00:49:45 +08:00
|
|
|
|
/// - Required accounts cannot be created or accessed
|
2025-12-08 01:35:40 +08:00
|
|
|
|
#[inline]
|
2025-11-24 22:52:56 +08:00
|
|
|
|
pub async fn sell(
|
|
|
|
|
|
&self,
|
|
|
|
|
|
params: TradeSellParams,
|
2025-12-08 01:35:40 +08:00
|
|
|
|
) -> Result<(bool, Vec<Signature>, Option<TradeError>), anyhow::Error> {
|
|
|
|
|
|
#[cfg(feature = "perf-trace")]
|
2025-09-19 00:49:45 +08:00
|
|
|
|
if params.slippage_basis_points.is_none() {
|
2025-12-08 01:35:40 +08:00
|
|
|
|
log::debug!(
|
2025-09-07 17:22:58 +08:00
|
|
|
|
"slippage_basis_points is none, use default slippage basis points: {}",
|
|
|
|
|
|
DEFAULT_SLIPPAGE
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
2025-09-22 23:18:53 +08:00
|
|
|
|
if params.output_token_type == TradeTokenType::USD1 && params.dex_type != DexType::Bonk {
|
|
|
|
|
|
return Err(anyhow::anyhow!(
|
|
|
|
|
|
" Current version only support USD1 trading on Bonk protocols"
|
|
|
|
|
|
));
|
|
|
|
|
|
}
|
2025-09-19 00:49:45 +08:00
|
|
|
|
let executor = TradeFactory::create_executor(params.dex_type.clone());
|
|
|
|
|
|
let protocol_params = params.extension_params;
|
2025-09-22 23:18:53 +08:00
|
|
|
|
let output_token_mint = if params.output_token_type == TradeTokenType::SOL {
|
2025-09-22 00:05:20 +08:00
|
|
|
|
SOL_TOKEN_ACCOUNT
|
2025-09-22 23:18:53 +08:00
|
|
|
|
} else if params.output_token_type == TradeTokenType::WSOL {
|
2025-09-22 00:05:20 +08:00
|
|
|
|
WSOL_TOKEN_ACCOUNT
|
2025-10-12 12:01:21 +08:00
|
|
|
|
} else if params.output_token_type == TradeTokenType::USDC {
|
|
|
|
|
|
USDC_TOKEN_ACCOUNT
|
2025-09-22 23:18:53 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
USD1_TOKEN_ACCOUNT
|
2025-09-22 00:05:20 +08:00
|
|
|
|
};
|
|
|
|
|
|
let sell_params = SwapParams {
|
2025-07-10 18:14:21 +08:00
|
|
|
|
rpc: Some(self.rpc.clone()),
|
|
|
|
|
|
payer: self.payer.clone(),
|
2025-09-22 01:25:32 +08:00
|
|
|
|
trade_type: TradeType::Sell,
|
2025-09-22 00:05:20 +08:00
|
|
|
|
input_mint: params.mint,
|
2025-09-22 23:18:53 +08:00
|
|
|
|
output_mint: output_token_mint,
|
2025-09-22 00:05:20 +08:00
|
|
|
|
input_token_program: None,
|
|
|
|
|
|
output_token_program: None,
|
2025-09-22 23:18:53 +08:00
|
|
|
|
input_amount: Some(params.input_token_amount),
|
2025-09-19 00:49:45 +08:00
|
|
|
|
slippage_basis_points: params.slippage_basis_points,
|
2025-10-07 20:56:02 +08:00
|
|
|
|
address_lookup_table_account: params.address_lookup_table_account,
|
2025-09-19 00:49:45 +08:00
|
|
|
|
recent_blockhash: params.recent_blockhash,
|
|
|
|
|
|
wait_transaction_confirmed: params.wait_transaction_confirmed,
|
2025-07-10 18:14:21 +08:00
|
|
|
|
protocol_params: protocol_params.clone(),
|
2025-09-19 00:49:45 +08:00
|
|
|
|
with_tip: params.with_tip,
|
2025-11-07 16:57:28 +08:00
|
|
|
|
open_seed_optimize: self.use_seed_optimize, // 使用全局seed优化配置
|
2025-09-19 00:49:45 +08:00
|
|
|
|
swqos_clients: self.swqos_clients.clone(),
|
2025-09-09 17:02:24 +08:00
|
|
|
|
middleware_manager: self.middleware_manager.clone(),
|
2025-09-20 14:00:45 +08:00
|
|
|
|
durable_nonce: params.durable_nonce,
|
2025-11-24 22:52:56 +08:00
|
|
|
|
data_size_limit: params
|
|
|
|
|
|
.gas_fee_strategy
|
|
|
|
|
|
.get_strategies(TradeType::Sell)
|
2025-11-09 21:20:55 +08:00
|
|
|
|
.get(0)
|
|
|
|
|
|
.map(|(_, _, v)| v.data_size_limit)
|
|
|
|
|
|
.unwrap_or(0),
|
2025-09-22 00:05:20 +08:00
|
|
|
|
create_input_mint_ata: false,
|
2025-10-26 23:55:28 +08:00
|
|
|
|
close_input_mint_ata: params.close_mint_token_ata,
|
2025-09-22 23:18:53 +08:00
|
|
|
|
create_output_mint_ata: params.create_output_token_ata,
|
|
|
|
|
|
close_output_mint_ata: params.close_output_token_ata,
|
2025-10-05 22:27:04 +08:00
|
|
|
|
fixed_output_amount: params.fixed_output_token_amount,
|
2025-10-07 23:12:37 +08:00
|
|
|
|
gas_fee_strategy: params.gas_fee_strategy,
|
2025-10-30 22:11:55 +08:00
|
|
|
|
simulate: params.simulate,
|
2025-07-10 18:14:21 +08:00
|
|
|
|
};
|
2025-09-12 04:29:47 +08:00
|
|
|
|
|
2025-07-10 18:14:21 +08:00
|
|
|
|
// Validate protocol params
|
2025-09-19 00:49:45 +08:00
|
|
|
|
let is_valid_params = match params.dex_type {
|
2025-08-19 18:07:21 +08:00
|
|
|
|
DexType::PumpFun => protocol_params.as_any().downcast_ref::<PumpFunParams>().is_some(),
|
|
|
|
|
|
DexType::PumpSwap => {
|
|
|
|
|
|
protocol_params.as_any().downcast_ref::<PumpSwapParams>().is_some()
|
|
|
|
|
|
}
|
|
|
|
|
|
DexType::Bonk => protocol_params.as_any().downcast_ref::<BonkParams>().is_some(),
|
|
|
|
|
|
DexType::RaydiumCpmm => {
|
|
|
|
|
|
protocol_params.as_any().downcast_ref::<RaydiumCpmmParams>().is_some()
|
|
|
|
|
|
}
|
|
|
|
|
|
DexType::RaydiumAmmV4 => {
|
|
|
|
|
|
protocol_params.as_any().downcast_ref::<RaydiumAmmV4Params>().is_some()
|
|
|
|
|
|
}
|
2025-10-05 22:27:04 +08:00
|
|
|
|
DexType::MeteoraDammV2 => {
|
|
|
|
|
|
protocol_params.as_any().downcast_ref::<MeteoraDammV2Params>().is_some()
|
|
|
|
|
|
}
|
2025-07-10 18:14:21 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
if !is_valid_params {
|
2025-07-09 22:29:29 +08:00
|
|
|
|
return Err(anyhow::anyhow!("Invalid protocol params for Trade"));
|
2025-06-10 19:39:06 +08:00
|
|
|
|
}
|
2025-07-10 18:14:21 +08:00
|
|
|
|
|
|
|
|
|
|
// Execute sell based on tip preference
|
2025-11-24 22:52:56 +08:00
|
|
|
|
let swap_result = executor.swap(sell_params).await;
|
|
|
|
|
|
let result =
|
2025-12-08 01:35:40 +08:00
|
|
|
|
swap_result.map(|(success, sigs, err)| (success, sigs, err.map(TradeError::from)));
|
2025-11-24 22:52:56 +08:00
|
|
|
|
return result;
|
2025-01-03 14:43:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-10 18:14:21 +08:00
|
|
|
|
/// Execute a sell order for a percentage of the specified token amount
|
|
|
|
|
|
///
|
|
|
|
|
|
/// This is a convenience function that calculates the exact amount to sell based on
|
|
|
|
|
|
/// a percentage of the total token amount and then calls the `sell` function.
|
|
|
|
|
|
///
|
|
|
|
|
|
/// # Arguments
|
|
|
|
|
|
///
|
2025-09-19 00:49:45 +08:00
|
|
|
|
/// * `params` - Sell trade parameters (will be modified with calculated token amount)
|
2025-07-10 18:14:21 +08:00
|
|
|
|
/// * `amount_token` - Total amount of tokens available (in smallest token units)
|
|
|
|
|
|
/// * `percent` - Percentage of tokens to sell (1-100, where 100 = 100%)
|
|
|
|
|
|
///
|
|
|
|
|
|
/// # Returns
|
|
|
|
|
|
///
|
2025-09-19 00:49:45 +08:00
|
|
|
|
/// Returns `Ok(Signature)` with the transaction signature if the sell order is successfully executed,
|
|
|
|
|
|
/// or an error if the transaction fails.
|
2025-07-10 18:14:21 +08:00
|
|
|
|
///
|
|
|
|
|
|
/// # Errors
|
|
|
|
|
|
///
|
|
|
|
|
|
/// This function will return an error if:
|
|
|
|
|
|
/// - `percent` is 0 or greater than 100
|
2025-09-19 00:49:45 +08:00
|
|
|
|
/// - Invalid protocol parameters are provided for the specified DEX type
|
2025-07-10 18:14:21 +08:00
|
|
|
|
/// - The transaction fails to execute
|
|
|
|
|
|
/// - Network or RPC errors occur
|
|
|
|
|
|
/// - Insufficient token balance for the calculated sale amount
|
|
|
|
|
|
/// - Token account doesn't exist or is not properly initialized
|
2025-09-19 00:49:45 +08:00
|
|
|
|
/// - Required accounts cannot be created or accessed
|
2025-07-10 18:14:21 +08:00
|
|
|
|
pub async fn sell_by_percent(
|
2025-06-22 23:05:54 +08:00
|
|
|
|
&self,
|
2025-09-19 00:49:45 +08:00
|
|
|
|
mut params: TradeSellParams,
|
2025-07-10 18:14:21 +08:00
|
|
|
|
amount_token: u64,
|
2025-06-22 23:05:54 +08:00
|
|
|
|
percent: u64,
|
2025-12-08 01:35:40 +08:00
|
|
|
|
) -> Result<(bool, Vec<Signature>, Option<TradeError>), anyhow::Error> {
|
2025-07-10 18:14:21 +08:00
|
|
|
|
if percent == 0 || percent > 100 {
|
|
|
|
|
|
return Err(anyhow::anyhow!("Percentage must be between 1 and 100"));
|
2025-06-22 23:05:54 +08:00
|
|
|
|
}
|
2025-07-10 18:14:21 +08:00
|
|
|
|
let amount = amount_token * percent / 100;
|
2025-09-22 23:18:53 +08:00
|
|
|
|
params.input_token_amount = amount;
|
2025-09-19 00:49:45 +08:00
|
|
|
|
self.sell(params).await
|
2025-06-15 20:50:51 +08:00
|
|
|
|
}
|
2025-09-09 17:02:24 +08:00
|
|
|
|
|
2025-09-19 00:49:45 +08:00
|
|
|
|
/// Wraps native SOL into wSOL (Wrapped SOL) for use in SPL token operations
|
2025-09-09 17:02:24 +08:00
|
|
|
|
///
|
|
|
|
|
|
/// This function creates a wSOL associated token account (if it doesn't exist),
|
|
|
|
|
|
/// transfers the specified amount of SOL to that account, and then syncs the native
|
2025-09-19 00:49:45 +08:00
|
|
|
|
/// token balance to make SOL usable as an SPL token in trading operations.
|
2025-09-09 17:02:24 +08:00
|
|
|
|
///
|
|
|
|
|
|
/// # Arguments
|
2025-09-19 00:49:45 +08:00
|
|
|
|
/// * `amount` - The amount of SOL to wrap (in lamports)
|
2025-09-09 17:02:24 +08:00
|
|
|
|
///
|
|
|
|
|
|
/// # Returns
|
2025-09-19 00:49:45 +08:00
|
|
|
|
/// * `Ok(String)` - Transaction signature if successful
|
|
|
|
|
|
/// * `Err(anyhow::Error)` - If the transaction fails to execute
|
|
|
|
|
|
///
|
|
|
|
|
|
/// # Errors
|
|
|
|
|
|
///
|
|
|
|
|
|
/// This function will return an error if:
|
|
|
|
|
|
/// - Insufficient SOL balance for the wrap operation
|
|
|
|
|
|
/// - wSOL associated token account creation fails
|
|
|
|
|
|
/// - Transaction fails to execute or confirm
|
|
|
|
|
|
/// - Network or RPC errors occur
|
2025-09-09 17:02:24 +08:00
|
|
|
|
pub async fn wrap_sol_to_wsol(&self, amount: u64) -> Result<String, anyhow::Error> {
|
|
|
|
|
|
use crate::trading::common::wsol_manager::handle_wsol;
|
|
|
|
|
|
use solana_sdk::transaction::Transaction;
|
|
|
|
|
|
let recent_blockhash = self.rpc.get_latest_blockhash().await?;
|
|
|
|
|
|
let instructions = handle_wsol(&self.payer.pubkey(), amount);
|
|
|
|
|
|
let mut transaction =
|
|
|
|
|
|
Transaction::new_with_payer(&instructions, Some(&self.payer.pubkey()));
|
|
|
|
|
|
transaction.sign(&[&*self.payer], recent_blockhash);
|
|
|
|
|
|
let signature = self.rpc.send_and_confirm_transaction(&transaction).await?;
|
|
|
|
|
|
Ok(signature.to_string())
|
|
|
|
|
|
}
|
2025-09-19 00:49:45 +08:00
|
|
|
|
/// Closes the wSOL associated token account and unwraps remaining balance to native SOL
|
2025-09-09 17:02:24 +08:00
|
|
|
|
///
|
|
|
|
|
|
/// This function closes the wSOL associated token account, which automatically
|
|
|
|
|
|
/// transfers any remaining wSOL balance back to the account owner as native SOL.
|
2025-09-19 00:49:45 +08:00
|
|
|
|
/// This is useful for cleaning up wSOL accounts and recovering wrapped SOL after trading operations.
|
2025-09-09 17:02:24 +08:00
|
|
|
|
///
|
|
|
|
|
|
/// # Returns
|
2025-09-19 00:49:45 +08:00
|
|
|
|
/// * `Ok(String)` - Transaction signature if successful
|
|
|
|
|
|
/// * `Err(anyhow::Error)` - If the transaction fails to execute
|
|
|
|
|
|
///
|
|
|
|
|
|
/// # Errors
|
|
|
|
|
|
///
|
|
|
|
|
|
/// This function will return an error if:
|
|
|
|
|
|
/// - wSOL associated token account doesn't exist
|
|
|
|
|
|
/// - Account closure fails due to insufficient permissions
|
|
|
|
|
|
/// - Transaction fails to execute or confirm
|
|
|
|
|
|
/// - Network or RPC errors occur
|
2025-09-09 17:02:24 +08:00
|
|
|
|
pub async fn close_wsol(&self) -> Result<String, anyhow::Error> {
|
|
|
|
|
|
use crate::trading::common::wsol_manager::close_wsol;
|
|
|
|
|
|
use solana_sdk::transaction::Transaction;
|
|
|
|
|
|
let recent_blockhash = self.rpc.get_latest_blockhash().await?;
|
|
|
|
|
|
let instructions = close_wsol(&self.payer.pubkey());
|
|
|
|
|
|
let mut transaction =
|
|
|
|
|
|
Transaction::new_with_payer(&instructions, Some(&self.payer.pubkey()));
|
|
|
|
|
|
transaction.sign(&[&*self.payer], recent_blockhash);
|
|
|
|
|
|
let signature = self.rpc.send_and_confirm_transaction(&transaction).await?;
|
|
|
|
|
|
Ok(signature.to_string())
|
|
|
|
|
|
}
|
2025-11-07 17:04:36 +08:00
|
|
|
|
|
|
|
|
|
|
/// Creates a wSOL associated token account (ATA) without wrapping any SOL
|
|
|
|
|
|
///
|
|
|
|
|
|
/// This function only creates the wSOL associated token account for the payer
|
|
|
|
|
|
/// without transferring any SOL into it. This is useful when you want to set up
|
|
|
|
|
|
/// the account infrastructure in advance without committing funds yet.
|
|
|
|
|
|
///
|
|
|
|
|
|
/// # Returns
|
|
|
|
|
|
/// * `Ok(String)` - Transaction signature if successful
|
|
|
|
|
|
/// * `Err(anyhow::Error)` - If the transaction fails to execute
|
|
|
|
|
|
///
|
|
|
|
|
|
/// # Errors
|
|
|
|
|
|
///
|
|
|
|
|
|
/// This function will return an error if:
|
|
|
|
|
|
/// - wSOL ATA account already exists (idempotent, will succeed silently)
|
|
|
|
|
|
/// - Transaction fails to execute or confirm
|
|
|
|
|
|
/// - Network or RPC errors occur
|
|
|
|
|
|
/// - Insufficient SOL for transaction fees
|
|
|
|
|
|
pub async fn create_wsol_ata(&self) -> Result<String, anyhow::Error> {
|
|
|
|
|
|
use crate::trading::common::wsol_manager::create_wsol_ata;
|
|
|
|
|
|
use solana_sdk::transaction::Transaction;
|
|
|
|
|
|
|
|
|
|
|
|
let recent_blockhash = self.rpc.get_latest_blockhash().await?;
|
|
|
|
|
|
let instructions = create_wsol_ata(&self.payer.pubkey());
|
|
|
|
|
|
|
|
|
|
|
|
// If instructions are empty, ATA already exists
|
|
|
|
|
|
if instructions.is_empty() {
|
2025-11-24 22:52:56 +08:00
|
|
|
|
return Err(anyhow::anyhow!("wSOL ATA already exists or no instructions needed"));
|
2025-11-07 17:04:36 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let mut transaction =
|
|
|
|
|
|
Transaction::new_with_payer(&instructions, Some(&self.payer.pubkey()));
|
|
|
|
|
|
transaction.sign(&[&*self.payer], recent_blockhash);
|
|
|
|
|
|
let signature = self.rpc.send_and_confirm_transaction(&transaction).await?;
|
|
|
|
|
|
Ok(signature.to_string())
|
|
|
|
|
|
}
|
2025-11-20 11:21:27 +08:00
|
|
|
|
|
|
|
|
|
|
/// 将 WSOL 转换为 SOL,使用 seed 账户
|
|
|
|
|
|
///
|
|
|
|
|
|
/// 这个函数实现以下步骤:
|
|
|
|
|
|
/// 1. 使用 super::seed::create_associated_token_account_use_seed 创建 WSOL seed 账号
|
|
|
|
|
|
/// 2. 使用 get_associated_token_address_with_program_id_use_seed 获取该账号的 ATA 地址
|
|
|
|
|
|
/// 3. 添加从用户 WSOL ATA 转账到该 seed ATA 账号的指令
|
|
|
|
|
|
/// 4. 添加关闭 WSOL seed 账号的指令
|
|
|
|
|
|
///
|
|
|
|
|
|
/// # Arguments
|
|
|
|
|
|
/// * `amount` - 要转换的 WSOL 数量(以 lamports 为单位)
|
|
|
|
|
|
///
|
|
|
|
|
|
/// # Returns
|
|
|
|
|
|
/// * `Ok(String)` - 交易签名
|
|
|
|
|
|
/// * `Err(anyhow::Error)` - 如果交易执行失败
|
|
|
|
|
|
///
|
|
|
|
|
|
/// # Errors
|
|
|
|
|
|
///
|
|
|
|
|
|
/// 此函数在以下情况下会返回错误:
|
|
|
|
|
|
/// - 用户 WSOL ATA 中余额不足
|
|
|
|
|
|
/// - seed 账户创建失败
|
|
|
|
|
|
/// - 转账指令执行失败
|
|
|
|
|
|
/// - 交易执行或确认失败
|
|
|
|
|
|
/// - 网络或 RPC 错误
|
|
|
|
|
|
pub async fn wrap_wsol_to_sol(&self, amount: u64) -> Result<String, anyhow::Error> {
|
2025-11-23 20:18:01 +08:00
|
|
|
|
use crate::trading::common::wsol_manager::{wrap_wsol_to_sol as wrap_wsol_to_sol_internal, wrap_wsol_to_sol_without_create};
|
|
|
|
|
|
use crate::common::seed::get_associated_token_address_with_program_id_use_seed;
|
2025-11-20 11:21:27 +08:00
|
|
|
|
use solana_sdk::transaction::Transaction;
|
|
|
|
|
|
|
2025-11-23 20:18:01 +08:00
|
|
|
|
// 检查临时seed账户是否已存在
|
|
|
|
|
|
let seed_ata_address = get_associated_token_address_with_program_id_use_seed(
|
|
|
|
|
|
&self.payer.pubkey(),
|
|
|
|
|
|
&crate::constants::WSOL_TOKEN_ACCOUNT,
|
|
|
|
|
|
&crate::constants::TOKEN_PROGRAM,
|
|
|
|
|
|
)?;
|
2025-11-20 11:21:27 +08:00
|
|
|
|
|
2025-11-23 20:18:01 +08:00
|
|
|
|
let account_exists = self.rpc.get_account(&seed_ata_address).await.is_ok();
|
|
|
|
|
|
|
|
|
|
|
|
let instructions = if account_exists {
|
|
|
|
|
|
// 如果账户已存在,使用不创建账户的版本
|
|
|
|
|
|
wrap_wsol_to_sol_without_create(&self.payer.pubkey(), amount)?
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 如果账户不存在,使用创建账户的版本
|
|
|
|
|
|
wrap_wsol_to_sol_internal(&self.payer.pubkey(), amount)?
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
let recent_blockhash = self.rpc.get_latest_blockhash().await?;
|
2025-11-20 11:21:27 +08:00
|
|
|
|
let mut transaction = Transaction::new_with_payer(&instructions, Some(&self.payer.pubkey()));
|
|
|
|
|
|
transaction.sign(&[&*self.payer], recent_blockhash);
|
|
|
|
|
|
let signature = self.rpc.send_and_confirm_transaction(&transaction).await?;
|
|
|
|
|
|
Ok(signature.to_string())
|
|
|
|
|
|
}
|
2024-12-31 16:51:58 +08:00
|
|
|
|
}
|