feat: add shared infrastructure support for multi-wallet trading

Introduce TradingInfrastructure to allow multiple wallets to share the same
RPC client and SWQOS clients, reducing initialization overhead and memory usage.

Changes:
- Add InfrastructureConfig struct for wallet-independent configuration
- Add TradingInfrastructure struct to hold shared RPC/SWQOS clients
- Refactor TradingClient to use Arc<TradingInfrastructure>
- Add from_infrastructure() for fast client creation from shared infrastructure
- Add from_infrastructure_with_wsol_setup() for async WSOL ATA setup
- Maintain backwards compatibility with existing TradingClient::new() API

Benefits:
- First wallet: Full initialization (~200-500ms)
- Subsequent wallets (same config): Fast creation (~1-2ms)
- Memory: Single set of RPC/SWQOS clients shared across wallets
This commit is contained in:
vibes
2026-01-05 19:40:53 +00:00
parent 285a5b5a7b
commit 3146d012c7
3 changed files with 276 additions and 126 deletions
+61
View File
@@ -1,5 +1,66 @@
use crate::swqos::SwqosConfig;
use solana_commitment_config::CommitmentConfig;
use std::hash::{Hash, Hasher};
/// Infrastructure-only configuration (wallet-independent)
/// Can be shared across multiple wallets using the same RPC/SWQOS setup
#[derive(Debug, Clone)]
pub struct InfrastructureConfig {
pub rpc_url: String,
pub swqos_configs: Vec<SwqosConfig>,
pub commitment: CommitmentConfig,
}
impl InfrastructureConfig {
pub fn new(
rpc_url: String,
swqos_configs: Vec<SwqosConfig>,
commitment: CommitmentConfig,
) -> Self {
Self {
rpc_url,
swqos_configs,
commitment,
}
}
/// Create from TradeConfig (extract infrastructure-only settings)
pub fn from_trade_config(config: &TradeConfig) -> Self {
Self {
rpc_url: config.rpc_url.clone(),
swqos_configs: config.swqos_configs.clone(),
commitment: config.commitment.clone(),
}
}
/// Generate a cache key for this infrastructure configuration
pub fn cache_key(&self) -> String {
use std::collections::hash_map::DefaultHasher;
let mut hasher = DefaultHasher::new();
self.hash(&mut hasher);
format!("{:x}", hasher.finish())
}
}
// Manual Hash implementation since CommitmentConfig doesn't implement Hash
impl Hash for InfrastructureConfig {
fn hash<H: Hasher>(&self, state: &mut H) {
self.rpc_url.hash(state);
self.swqos_configs.hash(state);
// Hash commitment level as string since CommitmentConfig doesn't impl Hash
format!("{:?}", self.commitment).hash(state);
}
}
impl PartialEq for InfrastructureConfig {
fn eq(&self, other: &Self) -> bool {
self.rpc_url == other.rpc_url
&& self.swqos_configs == other.swqos_configs
&& self.commitment == other.commitment
}
}
impl Eq for InfrastructureConfig {}
#[derive(Debug, Clone)]
pub struct TradeConfig {
+209 -120
View File
@@ -7,7 +7,7 @@ pub mod trading;
pub mod utils;
use crate::common::nonce_cache::DurableNonceInfo;
use crate::common::GasFeeStrategy;
use crate::common::TradeConfig;
use crate::common::{TradeConfig, InfrastructureConfig};
use crate::constants::trade::trade::DEFAULT_SLIPPAGE;
use crate::constants::SOL_TOKEN_ACCOUNT;
use crate::constants::USD1_TOKEN_ACCOUNT;
@@ -46,6 +46,73 @@ pub enum TradeTokenType {
USDC,
}
/// Shared infrastructure components that can be reused across multiple wallets
///
/// This struct holds the expensive-to-initialize components (RPC client, SWQOS clients)
/// that are wallet-independent and can be shared when only the trading wallet changes.
pub struct TradingInfrastructure {
/// Shared RPC client for blockchain interactions
pub rpc: Arc<SolanaRpcClient>,
/// Shared SWQOS clients for transaction priority and routing
pub swqos_clients: Vec<Arc<SwqosClient>>,
/// Configuration used to create this infrastructure
pub config: InfrastructureConfig,
}
impl TradingInfrastructure {
/// Create new shared infrastructure from configuration
///
/// This performs the expensive initialization:
/// - Creates RPC client with connection pool
/// - Creates SWQOS clients (each with their own HTTP client)
/// - Initializes rent cache and starts background updater
pub async fn new(config: InfrastructureConfig) -> Self {
// Install crypto provider (idempotent)
if CryptoProvider::get_default().is_none() {
let _ = default_provider()
.install_default()
.map_err(|e| anyhow::anyhow!("Failed to install crypto provider: {:?}", e));
}
// Create RPC client
let rpc = Arc::new(SolanaRpcClient::new_with_commitment(
config.rpc_url.clone(),
config.commitment.clone(),
));
// Initialize rent cache and start background updater
common::seed::update_rents(&rpc).await.unwrap();
common::seed::start_rent_updater(rpc.clone());
// Create SWQOS clients with blacklist checking
let mut swqos_clients: Vec<Arc<SwqosClient>> = vec![];
for swqos in &config.swqos_configs {
// Check blacklist, skip disabled providers
if swqos.is_blacklisted() {
eprintln!("\u{26a0}\u{fe0f} SWQOS {:?} is blacklisted, skipping", swqos.swqos_type());
continue;
}
match SwqosConfig::get_swqos_client(
config.rpc_url.clone(),
config.commitment.clone(),
swqos.clone(),
).await {
Ok(swqos_client) => swqos_clients.push(swqos_client),
Err(err) => eprintln!(
"failed to create {:?} swqos client: {err}. Excluding from swqos list",
swqos.swqos_type()
),
}
}
Self {
rpc,
swqos_clients,
config,
}
}
}
/// Main trading client for Solana DeFi protocols
///
/// `SolTradingSDK` provides a unified interface for trading across multiple Solana DEXs
@@ -54,10 +121,9 @@ pub enum TradeTokenType {
pub struct TradingClient {
/// The keypair used for signing all transactions
pub payer: Arc<Keypair>,
/// RPC client for blockchain interactions
pub rpc: Arc<SolanaRpcClient>,
/// SWQOS clients for transaction priority and routing
pub swqos_clients: Vec<Arc<SwqosClient>>,
/// Shared infrastructure (RPC client, SWQOS clients)
/// Can be shared across multiple TradingClient instances with different wallets
pub infrastructure: Arc<TradingInfrastructure>,
/// Optional middleware manager for custom transaction processing
pub middleware_manager: Option<Arc<MiddlewareManager>>,
/// Whether to use seed optimization for all ATA operations (default: true)
@@ -74,8 +140,7 @@ impl Clone for TradingClient {
fn clone(&self) -> Self {
Self {
payer: self.payer.clone(),
rpc: self.rpc.clone(),
swqos_clients: self.swqos_clients.clone(),
infrastructure: self.infrastructure.clone(),
middleware_manager: self.middleware_manager.clone(),
use_seed_optimize: self.use_seed_optimize,
}
@@ -169,6 +234,120 @@ pub struct TradeSellParams {
}
impl TradingClient {
/// Create a TradingClient from shared infrastructure (fast path)
///
/// This is the preferred method when multiple wallets share the same infrastructure.
/// It only performs wallet-specific initialization (fast_init) without the expensive
/// RPC/SWQOS client creation.
///
/// # Arguments
/// * `payer` - The keypair used for signing transactions
/// * `infrastructure` - Shared infrastructure (RPC client, SWQOS clients)
/// * `use_seed_optimize` - Whether to use seed optimization for ATA operations
///
/// # Returns
/// Returns a configured `TradingClient` instance ready for trading operations
pub fn from_infrastructure(
payer: Arc<Keypair>,
infrastructure: Arc<TradingInfrastructure>,
use_seed_optimize: bool,
) -> Self {
// Initialize wallet-specific caches (fast, synchronous)
crate::common::fast_fn::fast_init(&payer.pubkey());
Self {
payer,
infrastructure,
middleware_manager: None,
use_seed_optimize,
}
}
/// Create a TradingClient from shared infrastructure with optional WSOL ATA setup
///
/// Same as `from_infrastructure` but also handles WSOL ATA creation if requested.
///
/// # Arguments
/// * `payer` - The keypair used for signing transactions
/// * `infrastructure` - Shared infrastructure (RPC client, SWQOS clients)
/// * `use_seed_optimize` - Whether to use seed optimization for ATA operations
/// * `create_wsol_ata` - Whether to check/create WSOL ATA
pub async fn from_infrastructure_with_wsol_setup(
payer: Arc<Keypair>,
infrastructure: Arc<TradingInfrastructure>,
use_seed_optimize: bool,
create_wsol_ata: bool,
) -> Self {
crate::common::fast_fn::fast_init(&payer.pubkey());
if create_wsol_ata {
Self::ensure_wsol_ata(&payer, &infrastructure.rpc).await;
}
Self {
payer,
infrastructure,
middleware_manager: None,
use_seed_optimize,
}
}
/// Helper to ensure WSOL ATA exists for a wallet
async fn ensure_wsol_ata(payer: &Arc<Keypair>, rpc: &Arc<SolanaRpcClient>) {
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(_) => {
println!("✅ WSOL ATA已存在: {}", wsol_ata);
}
Err(_) => {
println!("🔨 创建WSOL ATA: {}", 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已存在(无需创建)");
}
}
}
}
/// Creates a new SolTradingSDK instance with the specified configuration
///
/// This function initializes the trading system with RPC connection, SWQOS settings,
@@ -176,117 +355,27 @@ impl TradingClient {
///
/// # 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
/// * `trade_config` - Trading configuration including RPC URL, SWQOS settings, etc.
///
/// # Returns
/// Returns a configured `SolTradingSDK` instance ready for trading operations
#[inline]
pub async fn new(payer: Arc<Keypair>, trade_config: TradeConfig) -> Self {
crate::common::fast_fn::fast_init(&payer.try_pubkey().unwrap());
// Create infrastructure from trade config
let infra_config = InfrastructureConfig::from_trade_config(&trade_config);
let infrastructure = Arc::new(TradingInfrastructure::new(infra_config).await);
if CryptoProvider::get_default().is_none() {
let _ = default_provider()
.install_default()
.map_err(|e| anyhow::anyhow!("Failed to install crypto provider: {:?}", e));
}
// Initialize wallet-specific caches
crate::common::fast_fn::fast_init(&payer.pubkey());
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![];
for swqos in swqos_configs {
// Check blacklist, skip disabled providers
if swqos.is_blacklisted() {
eprintln!("\u{26a0}\u{fe0f} SWQOS {:?} is blacklisted, skipping", swqos.swqos_type());
continue;
}
match SwqosConfig::get_swqos_client(rpc_url.clone(), commitment.clone(), swqos.clone())
.await
{
Ok(swqos_client) => swqos_clients.push(swqos_client),
Err(err) => eprintln!(
"failed to create {:?} swqos client: {err}. Excluding from swqos list",
swqos.swqos_type()
),
}
}
let rpc =
Arc::new(SolanaRpcClient::new_with_commitment(rpc_url.clone(), commitment.clone()));
common::seed::update_rents(&rpc).await.unwrap();
common::seed::start_rent_updater(rpc.clone());
// 🔧 初始化WSOL ATA:如果配置为启动时创建,则检查并创建
// Handle WSOL ATA creation if configured
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已存在(无需创建)");
}
}
}
Self::ensure_wsol_ata(&payer, &infrastructure.rpc).await;
}
let instance = Self {
payer,
rpc,
swqos_clients,
infrastructure,
middleware_manager: None,
use_seed_optimize: trade_config.use_seed_optimize,
};
@@ -320,7 +409,7 @@ impl TradingClient {
/// # Returns
/// Returns a reference to the Arc-wrapped SolanaRpcClient instance
pub fn get_rpc(&self) -> &Arc<SolanaRpcClient> {
&self.rpc
&self.infrastructure.rpc
}
/// Gets the current globally shared SolanaTrade instance
@@ -394,7 +483,7 @@ impl TradingClient {
let executor = TradeFactory::create_executor(params.dex_type.clone());
let protocol_params = params.extension_params;
let buy_params = SwapParams {
rpc: Some(self.rpc.clone()),
rpc: Some(self.infrastructure.rpc.clone()),
payer: self.payer.clone(),
trade_type: TradeType::Buy,
input_mint: input_token_mint,
@@ -408,7 +497,7 @@ impl TradingClient {
wait_transaction_confirmed: params.wait_transaction_confirmed,
protocol_params: protocol_params.clone(),
open_seed_optimize: self.use_seed_optimize, // 使用全局seed优化配置
swqos_clients: self.swqos_clients.clone(),
swqos_clients: self.infrastructure.swqos_clients.clone(),
middleware_manager: self.middleware_manager.clone(),
durable_nonce: params.durable_nonce,
with_tip: true,
@@ -503,7 +592,7 @@ impl TradingClient {
USD1_TOKEN_ACCOUNT
};
let sell_params = SwapParams {
rpc: Some(self.rpc.clone()),
rpc: Some(self.infrastructure.rpc.clone()),
payer: self.payer.clone(),
trade_type: TradeType::Sell,
input_mint: params.mint,
@@ -518,7 +607,7 @@ impl TradingClient {
protocol_params: protocol_params.clone(),
with_tip: params.with_tip,
open_seed_optimize: self.use_seed_optimize, // 使用全局seed优化配置
swqos_clients: self.swqos_clients.clone(),
swqos_clients: self.infrastructure.swqos_clients.clone(),
middleware_manager: self.middleware_manager.clone(),
durable_nonce: params.durable_nonce,
create_input_mint_ata: false,
@@ -622,12 +711,12 @@ impl TradingClient {
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 recent_blockhash = self.infrastructure.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?;
let signature = self.infrastructure.rpc.send_and_confirm_transaction(&transaction).await?;
Ok(signature.to_string())
}
/// Closes the wSOL associated token account and unwraps remaining balance to native SOL
@@ -650,12 +739,12 @@ impl TradingClient {
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 recent_blockhash = self.infrastructure.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?;
let signature = self.infrastructure.rpc.send_and_confirm_transaction(&transaction).await?;
Ok(signature.to_string())
}
@@ -680,7 +769,7 @@ impl TradingClient {
use crate::trading::common::wsol_manager::create_wsol_ata;
use solana_sdk::transaction::Transaction;
let recent_blockhash = self.rpc.get_latest_blockhash().await?;
let recent_blockhash = self.infrastructure.rpc.get_latest_blockhash().await?;
let instructions = create_wsol_ata(&self.payer.pubkey());
// If instructions are empty, ATA already exists
@@ -691,7 +780,7 @@ impl TradingClient {
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?;
let signature = self.infrastructure.rpc.send_and_confirm_transaction(&transaction).await?;
Ok(signature.to_string())
}
@@ -730,7 +819,7 @@ impl TradingClient {
&crate::constants::TOKEN_PROGRAM,
)?;
let account_exists = self.rpc.get_account(&seed_ata_address).await.is_ok();
let account_exists = self.infrastructure.rpc.get_account(&seed_ata_address).await.is_ok();
let instructions = if account_exists {
// 如果账户已存在,使用不创建账户的版本
@@ -740,10 +829,10 @@ impl TradingClient {
wrap_wsol_to_sol_internal(&self.payer.pubkey(), amount)?
};
let recent_blockhash = self.rpc.get_latest_blockhash().await?;
let recent_blockhash = self.infrastructure.rpc.get_latest_blockhash().await?;
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?;
let signature = self.infrastructure.rpc.send_and_confirm_transaction(&transaction).await?;
Ok(signature.to_string())
}
}
+6 -6
View File
@@ -9,12 +9,12 @@ use solana_sdk::signer::Signer;
impl TradingClient {
#[inline]
pub async fn get_sol_balance(&self, payer: &Pubkey) -> Result<u64, anyhow::Error> {
trading::common::utils::get_sol_balance(&self.rpc, payer).await
trading::common::utils::get_sol_balance(&self.infrastructure.rpc, payer).await
}
#[inline]
pub async fn get_payer_sol_balance(&self) -> Result<u64, anyhow::Error> {
trading::common::utils::get_sol_balance(&self.rpc, &self.payer.pubkey()).await
trading::common::utils::get_sol_balance(&self.infrastructure.rpc, &self.payer.pubkey()).await
}
#[inline]
@@ -23,12 +23,12 @@ impl TradingClient {
payer: &Pubkey,
mint: &Pubkey,
) -> Result<u64, anyhow::Error> {
trading::common::utils::get_token_balance(&self.rpc, payer, mint).await
trading::common::utils::get_token_balance(&self.infrastructure.rpc, payer, mint).await
}
#[inline]
pub async fn get_payer_token_balance(&self, mint: &Pubkey) -> Result<u64, anyhow::Error> {
trading::common::utils::get_token_balance(&self.rpc, &self.payer.pubkey(), mint).await
trading::common::utils::get_token_balance(&self.infrastructure.rpc, &self.payer.pubkey(), mint).await
}
#[inline]
@@ -48,11 +48,11 @@ impl TradingClient {
receive_wallet: &Pubkey,
amount: u64,
) -> Result<(), anyhow::Error> {
trading::common::utils::transfer_sol(&self.rpc, payer, receive_wallet, amount).await
trading::common::utils::transfer_sol(&self.infrastructure.rpc, payer, receive_wallet, amount).await
}
#[inline]
pub async fn close_token_account(&self, mint: &Pubkey) -> Result<(), anyhow::Error> {
trading::common::utils::close_token_account(&self.rpc, self.payer.as_ref(), mint).await
trading::common::utils::close_token_account(&self.infrastructure.rpc, self.payer.as_ref(), mint).await
}
}