iota_grpc_client/api/execution/
execute.rs

1// Copyright (c) 2026 IOTA Stiftung
2// SPDX-License-Identifier: Apache-2.0
3
4//! High-level API for transaction execution.
5
6use iota_grpc_types::v1::{
7    signatures::{UserSignature as ProtoUserSignature, UserSignatures},
8    transaction::ExecutedTransaction,
9    transaction_execution_service::{ExecuteTransactionItem, ExecuteTransactionsRequest},
10};
11use iota_sdk_types::SignedTransaction;
12
13use crate::{
14    Client,
15    api::{
16        EXECUTE_TRANSACTIONS_READ_MASK, Error, MetadataEnvelope, ProtoResult, Result,
17        build_proto_transaction, field_mask_with_default,
18    },
19};
20
21impl Client {
22    /// Execute a signed transaction.
23    ///
24    /// This submits the transaction to the network for execution and waits for
25    /// the result. The transaction must be signed with valid signatures.
26    ///
27    /// Returns proto `ExecutedTransaction`. Use lazy conversion methods to
28    /// extract data:
29    /// - `result.effects()` - Get transaction effects
30    /// - `result.events()` - Get transaction events (if available)
31    /// - `result.input_objects()` - Get input objects (if requested)
32    /// - `result.output_objects()` - Get output objects (if requested)
33    ///
34    /// # Available Read Mask Fields
35    ///
36    /// The optional `read_mask` parameter controls which fields the server
37    /// returns. If `None`, uses [`EXECUTE_TRANSACTIONS_READ_MASK`] which
38    /// includes effects, events, and input/output objects.
39    ///
40    /// ## Transaction Fields
41    /// - `transaction` - includes all transaction fields
42    ///   - `transaction.digest` - the transaction digest
43    ///   - `transaction.bcs` - the full BCS-encoded transaction
44    /// - `signatures` - includes all signature fields
45    ///   - `signatures.bcs` - the full BCS-encoded signature
46    /// - `effects` - includes all effects fields
47    ///   - `effects.digest` - the effects digest
48    ///   - `effects.bcs` - the full BCS-encoded effects
49    /// - `checkpoint` - the checkpoint that included the transaction. Requires
50    ///   `checkpoint_inclusion_timeout_ms` to be set.
51    /// - `timestamp` - the timestamp of the checkpoint. Requires
52    ///   `checkpoint_inclusion_timeout_ms` to be set.
53    ///
54    /// ## Event Fields
55    /// - `events` - includes all event fields (all events of the transaction)
56    ///   - `events.digest` - the events digest
57    ///   - `events.events.bcs` - the full BCS-encoded event
58    ///   - `events.events.package_id` - the ID of the package that emitted the
59    ///     event
60    ///   - `events.events.module` - the module that emitted the event
61    ///   - `events.events.sender` - the sender that triggered the event
62    ///   - `events.events.event_type` - the type of the event
63    ///   - `events.events.bcs_contents` - the full BCS-encoded contents of the
64    ///     event
65    ///   - `events.events.json_contents` - the JSON-encoded contents of the
66    ///     event
67    ///
68    /// ## Object Fields
69    /// - `input_objects` - includes all input object fields
70    ///   - `input_objects.reference` - includes all reference fields
71    ///     - `input_objects.reference.object_id` - the ID of the input object
72    ///     - `input_objects.reference.version` - the version of the input
73    ///       object
74    ///     - `input_objects.reference.digest` - the digest of the input object
75    ///       contents
76    ///   - `input_objects.bcs` - the full BCS-encoded object
77    /// - `output_objects` - includes all output object fields
78    ///   - `output_objects.reference` - includes all reference fields
79    ///     - `output_objects.reference.object_id` - the ID of the output object
80    ///     - `output_objects.reference.version` - the version of the output
81    ///       object
82    ///     - `output_objects.reference.digest` - the digest of the output
83    ///       object contents
84    ///   - `output_objects.bcs` - the full BCS-encoded object
85    ///
86    /// # Checkpoint Inclusion
87    ///
88    /// If `checkpoint_inclusion_timeout_ms` is set, the server will wait up to
89    /// the specified duration (in milliseconds) for the transaction to be
90    /// included in a checkpoint before returning. When set, include
91    /// `checkpoint` and `timestamp` in the `read_mask` to receive the data.
92    ///
93    /// # Example
94    ///
95    /// ```no_run
96    /// # use iota_grpc_client::Client;
97    /// # use iota_sdk_types::SignedTransaction;
98    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
99    /// let client = Client::connect("http://localhost:9000").await?;
100    ///
101    /// let signed_tx: SignedTransaction = todo!();
102    ///
103    /// // Execute transaction - returns proto type
104    /// let result = client.execute_transaction(signed_tx, None, None).await?;
105    ///
106    /// // Lazy conversion - only deserialize what you need
107    /// let effects = result.body().effects()?.effects()?;
108    /// println!("Status: {:?}", effects.status());
109    ///
110    /// let events = result.body().events()?.events()?;
111    /// if !events.0.is_empty() {
112    ///     println!("Events: {}", events.0.len());
113    /// }
114    /// # Ok(())
115    /// # }
116    /// ```
117    pub async fn execute_transaction(
118        &self,
119        signed_transaction: SignedTransaction,
120        read_mask: Option<&str>,
121        checkpoint_inclusion_timeout_ms: Option<u64>,
122    ) -> Result<MetadataEnvelope<ExecutedTransaction>> {
123        self.execute_transactions(
124            vec![signed_transaction],
125            read_mask,
126            checkpoint_inclusion_timeout_ms,
127        )
128        .await?
129        .try_map(|results| {
130            results
131                .into_iter()
132                .next()
133                .ok_or_else(|| Error::Protocol("empty transaction_results".into()))?
134        })
135    }
136
137    /// Execute a batch of signed transactions.
138    ///
139    /// Transactions are executed sequentially on the server. Each transaction
140    /// is independent — failure of one does not abort the rest.
141    ///
142    /// Returns a `Vec<Result<ExecutedTransaction>>` in the same order as the
143    /// input. Each element is either the successfully executed transaction or
144    /// the per-item error returned by the server.
145    ///
146    /// # Available Read Mask Fields
147    ///
148    /// The optional `read_mask` parameter controls which fields the server
149    /// returns for each `ExecutedTransaction`. If `None`, uses
150    /// [`EXECUTE_TRANSACTIONS_READ_MASK`] which includes effects, events, and
151    /// input/output objects.
152    ///
153    /// See [`execute_transaction`](Self::execute_transaction) for the full list
154    /// of supported read mask fields.
155    ///
156    /// # Checkpoint Inclusion
157    ///
158    /// If `checkpoint_inclusion_timeout_ms` is set, the server will wait up to
159    /// the specified duration (in milliseconds) for all executed transactions
160    /// to be included in a checkpoint before returning. When set, include
161    /// `checkpoint` and `timestamp` in the `read_mask` to receive the data.
162    ///
163    /// # Errors
164    ///
165    /// Returns [`Error::EmptyRequest`] if `transactions` is empty.
166    /// Returns a transport-level [`Error::Grpc`] if the entire RPC fails
167    /// (e.g. batch size exceeded).
168    pub async fn execute_transactions(
169        &self,
170        transactions: Vec<SignedTransaction>,
171        read_mask: Option<&str>,
172        checkpoint_inclusion_timeout_ms: Option<u64>,
173    ) -> Result<MetadataEnvelope<Vec<Result<ExecutedTransaction>>>> {
174        if transactions.is_empty() {
175            return Err(Error::EmptyRequest);
176        }
177
178        let items = transactions
179            .into_iter()
180            .map(build_execute_item)
181            .collect::<Result<Vec<_>>>()?;
182
183        let mut request = ExecuteTransactionsRequest::default()
184            .with_transactions(items)
185            .with_read_mask(field_mask_with_default(
186                read_mask,
187                EXECUTE_TRANSACTIONS_READ_MASK,
188            ));
189
190        if let Some(timeout_ms) = checkpoint_inclusion_timeout_ms {
191            request = request.with_checkpoint_inclusion_timeout_ms(timeout_ms);
192        }
193
194        let response = self
195            .execution_service_client()
196            .execute_transactions(request)
197            .await?;
198
199        MetadataEnvelope::from(response).try_map(|r| {
200            Ok(r.transaction_results
201                .into_iter()
202                .map(ProtoResult::into_result)
203                .collect())
204        })
205    }
206}
207
208/// Convert a `SignedTransaction` into a proto `ExecuteTransactionItem`.
209fn build_execute_item(signed_transaction: SignedTransaction) -> Result<ExecuteTransactionItem> {
210    let tx_digest = signed_transaction.transaction.digest();
211    let proto_transaction = build_proto_transaction(&signed_transaction.transaction, tx_digest)?;
212
213    let proto_signatures = UserSignatures::default().with_signatures(
214        signed_transaction
215            .signatures
216            .into_iter()
217            .map(|sig| {
218                ProtoUserSignature::try_from(sig).map_err(|e| Error::Signature(e.to_string()))
219            })
220            .collect::<Result<Vec<_>>>()?,
221    );
222
223    Ok(ExecuteTransactionItem::default()
224        .with_transaction(proto_transaction)
225        .with_signatures(proto_signatures))
226}