iota_grpc_client/api/ledger/transactions.rs
1// Copyright (c) 2026 IOTA Stiftung
2// SPDX-License-Identifier: Apache-2.0
3
4//! High-level API for transaction queries.
5
6use iota_grpc_types::v1::{
7 ledger_service::{GetTransactionsRequest, TransactionRequest, TransactionRequests},
8 transaction::ExecutedTransaction,
9};
10use iota_sdk_types::Digest;
11
12use crate::{
13 Client,
14 api::{
15 Error, GET_TRANSACTIONS_READ_MASK, MetadataEnvelope, ProtoResult, Result, collect_stream,
16 field_mask_with_default, saturating_usize_to_u32,
17 },
18};
19
20impl Client {
21 /// Get transactions by their digests.
22 ///
23 /// Returns proto `ExecutedTransaction` for each transaction. Use the lazy
24 /// conversion methods to extract data:
25 /// - `tx.digest()` - Get transaction digest
26 /// - `tx.transaction()` - Deserialize transaction
27 /// - `tx.signatures()` - Deserialize signatures
28 /// - `tx.effects()` - Deserialize effects
29 /// - `tx.events()` - Deserialize events (if available)
30 /// - `tx.checkpoint_sequence_number()` - Get checkpoint number
31 /// - `tx.timestamp_ms()` - Get timestamp
32 ///
33 /// Results are returned in the same order as the input digests.
34 /// If a transaction is not found, an error is returned.
35 ///
36 /// # Errors
37 ///
38 /// Returns [`Error::EmptyRequest`] if `digests` is empty.
39 ///
40 /// # Available Read Mask Fields
41 ///
42 /// The optional `read_mask` parameter controls which fields the server
43 /// returns. If `None`, uses [`GET_TRANSACTIONS_READ_MASK`].
44 ///
45 /// ## Transaction Fields
46 /// - `transaction` - includes all transaction fields
47 /// - `transaction.digest` - the transaction digest
48 /// - `transaction.bcs` - the full BCS-encoded transaction
49 /// - `signatures` - includes all signature fields
50 /// - `signatures.bcs` - the full BCS-encoded signature
51 /// - `effects` - includes all effects fields
52 /// - `effects.digest` - the effects digest
53 /// - `effects.bcs` - the full BCS-encoded effects
54 ///
55 /// ## Event Fields
56 /// - `events` - includes all event fields
57 /// - `events.digest` - the events digest
58 /// - `events.events` - includes all event fields
59 /// - `events.events.bcs` - the full BCS-encoded event
60 /// - `events.events.package_id` - the ID of the package that emitted
61 /// the event
62 /// - `events.events.module` - the module that emitted the event
63 /// - `events.events.sender` - the sender that triggered the event
64 /// - `events.events.event_type` - the type of the event
65 /// - `events.events.bcs_contents` - the full BCS-encoded contents of
66 /// the event
67 /// - `events.events.json_contents` - the JSON-encoded contents of the
68 /// event
69 ///
70 /// ## Timing Fields
71 /// - `checkpoint` - the checkpoint that included the transaction
72 /// - `timestamp` - the timestamp of the checkpoint that included the
73 /// transaction
74 ///
75 /// ## Object Fields
76 /// - `input_objects` - includes all input object fields
77 /// - `input_objects.reference` - includes all reference fields
78 /// - `input_objects.reference.object_id` - the ID of the input object
79 /// - `input_objects.reference.version` - the version of the input
80 /// object, which can be used to fetch a specific historical version
81 /// or the latest version if not provided
82 /// - `input_objects.reference.digest` - the digest of the input object
83 /// contents, which can be used for integrity verification
84 /// - `input_objects.bcs` - the full BCS-encoded object
85 /// - `output_objects` - includes all output object fields
86 /// - `output_objects.reference` - includes all reference fields
87 /// - `output_objects.reference.object_id` - the ID of the output object
88 /// - `output_objects.reference.version` - the version of the output
89 /// object, which can be used to fetch a specific historical version
90 /// or the latest version if not provided
91 /// - `output_objects.reference.digest` - the digest of the output
92 /// object contents, which can be used for integrity verification
93 /// - `output_objects.bcs` - the full BCS-encoded object
94 ///
95 /// # Example
96 ///
97 /// ```no_run
98 /// # use iota_grpc_client::Client;
99 /// # use iota_sdk_types::Digest;
100 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
101 /// let client = Client::connect("http://localhost:9000").await?;
102 /// let digest: Digest = todo!();
103 ///
104 /// // Get transactions - returns proto types
105 /// let txs = client.get_transactions(&[digest], None).await?;
106 ///
107 /// for tx in txs.body() {
108 /// // Lazy conversion - only deserialize what you need
109 /// let effects = tx.effects()?.effects()?;
110 /// println!("Status: {:?}", effects.status());
111 ///
112 /// // Access checkpoint number
113 /// let checkpoint = tx.checkpoint_sequence_number()?;
114 /// println!("Checkpoint: {}", checkpoint);
115 /// }
116 /// # Ok(())
117 /// # }
118 /// ```
119 pub async fn get_transactions(
120 &self,
121 digests: &[Digest],
122 read_mask: Option<&str>,
123 ) -> Result<MetadataEnvelope<Vec<ExecutedTransaction>>> {
124 if digests.is_empty() {
125 return Err(Error::EmptyRequest);
126 }
127
128 let requests = TransactionRequests::default().with_requests(
129 digests
130 .iter()
131 .map(|d| TransactionRequest::default().with_digest(*d))
132 .collect(),
133 );
134
135 let mut request = GetTransactionsRequest::default()
136 .with_requests(requests)
137 .with_read_mask(field_mask_with_default(
138 read_mask,
139 GET_TRANSACTIONS_READ_MASK,
140 ));
141
142 if let Some(max_size) = self.max_decoding_message_size() {
143 request = request.with_max_message_size_bytes(saturating_usize_to_u32(max_size));
144 }
145
146 let mut client = self.ledger_service_client();
147
148 let response = client.get_transactions(request).await?;
149 let (stream, metadata) = MetadataEnvelope::from(response).into_parts();
150
151 // Server guarantees results are returned in request order
152 collect_stream(stream, metadata, |msg| {
153 let items = msg
154 .transaction_results
155 .into_iter()
156 .map(|r| r.into_result())
157 .collect::<Result<Vec<_>>>()?;
158 Ok((msg.has_next, items))
159 })
160 .await
161 }
162}