Skip to main content

jito_bundle/client/
simulate.rs

1use crate::bundler::bundle::types::BuiltBundle;
2use crate::client::jito_bundler::JitoBundler;
3use crate::error::JitoError;
4use crate::types::{
5    JsonRpcRequest, SimulateBundleApiResult, SimulateBundleParams, SimulateBundleSummary,
6    SimulateBundleValue,
7};
8use solana_client::rpc_config::RpcSimulateTransactionConfig;
9use solana_sdk::commitment_config::CommitmentConfig;
10
11impl JitoBundler {
12    // --- Simulation ---
13    /// Simulates each transaction individually via standard Solana RPC.
14    pub async fn simulate_per_transaction(&self, bundle: &BuiltBundle) -> Result<(), JitoError> {
15        for (i, tx) in bundle.transactions.iter().enumerate() {
16            let sig = bs58::encode(&tx.signatures[0]).into_string();
17            let config = RpcSimulateTransactionConfig {
18                sig_verify: true,
19                replace_recent_blockhash: false,
20                commitment: Some(CommitmentConfig::confirmed()),
21                accounts: None,
22                min_context_slot: None,
23                inner_instructions: false,
24                encoding: None,
25            };
26
27            match self
28                .rpc_client
29                .simulate_transaction_with_config(tx, config)
30                .await
31            {
32                Ok(result) => {
33                    if let Some(err) = result.value.err {
34                        let logs = result.value.logs.unwrap_or_default();
35                        return Err(JitoError::SimulationFailed {
36                            details: format!(
37                                "transaction {i} simulation failed: {err:?}\nsignature: {sig}\nlogs: {logs:?}"
38                            ),
39                        });
40                    }
41                }
42                Err(e) => {
43                    return Err(JitoError::SimulationFailed {
44                        details: format!(
45                            "transaction {i} RPC simulation error: {e}\nsignature: {sig}"
46                        ),
47                    });
48                }
49            }
50        }
51
52        Ok(())
53    }
54
55    /// Simulates the full atomic bundle through Helius `simulateBundle`.
56    pub async fn simulate_bundle_helius(
57        &self,
58        bundle: &BuiltBundle,
59        helius_rpc_url: &str,
60    ) -> Result<SimulateBundleValue, JitoError> {
61        let encoded_transactions = Self::encode_transactions_base64(&bundle.transactions)?;
62        let params = SimulateBundleParams {
63            encoded_transactions,
64        };
65        let request = JsonRpcRequest {
66            jsonrpc: "2.0",
67            id: 1,
68            method: "simulateBundle",
69            params: [params],
70        };
71        let (status, response_text) = self
72            .send_json_rpc_request(
73                self.http_client
74                    .post(helius_rpc_url)
75                    .header("Content-Type", "application/json"),
76                &request,
77                "Helius simulateBundle",
78            )
79            .await?;
80        if !status.is_success() {
81            return Err(JitoError::Network {
82                reason: format!("Helius simulateBundle HTTP {status}: {response_text}"),
83            });
84        }
85        let result: SimulateBundleApiResult = Self::parse_json_rpc_result(
86            &response_text,
87            "Helius simulateBundle",
88            "no result in Helius simulateBundle response",
89        )?;
90
91        if let SimulateBundleSummary::Failed(failure) = &result.value.summary {
92            let tx_count = bundle.transactions.len();
93            let results_count = result.value.transaction_results.len();
94            let failed_tx_index = if results_count < tx_count {
95                results_count
96            } else {
97                result
98                    .value
99                    .transaction_results
100                    .iter()
101                    .position(|r| r.err.is_some())
102                    .unwrap_or(0)
103            };
104            let mut error_details = format!(
105                "bundle simulation failed at transaction {failed_tx_index}: {}\n\
106                 failing tx signature: {:?}\n\
107                 bundle size: {tx_count} transactions, results returned: {results_count}",
108                failure.error_message(),
109                failure.tx_signature
110            );
111
112            if results_count < tx_count {
113                error_details.push_str(&format!(
114                    "\nHelius stopped after tx {failed_tx_index} failed - no logs for subsequent transactions"
115                ));
116            }
117            for (idx, tx_result) in result.value.transaction_results.iter().enumerate() {
118                let status_str = if tx_result.err.is_some() {
119                    "FAILED"
120                } else {
121                    "OK"
122                };
123                let units = tx_result
124                    .units_consumed
125                    .map_or_else(|| "N/A".to_string(), |u| u.to_string());
126                error_details.push_str(&format!("\n\n=== transaction {idx} [{status_str}] ==="));
127                error_details.push_str(&format!("\ncompute units: {units}"));
128                if let Some(err) = &tx_result.err {
129                    error_details.push_str(&format!("\nerror: {err}"));
130                }
131                if let Some(logs) = &tx_result.logs {
132                    error_details.push_str("\nlogs:");
133                    for log in logs {
134                        error_details.push_str(&format!("\n  {log}"));
135                    }
136                } else {
137                    error_details.push_str("\nlogs: none");
138                }
139            }
140            return Err(JitoError::SimulationFailed {
141                details: error_details,
142            });
143        }
144        Ok(result.value)
145    }
146}