jito_bundle/client/
simulate.rs1use crate::bundler::bundle::Bundle;
2use crate::client::jito_bundler::JitoBundler;
3use crate::error::JitoError;
4use crate::types::{
5 JsonRpcRequest, JsonRpcResponse, SimulateBundleApiResult, SimulateBundleParams,
6 SimulateBundleSummary, SimulateBundleValue,
7};
8use base64::Engine;
9use solana_client::rpc_config::RpcSimulateTransactionConfig;
10use solana_sdk::commitment_config::CommitmentConfig;
11
12impl JitoBundler {
13 pub async fn simulate_per_transaction(&self, bundle: &Bundle<'_>) -> Result<(), JitoError> {
14 for (i, tx) in bundle.versioned_transaction.iter().enumerate() {
15 let sig = bs58::encode(&tx.signatures[0]).into_string();
16 let config = RpcSimulateTransactionConfig {
17 sig_verify: true,
18 replace_recent_blockhash: false,
19 commitment: Some(CommitmentConfig::confirmed()),
20 accounts: None,
21 min_context_slot: None,
22 inner_instructions: false,
23 encoding: None,
24 };
25
26 match self
27 .rpc_client
28 .simulate_transaction_with_config(tx, config)
29 .await
30 {
31 Ok(result) => {
32 if let Some(err) = result.value.err {
33 let logs = result.value.logs.unwrap_or_default();
34 return Err(JitoError::SimulationFailed {
35 details: format!(
36 "transaction {i} simulation failed: {err:?}\nsignature: {sig}\nlogs: {logs:?}"
37 ),
38 });
39 }
40 }
41 Err(e) => {
42 return Err(JitoError::SimulationFailed {
43 details: format!(
44 "transaction {i} RPC simulation error: {e}\nsignature: {sig}"
45 ),
46 });
47 }
48 }
49 }
50
51 Ok(())
52 }
53
54 pub async fn simulate_bundle_helius(
55 &self,
56 bundle: &Bundle<'_>,
57 helius_rpc_url: &str,
58 ) -> Result<SimulateBundleValue, JitoError> {
59 let encoded_transactions: Vec<String> = bundle
60 .versioned_transaction
61 .iter()
62 .map(|tx| {
63 let serialized = bincode::serialize(tx).map_err(|e| JitoError::Serialization {
64 reason: e.to_string(),
65 })?;
66 Ok(base64::engine::general_purpose::STANDARD.encode(serialized))
67 })
68 .collect::<Result<Vec<String>, JitoError>>()?;
69 let params = SimulateBundleParams {
70 encoded_transactions,
71 };
72 let request = JsonRpcRequest {
73 jsonrpc: "2.0",
74 id: 1,
75 method: "simulateBundle",
76 params: [params],
77 };
78 let response = self
79 .http_client
80 .post(helius_rpc_url)
81 .header("Content-Type", "application/json")
82 .json(&request)
83 .send()
84 .await
85 .map_err(|e| JitoError::Network {
86 reason: format!("Helius simulateBundle: {e}"),
87 })?;
88 let status = response.status();
89 let response_text = response.text().await.map_err(|e| JitoError::Network {
90 reason: format!("failed to read Helius response: {e}"),
91 })?;
92 if !status.is_success() {
93 return Err(JitoError::Network {
94 reason: format!("Helius simulateBundle HTTP {status}: {response_text}"),
95 });
96 }
97 let parsed: JsonRpcResponse<SimulateBundleApiResult> = serde_json::from_str(&response_text)
98 .map_err(|e| JitoError::Serialization {
99 reason: format!(
100 "failed to parse Helius simulateBundle response: {e}, body: {response_text}"
101 ),
102 })?;
103 if let Some(error) = parsed.error {
104 return Err(JitoError::JsonRpc {
105 code: error.code,
106 message: error.message,
107 });
108 }
109
110 let result = parsed.result.ok_or_else(|| JitoError::JsonRpc {
111 code: -1,
112 message: "no result in Helius simulateBundle response".to_string(),
113 })?;
114
115 if let SimulateBundleSummary::Failed(failure) = &result.value.summary {
116 let tx_count = bundle.versioned_transaction.len();
117 let results_count = result.value.transaction_results.len();
118 let failed_tx_index = if results_count < tx_count {
119 results_count
120 } else {
121 result
122 .value
123 .transaction_results
124 .iter()
125 .position(|r| r.err.is_some())
126 .unwrap_or(0)
127 };
128 let mut error_details = format!(
129 "bundle simulation failed at transaction {failed_tx_index}: {}\n\
130 failing tx signature: {:?}\n\
131 bundle size: {tx_count} transactions, results returned: {results_count}",
132 failure.error_message(),
133 failure.tx_signature
134 );
135
136 if results_count < tx_count {
137 error_details.push_str(&format!(
138 "\nHelius stopped after tx {failed_tx_index} failed — no logs for subsequent transactions"
139 ));
140 }
141
142 for (idx, tx_result) in result.value.transaction_results.iter().enumerate() {
143 let status_str = if tx_result.err.is_some() {
144 "FAILED"
145 } else {
146 "OK"
147 };
148 let units = tx_result
149 .units_consumed
150 .map_or_else(|| "N/A".to_string(), |u| u.to_string());
151
152 error_details.push_str(&format!("\n\n=== transaction {idx} [{status_str}] ==="));
153 error_details.push_str(&format!("\ncompute units: {units}"));
154
155 if let Some(err) = &tx_result.err {
156 error_details.push_str(&format!("\nerror: {err}"));
157 }
158
159 if let Some(logs) = &tx_result.logs {
160 error_details.push_str("\nlogs:");
161 for log in logs {
162 error_details.push_str(&format!("\n {log}"));
163 }
164 } else {
165 error_details.push_str("\nlogs: none");
166 }
167 }
168
169 return Err(JitoError::SimulationFailed {
170 details: error_details,
171 });
172 }
173
174 Ok(result.value)
175 }
176}