feat: add transaction simulation support and close_mint_token_ata parameter
- Add `simulate` parameter to TradeBuyParams and TradeSellParams for transaction simulation - Add `close_mint_token_ata` parameter to TradeSellParams for closing mint token ATA - Implement simulate_transaction function to validate transactions without blockchain submission - Update all examples to include new parameters (simulate: false, close_mint_token_ata: false) - Add comprehensive documentation for transaction simulation feature - Show detailed simulation output including logs, compute units, and errors - Print timing metrics in English for better international compatibility
This commit is contained in:
@@ -38,6 +38,7 @@ The `TradeBuyParams` struct contains all parameters required for executing buy o
|
||||
| `durable_nonce` | `Option<DurableNonceInfo>` | ❌ | Durable nonce information containing nonce account and current nonce value |
|
||||
| `fixed_output_token_amount` | `Option<u64>` | ❌ | 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<DurableNonceInfo>` | ❌ | 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<u64>` | ❌ | 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
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
| `durable_nonce` | `Option<DurableNonceInfo>` | ❌ | 持久 nonce 信息,包含 nonce 账户和当前 nonce 值 |
|
||||
| `fixed_output_token_amount` | `Option<u64>` | ❌ | 可选的固定输出代币数量。如果设置,此值将直接分配给输出数量而不是通过计算得出(Meteora DAMM V2 必需) |
|
||||
| `gas_fee_strategy` | `GasFeeStrategy` | ✅ | Gas fee 策略实例,用于控制交易费用和优先级 |
|
||||
| `simulate` | `bool` | ✅ | 是否模拟交易而不实际执行。当为 true 时,将通过 RPC 模拟交易以验证并显示详细日志、计算单元消耗和潜在错误,而不会实际提交到区块链 |
|
||||
|
||||
|
||||
## TradeSellParams
|
||||
@@ -69,6 +70,7 @@
|
||||
| `durable_nonce` | `Option<DurableNonceInfo>` | ❌ | 持久 nonce 信息,包含 nonce 账户和当前 nonce 值 |
|
||||
| `gas_fee_strategy` | `GasFeeStrategy` | ✅ | Gas fee 策略实例,用于控制交易费用和优先级 |
|
||||
| `fixed_output_token_amount` | `Option<u64>` | ❌ | 可选的固定输出代币数量。如果设置,此值将直接分配给输出数量而不是通过计算得出(Meteora DAMM V2 必需) |
|
||||
| `simulate` | `bool` | ✅ | 是否模拟交易而不实际执行。当为 true 时,将通过 RPC 模拟交易以验证并显示详细日志、计算单元消耗和潜在错误,而不会实际提交到区块链 |
|
||||
|
||||
|
||||
## 参数分类
|
||||
@@ -158,3 +160,20 @@
|
||||
- **Meteora DAMM V2**: `MeteoraDammV2Params`
|
||||
|
||||
请参阅相应的协议文档了解详细的参数规格。
|
||||
|
||||
### 🔍 交易模拟
|
||||
|
||||
当 `simulate: true` 时:
|
||||
- **不提交区块链**: 交易不会实际提交到区块链
|
||||
- **验证功能**: 验证交易构建和执行,而不会消耗实际代币
|
||||
- **详细输出**: 显示全面的信息,包括:
|
||||
- 带有详细执行步骤的交易日志
|
||||
- 计算单元消耗(用于优化 CU 预算)
|
||||
- 潜在错误和失败原因
|
||||
- 用于调试的内部指令
|
||||
- **使用场景**:
|
||||
- 在真实执行前测试交易逻辑
|
||||
- 调试失败的交易
|
||||
- 估算计算单元消耗
|
||||
- 验证交易参数
|
||||
- 📝 **注意**: 模拟使用 RPC 的 `simulateTransaction` 方法,采用 processed 承诺级别
|
||||
|
||||
@@ -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?;
|
||||
|
||||
|
||||
@@ -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?;
|
||||
|
||||
|
||||
@@ -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?;
|
||||
|
||||
|
||||
@@ -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)) => {
|
||||
|
||||
@@ -46,7 +46,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
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<dyn std::error::Error>> {
|
||||
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?;
|
||||
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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?;
|
||||
|
||||
|
||||
@@ -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?;
|
||||
|
||||
|
||||
@@ -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?;
|
||||
|
||||
|
||||
@@ -47,6 +47,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
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<dyn std::error::Error>> {
|
||||
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?;
|
||||
|
||||
|
||||
@@ -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?;
|
||||
|
||||
|
||||
@@ -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?;
|
||||
|
||||
|
||||
@@ -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?;
|
||||
|
||||
|
||||
@@ -46,6 +46,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
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<dyn std::error::Error>> {
|
||||
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?;
|
||||
|
||||
|
||||
@@ -114,6 +114,8 @@ pub struct TradeBuyParams {
|
||||
pub fixed_output_token_amount: Option<u64>,
|
||||
/// 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<u64>,
|
||||
/// 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
|
||||
|
||||
+213
-26
@@ -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<dyn InstructionBuilder>,
|
||||
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<Arc<SolanaRpcClient>>,
|
||||
payer: Arc<Keypair>,
|
||||
instructions: Vec<Instruction>,
|
||||
address_lookup_table_account: Option<AddressLookupTableAccount>,
|
||||
recent_blockhash: Option<Hash>,
|
||||
durable_nonce: Option<DurableNonceInfo>,
|
||||
data_size_limit: u32,
|
||||
middleware_manager: Option<Arc<MiddlewareManager>>,
|
||||
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))
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ pub struct SwapParams {
|
||||
pub close_output_mint_ata: bool,
|
||||
pub fixed_output_amount: Option<u64>,
|
||||
pub gas_fee_strategy: GasFeeStrategy,
|
||||
pub simulate: bool,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for SwapParams {
|
||||
|
||||
Reference in New Issue
Block a user