diff --git a/docs/TRADING_PARAMETERS.md b/docs/TRADING_PARAMETERS.md index 8ec4b4e..6e09a25 100644 --- a/docs/TRADING_PARAMETERS.md +++ b/docs/TRADING_PARAMETERS.md @@ -38,6 +38,7 @@ The `TradeBuyParams` struct contains all parameters required for executing buy o | `durable_nonce` | `Option` | ❌ | Durable nonce information containing nonce account and current nonce value | | `fixed_output_token_amount` | `Option` | ❌ | Optional fixed output token amount. If set, this value will be directly assigned to the output amount instead of being calculated (required for Meteora DAMM V2) | | `gas_fee_strategy` | `GasFeeStrategy` | ✅ | Gas fee strategy instance for controlling transaction fees and priorities | +| `simulate` | `bool` | ✅ | Whether to simulate the transaction instead of executing it. When true, the transaction will be simulated via RPC to validate and show detailed logs, compute units consumed, and potential errors without actually submitting to the blockchain | ## TradeSellParams @@ -69,6 +70,7 @@ The `TradeSellParams` struct contains all parameters required for executing sell | `durable_nonce` | `Option` | ❌ | Durable nonce information containing nonce account and current nonce value | | `gas_fee_strategy` | `GasFeeStrategy` | ✅ | Gas fee strategy instance for controlling transaction fees and priorities | | `fixed_output_token_amount` | `Option` | ❌ | Optional fixed output token amount. If set, this value will be directly assigned to the output amount instead of being calculated (required for Meteora DAMM V2) | +| `simulate` | `bool` | ✅ | Whether to simulate the transaction instead of executing it. When true, the transaction will be simulated via RPC to validate and show detailed logs, compute units consumed, and potential errors without actually submitting to the blockchain | ## Parameter Categories @@ -158,3 +160,20 @@ Each DEX protocol requires specific `extension_params`: - **Meteora DAMM V2**: `MeteoraDammV2Params` Refer to the respective protocol documentation for detailed parameter specifications. + +### 🔍 Transaction Simulation + +When `simulate: true`: +- **No Blockchain Submission**: The transaction is not actually submitted to the blockchain +- **Validation**: Validates transaction construction and execution without consuming actual tokens +- **Detailed Output**: Shows comprehensive information including: + - Transaction logs with detailed execution steps + - Compute units consumed (useful for optimizing CU budget) + - Potential errors and failure reasons + - Inner instructions for debugging +- **Use Cases**: + - Testing transaction logic before real execution + - Debugging failed transactions + - Estimating compute unit consumption + - Validating transaction parameters +- 📝 **Note**: Simulation uses RPC's `simulateTransaction` method with processed commitment level diff --git a/docs/TRADING_PARAMETERS_CN.md b/docs/TRADING_PARAMETERS_CN.md index 7a212c6..1c6c880 100644 --- a/docs/TRADING_PARAMETERS_CN.md +++ b/docs/TRADING_PARAMETERS_CN.md @@ -38,6 +38,7 @@ | `durable_nonce` | `Option` | ❌ | 持久 nonce 信息,包含 nonce 账户和当前 nonce 值 | | `fixed_output_token_amount` | `Option` | ❌ | 可选的固定输出代币数量。如果设置,此值将直接分配给输出数量而不是通过计算得出(Meteora DAMM V2 必需) | | `gas_fee_strategy` | `GasFeeStrategy` | ✅ | Gas fee 策略实例,用于控制交易费用和优先级 | +| `simulate` | `bool` | ✅ | 是否模拟交易而不实际执行。当为 true 时,将通过 RPC 模拟交易以验证并显示详细日志、计算单元消耗和潜在错误,而不会实际提交到区块链 | ## TradeSellParams @@ -69,6 +70,7 @@ | `durable_nonce` | `Option` | ❌ | 持久 nonce 信息,包含 nonce 账户和当前 nonce 值 | | `gas_fee_strategy` | `GasFeeStrategy` | ✅ | Gas fee 策略实例,用于控制交易费用和优先级 | | `fixed_output_token_amount` | `Option` | ❌ | 可选的固定输出代币数量。如果设置,此值将直接分配给输出数量而不是通过计算得出(Meteora DAMM V2 必需) | +| `simulate` | `bool` | ✅ | 是否模拟交易而不实际执行。当为 true 时,将通过 RPC 模拟交易以验证并显示详细日志、计算单元消耗和潜在错误,而不会实际提交到区块链 | ## 参数分类 @@ -158,3 +160,20 @@ - **Meteora DAMM V2**: `MeteoraDammV2Params` 请参阅相应的协议文档了解详细的参数规格。 + +### 🔍 交易模拟 + +当 `simulate: true` 时: +- **不提交区块链**: 交易不会实际提交到区块链 +- **验证功能**: 验证交易构建和执行,而不会消耗实际代币 +- **详细输出**: 显示全面的信息,包括: + - 带有详细执行步骤的交易日志 + - 计算单元消耗(用于优化 CU 预算) + - 潜在错误和失败原因 + - 用于调试的内部指令 +- **使用场景**: + - 在真实执行前测试交易逻辑 + - 调试失败的交易 + - 估算计算单元消耗 + - 验证交易参数 +- 📝 **注意**: 模拟使用 RPC 的 `simulateTransaction` 方法,采用 processed 承诺级别 diff --git a/examples/address_lookup/src/main.rs b/examples/address_lookup/src/main.rs index 2adc010..df2858d 100644 --- a/examples/address_lookup/src/main.rs +++ b/examples/address_lookup/src/main.rs @@ -158,6 +158,7 @@ async fn pumpfun_copy_trade_with_grpc(trade_info: PumpFunTradeEvent) -> AnyResul durable_nonce: None, fixed_output_token_amount: None, gas_fee_strategy: gas_fee_strategy, + simulate: false, }; client.buy(buy_params).await?; diff --git a/examples/bonk_copy_trading/src/main.rs b/examples/bonk_copy_trading/src/main.rs index aa272d4..2896512 100644 --- a/examples/bonk_copy_trading/src/main.rs +++ b/examples/bonk_copy_trading/src/main.rs @@ -166,6 +166,7 @@ async fn bonk_copy_trade_with_grpc(trade_info: BonkTradeEvent) -> AnyResult<()> durable_nonce: None, fixed_output_token_amount: None, gas_fee_strategy: gas_fee_strategy.clone(), + simulate: false, }; client.buy(buy_params).await?; @@ -208,8 +209,10 @@ async fn bonk_copy_trade_with_grpc(trade_info: BonkTradeEvent) -> AnyResult<()> durable_nonce: None, create_output_token_ata: false, close_output_token_ata: false, + close_mint_token_ata: false, fixed_output_token_amount: None, gas_fee_strategy: gas_fee_strategy, + simulate: false, }; client.sell(sell_params).await?; diff --git a/examples/bonk_sniper_trading/src/main.rs b/examples/bonk_sniper_trading/src/main.rs index 6c037ac..3423587 100644 --- a/examples/bonk_sniper_trading/src/main.rs +++ b/examples/bonk_sniper_trading/src/main.rs @@ -136,6 +136,7 @@ async fn bonk_sniper_trade_with_shreds(trade_info: BonkTradeEvent) -> AnyResult< durable_nonce: None, fixed_output_token_amount: None, gas_fee_strategy: gas_fee_strategy.clone(), + simulate: false, }; client.buy(buy_params).await?; @@ -168,11 +169,13 @@ async fn bonk_sniper_trade_with_shreds(trade_info: BonkTradeEvent) -> AnyResult< wait_transaction_confirmed: true, create_output_token_ata: true, close_output_token_ata: true, + close_mint_token_ata: false, open_seed_optimize: false, with_tip: false, durable_nonce: None, fixed_output_token_amount: None, gas_fee_strategy: gas_fee_strategy, + simulate: false, }; client.sell(sell_params).await?; diff --git a/examples/cli_trading/src/main.rs b/examples/cli_trading/src/main.rs index 7876900..44fee86 100644 --- a/examples/cli_trading/src/main.rs +++ b/examples/cli_trading/src/main.rs @@ -634,6 +634,7 @@ async fn handle_buy_pumpfun( durable_nonce: None, fixed_output_token_amount: None, gas_fee_strategy: gas_fee_strategy, + simulate: false, }; match client.buy(buy_params).await { Ok((_, signature)) => { @@ -688,6 +689,7 @@ async fn handle_buy_pumpswap( durable_nonce: None, fixed_output_token_amount: None, gas_fee_strategy: gas_fee_strategy, + simulate: false, }; match client.buy(buy_params).await { Ok((_, signature)) => { @@ -741,6 +743,7 @@ async fn handle_buy_bonk( durable_nonce: None, fixed_output_token_amount: None, gas_fee_strategy: gas_fee_strategy, + simulate: false, }; match client.buy(buy_params).await { Ok((_, signature)) => { @@ -798,6 +801,7 @@ async fn handle_buy_raydium_v4( durable_nonce: None, fixed_output_token_amount: None, gas_fee_strategy: gas_fee_strategy, + simulate: false, }; match client.buy(buy_params).await { Ok((_, signature)) => { @@ -855,6 +859,7 @@ async fn handle_buy_raydium_cpmm( durable_nonce: None, fixed_output_token_amount: None, gas_fee_strategy: gas_fee_strategy, + simulate: false, }; match client.buy(buy_params).await { Ok((_, signature)) => { @@ -1018,10 +1023,12 @@ async fn handle_sell_pumpfun( wait_transaction_confirmed: true, create_output_token_ata: true, close_output_token_ata: false, + close_mint_token_ata: false, open_seed_optimize: use_seed, durable_nonce: None, fixed_output_token_amount: None, gas_fee_strategy: gas_fee_strategy, + simulate: false, }; match client.sell(sell_params).await { @@ -1075,10 +1082,12 @@ async fn handle_sell_pumpswap( wait_transaction_confirmed: true, create_output_token_ata: true, close_output_token_ata: false, + close_mint_token_ata: false, open_seed_optimize: use_seed, durable_nonce: None, fixed_output_token_amount: None, gas_fee_strategy: gas_fee_strategy, + simulate: false, }; match client.sell(sell_params).await { Ok((_, signature)) => { @@ -1131,10 +1140,12 @@ async fn handle_sell_bonk( wait_transaction_confirmed: true, create_output_token_ata: true, close_output_token_ata: false, + close_mint_token_ata: false, open_seed_optimize: use_seed, durable_nonce: None, fixed_output_token_amount: None, gas_fee_strategy: gas_fee_strategy, + simulate: false, }; match client.sell(sell_params).await { Ok((_, signature)) => { @@ -1190,10 +1201,12 @@ async fn handle_sell_raydium_v4( wait_transaction_confirmed: true, create_output_token_ata: true, close_output_token_ata: false, + close_mint_token_ata: false, open_seed_optimize: use_seed, durable_nonce: None, fixed_output_token_amount: None, gas_fee_strategy: gas_fee_strategy, + simulate: false, }; match client.sell(sell_params).await { Ok((_, signature)) => { @@ -1249,10 +1262,12 @@ async fn handle_sell_raydium_cpmm( wait_transaction_confirmed: true, create_output_token_ata: true, close_output_token_ata: false, + close_mint_token_ata: false, open_seed_optimize: use_seed, durable_nonce: None, fixed_output_token_amount: None, gas_fee_strategy: gas_fee_strategy, + simulate: false, }; match client.sell(sell_params).await { Ok((_, signature)) => { diff --git a/examples/meteora_damm_v2_direct_trading/src/main.rs b/examples/meteora_damm_v2_direct_trading/src/main.rs index 697153f..55284d1 100644 --- a/examples/meteora_damm_v2_direct_trading/src/main.rs +++ b/examples/meteora_damm_v2_direct_trading/src/main.rs @@ -46,7 +46,8 @@ async fn main() -> Result<(), Box> { open_seed_optimize: false, durable_nonce: None, fixed_output_token_amount: Some(1), - gas_fee_strategy: gas_fee_strategy.clone() + gas_fee_strategy: gas_fee_strategy.clone(), + simulate: false, }; client.buy(buy_params).await?; @@ -75,10 +76,12 @@ async fn main() -> Result<(), Box> { wait_transaction_confirmed: true, create_output_token_ata: false, //if output token is SOL/WSOL,set to true,if output token is USDC,set to false. close_output_token_ata: false, //if output token is SOL/WSOL,set to true,if output token is USDC,set to false. + close_mint_token_ata: false, open_seed_optimize: false, durable_nonce: None, fixed_output_token_amount: Some(1), gas_fee_strategy: gas_fee_strategy, + simulate: false, }; client.sell(sell_params).await?; diff --git a/examples/middleware_system/src/main.rs b/examples/middleware_system/src/main.rs index 6b0798c..ed6b268 100644 --- a/examples/middleware_system/src/main.rs +++ b/examples/middleware_system/src/main.rs @@ -102,6 +102,7 @@ async fn test_middleware() -> AnyResult<()> { durable_nonce: None, fixed_output_token_amount: None, gas_fee_strategy: gas_fee_strategy, + simulate: false, }; client.buy(buy_params).await?; println!("tip: This transaction will not succeed because we're using a test account. You can modify the code to initialize the payer with your own private key"); diff --git a/examples/nonce_cache/src/main.rs b/examples/nonce_cache/src/main.rs index 5430403..b32718b 100644 --- a/examples/nonce_cache/src/main.rs +++ b/examples/nonce_cache/src/main.rs @@ -158,6 +158,7 @@ async fn pumpfun_copy_trade_with_grpc(trade_info: PumpFunTradeEvent) -> AnyResul durable_nonce: durable_nonce, fixed_output_token_amount: None, gas_fee_strategy: gas_fee_strategy, + simulate: false, }; client.buy(buy_params).await?; diff --git a/examples/pumpfun_copy_trading/src/main.rs b/examples/pumpfun_copy_trading/src/main.rs index 6ee0436..162f44b 100644 --- a/examples/pumpfun_copy_trading/src/main.rs +++ b/examples/pumpfun_copy_trading/src/main.rs @@ -154,6 +154,7 @@ async fn pumpfun_copy_trade_with_grpc(trade_info: PumpFunTradeEvent) -> AnyResul durable_nonce: None, fixed_output_token_amount: None, gas_fee_strategy: gas_fee_strategy.clone(), + simulate: false, }; client.buy(buy_params).await?; @@ -192,10 +193,12 @@ async fn pumpfun_copy_trade_with_grpc(trade_info: PumpFunTradeEvent) -> AnyResul wait_transaction_confirmed: true, create_output_token_ata: false, close_output_token_ata: false, + close_mint_token_ata: false, open_seed_optimize: false, durable_nonce: None, fixed_output_token_amount: None, gas_fee_strategy: gas_fee_strategy, + simulate: false, }; client.sell(sell_params).await?; diff --git a/examples/pumpfun_sniper_trading/src/main.rs b/examples/pumpfun_sniper_trading/src/main.rs index afced86..3b212fd 100644 --- a/examples/pumpfun_sniper_trading/src/main.rs +++ b/examples/pumpfun_sniper_trading/src/main.rs @@ -120,6 +120,7 @@ async fn pumpfun_sniper_trade_with_shreds(trade_info: PumpFunTradeEvent) -> AnyR durable_nonce: None, fixed_output_token_amount: None, gas_fee_strategy: gas_fee_strategy.clone(), + simulate: false, }; client.buy(buy_params).await?; @@ -147,10 +148,12 @@ async fn pumpfun_sniper_trade_with_shreds(trade_info: PumpFunTradeEvent) -> AnyR wait_transaction_confirmed: true, create_output_token_ata: true, close_output_token_ata: true, + close_mint_token_ata: false, open_seed_optimize: false, durable_nonce: None, fixed_output_token_amount: None, gas_fee_strategy: gas_fee_strategy, + simulate: false, }; client.sell(sell_params).await?; diff --git a/examples/pumpswap_direct_trading/src/main.rs b/examples/pumpswap_direct_trading/src/main.rs index 0f445c2..282e437 100644 --- a/examples/pumpswap_direct_trading/src/main.rs +++ b/examples/pumpswap_direct_trading/src/main.rs @@ -47,6 +47,7 @@ async fn main() -> Result<(), Box> { durable_nonce: None, fixed_output_token_amount: None, gas_fee_strategy: gas_fee_strategy.clone(), + simulate: false, }; client.buy(buy_params).await?; @@ -74,10 +75,12 @@ async fn main() -> Result<(), Box> { wait_transaction_confirmed: true, create_output_token_ata: true, close_output_token_ata: true, + close_mint_token_ata: false, open_seed_optimize: false, durable_nonce: None, fixed_output_token_amount: None, gas_fee_strategy: gas_fee_strategy, + simulate: false, }; client.sell(sell_params).await?; diff --git a/examples/pumpswap_trading/src/main.rs b/examples/pumpswap_trading/src/main.rs index f7d93cc..f0fdc63 100644 --- a/examples/pumpswap_trading/src/main.rs +++ b/examples/pumpswap_trading/src/main.rs @@ -216,6 +216,7 @@ async fn pumpswap_trade_with_grpc(mint_pubkey: Pubkey, params: PumpSwapParams) - durable_nonce: None, fixed_output_token_amount: None, gas_fee_strategy: gas_fee_strategy.clone(), + simulate: false, }; client.buy(buy_params).await?; @@ -245,10 +246,12 @@ async fn pumpswap_trade_with_grpc(mint_pubkey: Pubkey, params: PumpSwapParams) - wait_transaction_confirmed: true, create_output_token_ata: is_sol, close_output_token_ata: is_sol, + close_mint_token_ata: false, open_seed_optimize: false, durable_nonce: None, fixed_output_token_amount: None, gas_fee_strategy: gas_fee_strategy, + simulate: false, }; client.sell(sell_params).await?; diff --git a/examples/raydium_amm_v4_trading/src/main.rs b/examples/raydium_amm_v4_trading/src/main.rs index e47bf8c..e0857db 100644 --- a/examples/raydium_amm_v4_trading/src/main.rs +++ b/examples/raydium_amm_v4_trading/src/main.rs @@ -164,6 +164,7 @@ async fn raydium_amm_v4_copy_trade_with_grpc(trade_info: RaydiumAmmV4SwapEvent) durable_nonce: None, fixed_output_token_amount: None, gas_fee_strategy: gas_fee_strategy.clone(), + simulate: false, }; client.buy(buy_params).await?; @@ -192,10 +193,12 @@ async fn raydium_amm_v4_copy_trade_with_grpc(trade_info: RaydiumAmmV4SwapEvent) wait_transaction_confirmed: true, create_output_token_ata: is_wsol, close_output_token_ata: is_wsol, + close_mint_token_ata: false, open_seed_optimize: false, durable_nonce: None, fixed_output_token_amount: None, gas_fee_strategy: gas_fee_strategy, + simulate: false, }; client.sell(sell_params).await?; diff --git a/examples/raydium_cpmm_trading/src/main.rs b/examples/raydium_cpmm_trading/src/main.rs index 22d4e19..4be1f73 100644 --- a/examples/raydium_cpmm_trading/src/main.rs +++ b/examples/raydium_cpmm_trading/src/main.rs @@ -158,6 +158,7 @@ async fn raydium_cpmm_copy_trade_with_grpc(trade_info: RaydiumCpmmSwapEvent) -> durable_nonce: None, fixed_output_token_amount: None, gas_fee_strategy: gas_fee_strategy.clone(), + simulate: false, }; client.buy(buy_params).await?; @@ -188,10 +189,12 @@ async fn raydium_cpmm_copy_trade_with_grpc(trade_info: RaydiumCpmmSwapEvent) -> wait_transaction_confirmed: true, create_output_token_ata: is_wsol, close_output_token_ata: is_wsol, + close_mint_token_ata: false, open_seed_optimize: false, durable_nonce: None, fixed_output_token_amount: None, gas_fee_strategy: gas_fee_strategy, + simulate: false, }; client.sell(sell_params).await?; diff --git a/examples/seed_trading/src/main.rs b/examples/seed_trading/src/main.rs index 3e75978..31ea304 100644 --- a/examples/seed_trading/src/main.rs +++ b/examples/seed_trading/src/main.rs @@ -46,6 +46,7 @@ async fn main() -> Result<(), Box> { durable_nonce: None, fixed_output_token_amount: None, gas_fee_strategy: gas_fee_strategy.clone(), + simulate: false, }; client.buy(buy_params).await?; @@ -81,10 +82,12 @@ async fn main() -> Result<(), Box> { wait_transaction_confirmed: true, create_output_token_ata: true, close_output_token_ata: true, + close_mint_token_ata: false, open_seed_optimize: true, // ❗️❗️❗️❗️ open seed optimize durable_nonce: None, fixed_output_token_amount: None, gas_fee_strategy: gas_fee_strategy, + simulate: false, }; client.sell(sell_params).await?; diff --git a/src/lib.rs b/src/lib.rs index 455eef6..e19b8ee 100755 --- a/src/lib.rs +++ b/src/lib.rs @@ -114,6 +114,8 @@ pub struct TradeBuyParams { pub fixed_output_token_amount: Option, /// Gas fee strategy pub gas_fee_strategy: GasFeeStrategy, + /// Whether to simulate the transaction instead of executing it + pub simulate: bool, } /// Parameters for executing sell orders across different DEX protocols @@ -158,6 +160,8 @@ pub struct TradeSellParams { pub fixed_output_token_amount: Option, /// Gas fee strategy pub gas_fee_strategy: GasFeeStrategy, + /// Whether to simulate the transaction instead of executing it + pub simulate: bool, } impl SolanaTrade { @@ -320,6 +324,7 @@ impl SolanaTrade { close_output_mint_ata: false, fixed_output_amount: params.fixed_output_token_amount, gas_fee_strategy: params.gas_fee_strategy, + simulate: params.simulate, }; // Validate protocol params @@ -416,6 +421,7 @@ impl SolanaTrade { close_output_mint_ata: params.close_output_token_ata, fixed_output_amount: params.fixed_output_token_amount, gas_fee_strategy: params.gas_fee_strategy, + simulate: params.simulate, }; // Validate protocol params diff --git a/src/trading/core/executor.rs b/src/trading/core/executor.rs index 8a6e4eb..e9c625e 100755 --- a/src/trading/core/executor.rs +++ b/src/trading/core/executor.rs @@ -1,14 +1,20 @@ use anyhow::Result; -use solana_sdk::signature::Signature; +use solana_hash::Hash; +use solana_sdk::{ + instruction::Instruction, message::AddressLookupTableAccount, pubkey::Pubkey, + signature::Keypair, signature::Signature, +}; use std::{sync::Arc, time::Instant}; use crate::{ + common::{nonce_cache::DurableNonceInfo, GasFeeStrategy, SolanaRpcClient}, perf::syscall_bypass::SystemCallBypassManager, trading::core::{ async_executor::execute_parallel, - execution::{Prefetch, InstructionProcessor, ExecutionPath}, + execution::{ExecutionPath, InstructionProcessor, Prefetch}, traits::TradeExecutor, }, + trading::MiddlewareManager, }; use once_cell::sync::Lazy; @@ -32,10 +38,7 @@ impl GenericTradeExecutor { instruction_builder: Arc, protocol_name: &'static str, ) -> Self { - Self { - instruction_builder, - protocol_name, - } + Self { instruction_builder, protocol_name } } } @@ -66,20 +69,68 @@ impl TradeExecutor for GenericTradeExecutor { // 中间件处理 let final_instructions = match ¶ms.middleware_manager { - Some(middleware_manager) => { - middleware_manager - .apply_middlewares_process_protocol_instructions( - instructions, - self.protocol_name.to_string(), - is_buy, - )? - } - None => instructions + Some(middleware_manager) => middleware_manager + .apply_middlewares_process_protocol_instructions( + instructions, + self.protocol_name.to_string(), + is_buy, + )?, + None => instructions, }; // 提交前耗时 let before_submit_elapsed = total_start.elapsed(); + // 如果是模拟模式,直接通过 RPC 模拟交易 + if params.simulate { + let send_start = Instant::now(); + let result = simulate_transaction( + params.rpc, + params.payer, + final_instructions, + params.address_lookup_table_account, + params.recent_blockhash, + params.durable_nonce, + if is_buy { params.data_size_limit } else { 0 }, + params.middleware_manager, + self.protocol_name, + is_buy, + if is_buy { true } else { params.with_tip }, + params.gas_fee_strategy, + ) + .await; + let send_elapsed = send_start.elapsed(); + let total_elapsed = total_start.elapsed(); + + // Get performance metrics using fast timestamp + let timestamp_ns = SYSCALL_BYPASS.fast_timestamp_nanos(); + + // Print all timing metrics at once to avoid blocking critical path + println!("[Timestamp] {}ns", timestamp_ns); + println!( + "[Build Instructions] Time: {:.3}ms ({:.0}μs)", + build_elapsed.as_micros() as f64 / 1000.0, + build_elapsed.as_micros() + ); + println!( + "[Before Submit] {:.3}ms ({:.0}μs)", + before_submit_elapsed.as_micros() as f64 / 1000.0, + before_submit_elapsed.as_micros() + ); + println!( + "[Simulate Transaction] Time: {:.3}ms ({:.0}μs)", + send_elapsed.as_micros() as f64 / 1000.0, + send_elapsed.as_micros() + ); + println!( + "[Total Time] {:.3}ms ({:.0}μs)", + total_elapsed.as_micros() as f64 / 1000.0, + total_elapsed.as_micros() + ); + + return result; + } + // 并行发送交易 let send_start = Instant::now(); let result = execute_parallel( @@ -102,19 +153,31 @@ impl TradeExecutor for GenericTradeExecutor { let send_elapsed = send_start.elapsed(); let total_elapsed = total_start.elapsed(); - // 使用快速时间戳获取性能指标 + // Get performance metrics using fast timestamp let timestamp_ns = SYSCALL_BYPASS.fast_timestamp_nanos(); - // 在完成后一次性打印所有耗时,避免阻塞关键路径 - println!("[时间戳] {}ns", timestamp_ns); - println!("[构建指令] 耗时: {:.3}ms ({:.0}μs)", - build_elapsed.as_micros() as f64 / 1000.0, build_elapsed.as_micros()); - println!("[提交前耗时] {:.3}ms ({:.0}μs)", - before_submit_elapsed.as_micros() as f64 / 1000.0, before_submit_elapsed.as_micros()); - println!("[发送交易] 耗时: {:.3}ms ({:.0}μs)", - send_elapsed.as_micros() as f64 / 1000.0, send_elapsed.as_micros()); - println!("[总耗时] {:.3}ms ({:.0}μs)", - total_elapsed.as_micros() as f64 / 1000.0, total_elapsed.as_micros()); + // Print all timing metrics at once to avoid blocking critical path + println!("[Timestamp] {}ns", timestamp_ns); + println!( + "[Build Instructions] Time: {:.3}ms ({:.0}μs)", + build_elapsed.as_micros() as f64 / 1000.0, + build_elapsed.as_micros() + ); + println!( + "[Before Submit] {:.3}ms ({:.0}μs)", + before_submit_elapsed.as_micros() as f64 / 1000.0, + before_submit_elapsed.as_micros() + ); + println!( + "[Send Transaction] Time: {:.3}ms ({:.0}μs)", + send_elapsed.as_micros() as f64 / 1000.0, + send_elapsed.as_micros() + ); + println!( + "[Total Time] {:.3}ms ({:.0}μs)", + total_elapsed.as_micros() as f64 / 1000.0, + total_elapsed.as_micros() + ); result } @@ -123,3 +186,127 @@ impl TradeExecutor for GenericTradeExecutor { self.protocol_name } } + +/// Simulate transaction using RPC client +async fn simulate_transaction( + rpc: Option>, + payer: Arc, + instructions: Vec, + address_lookup_table_account: Option, + recent_blockhash: Option, + durable_nonce: Option, + data_size_limit: u32, + middleware_manager: Option>, + protocol_name: &'static str, + is_buy: bool, + with_tip: bool, + gas_fee_strategy: GasFeeStrategy, +) -> Result<(bool, Signature)> { + use crate::trading::common::build_transaction; + use solana_client::rpc_config::RpcSimulateTransactionConfig; + use solana_commitment_config::CommitmentLevel; + use solana_transaction_status::UiTransactionEncoding; + + let rpc = rpc.ok_or_else(|| anyhow::anyhow!("RPC client is required for simulation"))?; + + // Get gas fee strategy for simulation (use Default swqos type) + let trade_type = + if is_buy { crate::swqos::TradeType::Buy } else { crate::swqos::TradeType::Sell }; + let gas_fee_configs = gas_fee_strategy.get_strategies(trade_type); + + let default_config = gas_fee_configs + .iter() + .find(|config| config.0 == crate::swqos::SwqosType::Default) + .ok_or_else(|| anyhow::anyhow!("No default gas fee strategy found"))?; + + let tip = if with_tip { default_config.2.tip } else { 0.0 }; + let unit_limit = default_config.2.cu_limit; + let unit_price = default_config.2.cu_price; + + // Build transaction for simulation + let transaction = build_transaction( + payer.clone(), + Some(rpc.clone()), + unit_limit, + unit_price, + instructions, + address_lookup_table_account, + recent_blockhash, + data_size_limit, + middleware_manager, + protocol_name, + is_buy, + false, // simulate doesn't need tip instruction + &Pubkey::default(), + tip, + durable_nonce, + ) + .await?; + + // Simulate the transaction + use solana_commitment_config::CommitmentConfig; + let simulate_result = rpc + .simulate_transaction_with_config( + &transaction, + RpcSimulateTransactionConfig { + sig_verify: false, // Don't verify signature during simulation for speed + replace_recent_blockhash: false, // Use actual blockhash from transaction + commitment: Some(CommitmentConfig { + commitment: CommitmentLevel::Processed, // Use Processed level to get latest state + }), + encoding: Some(UiTransactionEncoding::Base64), // Base64 encoding + accounts: None, // Don't return specific account states (can be specified if needed) + min_context_slot: None, // Don't specify minimum context slot + inner_instructions: true, // Enable inner instructions for debugging and detailed execution flow + }, + ) + .await?; + + let signature = transaction + .signatures + .first() + .ok_or_else(|| anyhow::anyhow!("Transaction has no signatures"))? + .clone(); + + if let Some(err) = simulate_result.value.err { + println!("\n========== [Simulation Failed] =========="); + println!("Error Type: {:?}", err); + println!("Signature: {:?}", signature); + + // Print logs + if let Some(logs) = simulate_result.value.logs { + println!("\n========== Transaction Logs =========="); + for (i, log) in logs.iter().enumerate() { + println!("{:3}. {}", i + 1, log); + } + } + + // Print account usage + if let Some(units_consumed) = simulate_result.value.units_consumed { + println!("\n========== Resource Consumption =========="); + println!("Compute Units Consumed: {}", units_consumed); + } + + println!("=========================================\n"); + return Ok((false, signature)); + } + + // Simulation succeeded + println!("\n========== [Simulation Succeeded] =========="); + println!("Signature: {:?}", signature); + + if let Some(units_consumed) = simulate_result.value.units_consumed { + println!("Compute Units Consumed: {}", units_consumed); + } + + if let Some(logs) = simulate_result.value.logs { + println!("\n========== Transaction Logs =========="); + for (i, log) in logs.iter().enumerate() { + println!("{:3}. {}", i + 1, log); + } + } + + println!("============================================\n"); + + Ok((true, signature)) +} diff --git a/src/trading/core/params.rs b/src/trading/core/params.rs index e359024..61c74f2 100755 --- a/src/trading/core/params.rs +++ b/src/trading/core/params.rs @@ -40,6 +40,7 @@ pub struct SwapParams { pub close_output_mint_ata: bool, pub fixed_output_amount: Option, pub gas_fee_strategy: GasFeeStrategy, + pub simulate: bool, } impl std::fmt::Debug for SwapParams {