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}