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}