Skip to main content

iota_sdk_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::{
7    read_mask_fields::{IntoReadMask, SimulateReadMask},
8    v1::transaction_execution_service::{
9        SimulateTransactionItem, SimulateTransactionsRequest, SimulatedTransaction,
10        simulate_transaction_item::TransactionCheckModes,
11    },
12};
13use iota_types::Transaction;
14
15use crate::{
16    Client,
17    api::{
18        Error, MetadataEnvelope, ProtocolError, Result, build_proto_transaction, into_item_results,
19    },
20};
21
22/// A single transaction with simulation options for use in batch simulation.
23pub struct SimulateTransactionInput {
24    /// The transaction to simulate.
25    pub transaction: Transaction,
26    /// Set to true for relaxed Move VM checks (useful for debugging and
27    /// development).
28    pub skip_checks: bool,
29}
30
31impl Client {
32    /// Simulate a transaction without executing it.
33    ///
34    /// This allows you to preview the effects of a transaction before
35    /// actually submitting it to the network.
36    ///
37    /// # Parameters
38    ///
39    /// - `transaction`: The transaction to simulate
40    /// - `skip_checks`: Set to true for relaxed Move VM checks (useful for
41    ///   debugging and development)
42    ///
43    /// Returns [`SimulatedTransaction`] which contains:
44    /// - `executed_transaction()` - Access to the simulated ExecutedTransaction
45    /// - `command_results()` - Access to intermediate command execution results
46    ///
47    /// Use lazy conversion methods on the executed transaction to extract data:
48    /// - `result.executed_transaction()?.effects()` - Get simulated effects
49    /// - `result.executed_transaction()?.events()` - Get simulated events (if
50    ///   available)
51    /// - `result.executed_transaction()?.input_objects()` - Get input objects
52    ///   (if requested)
53    /// - `result.executed_transaction()?.output_objects()` - Get output objects
54    ///   (if requested)
55    /// - `result.executed_transaction()?.balance_changes()` - Get balance
56    ///   changes (if requested)
57    /// - `result.executed_transaction()?.object_changes()` - Get object changes
58    ///   (if requested)
59    ///
60    /// # Example
61    ///
62    /// ```no_run
63    /// # use iota_sdk_grpc_client::Client;
64    /// # use iota_sdk_grpc_client::read_mask_fields::SimulateReadMask;
65    /// # use iota_types::Transaction;
66    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
67    /// let client = Client::new_localnet()?;
68    ///
69    /// let tx: Transaction = todo!();
70    /// let result = client
71    ///     .simulate_transaction(tx, false, SimulateReadMask::default())
72    ///     .await?;
73    ///
74    /// let executed_tx = result.body().executed_transaction()?;
75    /// let effects = executed_tx.effects()?.effects()?;
76    /// println!("Simulation status: {:?}", effects.as_v1().status);
77    ///
78    /// let output_objs = executed_tx.output_objects()?;
79    /// println!("Would create {} objects", output_objs.objects.len());
80    /// # Ok(())
81    /// # }
82    /// ```
83    ///
84    /// The `read_mask` controls which fields the server returns; use
85    /// `SimulateReadMask::default()` for the default mask. Pass a
86    /// [`SimulateField`](iota_grpc_types::read_mask_fields::SimulateField) or
87    /// any slice/array/vec of fields — conversion is automatic.
88    pub async fn simulate_transaction(
89        &self,
90        transaction: Transaction,
91        skip_checks: bool,
92        read_mask: impl IntoReadMask<SimulateReadMask>,
93    ) -> Result<MetadataEnvelope<SimulatedTransaction>> {
94        self.simulate_transactions(
95            vec![SimulateTransactionInput {
96                transaction,
97                skip_checks,
98            }],
99            read_mask,
100        )
101        .await?
102        .try_map(extract_single_simulation_result)
103    }
104
105    /// Simulate a batch of transactions without executing them.
106    ///
107    /// Transactions are simulated sequentially on the server. Each transaction
108    /// is independent — failure of one does not abort the rest.
109    ///
110    /// Returns a `Vec<Result<SimulatedTransaction>>` in the same order as the
111    /// input. Each element is either the successfully simulated transaction or
112    /// the per-item error returned by the server.
113    ///
114    /// The `read_mask` controls which fields the server returns for each
115    /// `SimulatedTransaction`; use `SimulateReadMask::default()` for the
116    /// default mask. Pass a
117    /// [`SimulateField`](iota_grpc_types::read_mask_fields::SimulateField) or
118    /// any slice/array/vec of fields — conversion is automatic.
119    ///
120    /// # Errors
121    ///
122    /// Returns [`Error::EmptyRequest`] if `transactions` is empty.
123    /// Returns a transport-level [`Error::Grpc`] if the entire RPC fails
124    /// (e.g. batch size exceeded).
125    pub async fn simulate_transactions(
126        &self,
127        transactions: Vec<SimulateTransactionInput>,
128        read_mask: impl IntoReadMask<SimulateReadMask>,
129    ) -> Result<MetadataEnvelope<Vec<Result<SimulatedTransaction>>>> {
130        let read_mask = read_mask.into_read_mask();
131        if transactions.is_empty() {
132            return Err(Error::EmptyRequest);
133        }
134
135        let items = transactions
136            .into_iter()
137            .map(|input| build_simulate_item(input.transaction, input.skip_checks))
138            .collect::<Result<Vec<_>>>()?;
139
140        let request = SimulateTransactionsRequest::default()
141            .with_transactions(items)
142            .with_read_mask(read_mask);
143
144        let response = self
145            .execution_service_client()
146            .simulate_transactions(request)
147            .await?;
148
149        Ok(MetadataEnvelope::from(response).map(|r| into_item_results(r.transaction_results)))
150    }
151}
152
153fn extract_single_simulation_result(
154    results: Vec<Result<SimulatedTransaction>>,
155) -> Result<SimulatedTransaction> {
156    results
157        .into_iter()
158        .next()
159        .ok_or_else(|| Error::Protocol(ProtocolError::EmptyResponseField("transaction_results")))?
160}
161
162/// Convert a transaction and options into a proto `SimulateTransactionItem`.
163fn build_simulate_item(
164    transaction: Transaction,
165    skip_checks: bool,
166) -> Result<SimulateTransactionItem> {
167    let proto_transaction = build_proto_transaction(&transaction, transaction.digest())?;
168
169    let tx_checks = if skip_checks {
170        vec![TransactionCheckModes::DisableVmChecks as i32]
171    } else {
172        vec![]
173    };
174
175    Ok(SimulateTransactionItem::default()
176        .with_transaction(proto_transaction)
177        .with_tx_checks(tx_checks))
178}