iota_grpc_client/api/execution/simulate.rs
1// Copyright (c) 2026 IOTA Stiftung
2// SPDX-License-Identifier: Apache-2.0
3
4//! High-level API for transaction simulation.
5
6use iota_grpc_types::v1::transaction_execution_service::{
7 SimulateTransactionItem, SimulateTransactionsRequest, SimulatedTransaction,
8 simulate_transaction_item::TransactionCheckModes,
9};
10use iota_sdk_types::Transaction;
11
12use crate::{
13 Client,
14 api::{
15 Error, MetadataEnvelope, ProtoResult, Result, SIMULATE_TRANSACTIONS_READ_MASK,
16 build_proto_transaction, field_mask_with_default,
17 },
18};
19
20/// A single transaction with simulation options for use in batch simulation.
21pub struct SimulateTransactionInput {
22 /// The transaction to simulate.
23 pub transaction: Transaction,
24 /// Set to true for relaxed Move VM checks (useful for debugging and
25 /// development).
26 pub skip_checks: bool,
27}
28
29impl Client {
30 /// Simulate a transaction without executing it.
31 ///
32 /// This allows you to preview the effects of a transaction before
33 /// actually submitting it to the network.
34 ///
35 /// # Parameters
36 ///
37 /// - `transaction`: The transaction to simulate
38 /// - `skip_checks`: Set to true for relaxed Move VM checks (useful for
39 /// debugging and development)
40 /// - `read_mask`: Optional field mask to control which fields are returned
41 ///
42 /// Returns [`SimulatedTransaction`] which contains:
43 /// - `executed_transaction()` - Access to the simulated ExecutedTransaction
44 /// - `command_results()` - Access to intermediate command execution results
45 ///
46 /// Use lazy conversion methods on the executed transaction to extract data:
47 /// - `result.executed_transaction()?.effects()` - Get simulated effects
48 /// - `result.executed_transaction()?.events()` - Get simulated events (if
49 /// available)
50 /// - `result.executed_transaction()?.input_objects()` - Get input objects
51 /// (if requested)
52 /// - `result.executed_transaction()?.output_objects()` - Get output objects
53 /// (if requested)
54 ///
55 /// # Available Read Mask Fields
56 ///
57 /// The optional `read_mask` parameter controls which fields the server
58 /// returns. If `None`, uses [`SIMULATE_TRANSACTIONS_READ_MASK`] which
59 /// includes effects, events, and input/output objects.
60 ///
61 /// ## Transaction Fields
62 /// - `executed_transaction` - includes all executed transaction fields
63 /// - `executed_transaction.transaction` - includes all transaction fields
64 /// - `executed_transaction.transaction.digest` - the transaction digest
65 /// - `executed_transaction.transaction.bcs` - the full BCS-encoded
66 /// transaction
67 /// - `executed_transaction.signatures` - includes all signature fields
68 /// - `executed_transaction.signatures.bcs` - the full BCS-encoded
69 /// signature
70 /// - `executed_transaction.effects` - includes all effects fields
71 /// - `executed_transaction.effects.digest` - the effects digest
72 /// - `executed_transaction.effects.bcs` - the full BCS-encoded effects
73 /// - `executed_transaction.events` - includes all event fields
74 /// - `executed_transaction.events.digest` - the events digest
75 /// - `executed_transaction.events.events` - includes all event fields
76 /// (all events of the transaction)
77 /// - `executed_transaction.events.events.bcs` - the full BCS-encoded
78 /// event
79 /// - `executed_transaction.events.events.package_id` - the ID of the
80 /// package that emitted the event
81 /// - `executed_transaction.events.events.module` - the module that
82 /// emitted the event
83 /// - `executed_transaction.events.events.sender` - the sender that
84 /// triggered the event
85 /// - `executed_transaction.events.events.event_type` - the type of
86 /// the event
87 /// - `executed_transaction.events.events.bcs_contents` - the full
88 /// BCS-encoded contents of the event
89 /// - `executed_transaction.events.events.json_contents` - the
90 /// JSON-encoded contents of the event
91 /// - `executed_transaction.checkpoint` - the checkpoint that included the
92 /// transaction (not available for just-executed transactions)
93 /// - `executed_transaction.timestamp` - the timestamp of the checkpoint
94 /// (not available for just-executed transactions)
95 /// - `executed_transaction.input_objects` - includes all input object
96 /// fields
97 /// - `executed_transaction.input_objects.reference` - includes all
98 /// reference fields
99 /// - `executed_transaction.input_objects.reference.object_id` - the
100 /// ID of the input object
101 /// - `executed_transaction.input_objects.reference.version` - the
102 /// version of the input object
103 /// - `executed_transaction.input_objects.reference.digest` - the
104 /// digest of the input object contents
105 /// - `executed_transaction.input_objects.bcs` - the full BCS-encoded
106 /// object
107 /// - `executed_transaction.output_objects` - includes all output object
108 /// fields
109 /// - `executed_transaction.output_objects.reference` - includes all
110 /// reference fields
111 /// - `executed_transaction.output_objects.reference.object_id` - the
112 /// ID of the output object
113 /// - `executed_transaction.output_objects.reference.version` - the
114 /// version of the output object
115 /// - `executed_transaction.output_objects.reference.digest` - the
116 /// digest of the output object contents
117 /// - `executed_transaction.output_objects.bcs` - the full BCS-encoded
118 /// object
119 ///
120 /// ## Gas Fields
121 /// - `suggested_gas_price` - the suggested gas price for the transaction,
122 /// denominated in NANOS
123 ///
124 /// ## Execution Result Fields
125 /// - `execution_result` - the execution result (oneof: command_results on
126 /// success, execution_error on failure)
127 /// - `execution_result.command_results` - includes all fields of
128 /// per-command results if execution succeeded
129 /// - `execution_result.command_results.mutated_by_ref` - includes all
130 /// fields of objects mutated by reference
131 /// - `execution_result.command_results.mutated_by_ref.argument` - the
132 /// argument reference
133 /// - `execution_result.command_results.mutated_by_ref.type_tag` - the
134 /// Move type tag
135 /// - `execution_result.command_results.mutated_by_ref.bcs` - the
136 /// BCS-encoded value
137 /// - `execution_result.command_results.mutated_by_ref.json` - the
138 /// JSON-encoded value
139 /// - `execution_result.command_results.return_values` - includes all
140 /// fields of return values returned by the command
141 /// - `execution_result.command_results.return_values.argument` - the
142 /// argument reference
143 /// - `execution_result.command_results.return_values.type_tag` - the
144 /// Move type tag
145 /// - `execution_result.command_results.return_values.bcs` - the
146 /// BCS-encoded value
147 /// - `execution_result.command_results.return_values.json` - the
148 /// JSON-encoded value
149 /// - `execution_result.execution_error` - includes all fields of the
150 /// execution error if execution failed
151 /// - `execution_result.execution_error.bcs_kind` - the BCS-encoded
152 /// error kind
153 /// - `execution_result.execution_error.source` - the error source
154 /// description
155 /// - `execution_result.execution_error.command_index` - the index of
156 /// the command that failed
157 ///
158 /// # Example
159 ///
160 /// ```no_run
161 /// # use iota_grpc_client::Client;
162 /// # use iota_sdk_types::Transaction;
163 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
164 /// let client = Client::connect("http://localhost:9000").await?;
165 ///
166 /// let tx: Transaction = todo!();
167 ///
168 /// // Simulate transaction - returns proto type
169 /// let result = client.simulate_transaction(tx, false, None).await?;
170 ///
171 /// // Lazy conversion - only deserialize what you need
172 /// let executed_tx = result.body().executed_transaction()?;
173 /// let effects = executed_tx.effects()?.effects()?;
174 /// println!("Simulation status: {:?}", effects.status());
175 ///
176 /// let output_objs = executed_tx.output_objects()?;
177 /// println!("Would create {} objects", output_objs.objects.len());
178 /// # Ok(())
179 /// # }
180 /// ```
181 pub async fn simulate_transaction(
182 &self,
183 transaction: Transaction,
184 skip_checks: bool,
185 read_mask: Option<&str>,
186 ) -> Result<MetadataEnvelope<SimulatedTransaction>> {
187 self.simulate_transactions(
188 vec![SimulateTransactionInput {
189 transaction,
190 skip_checks,
191 }],
192 read_mask,
193 )
194 .await?
195 .try_map(|results| {
196 results
197 .into_iter()
198 .next()
199 .ok_or_else(|| Error::Protocol("empty transaction_results".into()))?
200 })
201 }
202
203 /// Simulate a batch of transactions without executing them.
204 ///
205 /// Transactions are simulated sequentially on the server. Each transaction
206 /// is independent — failure of one does not abort the rest.
207 ///
208 /// Returns a `Vec<Result<SimulatedTransaction>>` in the same order as the
209 /// input. Each element is either the successfully simulated transaction or
210 /// the per-item error returned by the server.
211 ///
212 /// # Errors
213 ///
214 /// Returns [`Error::EmptyRequest`] if `transactions` is empty.
215 /// Returns a transport-level [`Error::Grpc`] if the entire RPC fails
216 /// (e.g. batch size exceeded).
217 pub async fn simulate_transactions(
218 &self,
219 transactions: Vec<SimulateTransactionInput>,
220 read_mask: Option<&str>,
221 ) -> Result<MetadataEnvelope<Vec<Result<SimulatedTransaction>>>> {
222 if transactions.is_empty() {
223 return Err(Error::EmptyRequest);
224 }
225
226 let items = transactions
227 .into_iter()
228 .map(|input| build_simulate_item(input.transaction, input.skip_checks))
229 .collect::<Result<Vec<_>>>()?;
230
231 let request = SimulateTransactionsRequest::default()
232 .with_transactions(items)
233 .with_read_mask(field_mask_with_default(
234 read_mask,
235 SIMULATE_TRANSACTIONS_READ_MASK,
236 ));
237
238 let response = self
239 .execution_service_client()
240 .simulate_transactions(request)
241 .await?;
242
243 MetadataEnvelope::from(response).try_map(|r| {
244 Ok(r.transaction_results
245 .into_iter()
246 .map(ProtoResult::into_result)
247 .collect())
248 })
249 }
250}
251
252/// Convert a transaction and options into a proto `SimulateTransactionItem`.
253fn build_simulate_item(
254 transaction: Transaction,
255 skip_checks: bool,
256) -> Result<SimulateTransactionItem> {
257 let proto_transaction = build_proto_transaction(&transaction, transaction.digest())?;
258
259 let tx_checks = if skip_checks {
260 vec![TransactionCheckModes::DisableVmChecks as i32]
261 } else {
262 vec![]
263 };
264
265 Ok(SimulateTransactionItem::default()
266 .with_transaction(proto_transaction)
267 .with_tx_checks(tx_checks))
268}