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}