iota_sdk/apis/read.rs
1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use std::{collections::BTreeMap, sync::Arc};
6
7use fastcrypto::encoding::Base64;
8use futures::{StreamExt, stream};
9use futures_core::Stream;
10use iota_json_rpc_api::{
11 GovernanceReadApiClient, IndexerApiClient, MoveUtilsClient, ReadApiClient, WriteApiClient,
12};
13use iota_json_rpc_types::{
14 Checkpoint, CheckpointId, CheckpointPage, DevInspectArgs, DevInspectResults,
15 DryRunTransactionBlockResponse, DynamicFieldPage, IotaData, IotaGetPastObjectRequest,
16 IotaMoveNormalizedModule, IotaObjectDataOptions, IotaObjectResponse, IotaObjectResponseQuery,
17 IotaPastObjectResponse, IotaTransactionBlockEffects, IotaTransactionBlockResponse,
18 IotaTransactionBlockResponseOptions, IotaTransactionBlockResponseQuery, ObjectsPage,
19 ProtocolConfigResponse, TransactionBlocksPage, TransactionFilter,
20};
21use iota_types::{
22 base_types::{IotaAddress, ObjectID, SequenceNumber, TransactionDigest},
23 dynamic_field::DynamicFieldName,
24 iota_serde::BigInt,
25 messages_checkpoint::CheckpointSequenceNumber,
26 transaction::{TransactionData, TransactionKind},
27};
28use jsonrpsee::core::client::Subscription;
29
30use crate::{
31 RpcClient,
32 error::{Error, IotaRpcResult},
33};
34
35/// Defines methods for retrieving data about objects and transactions.
36#[derive(Debug)]
37pub struct ReadApi {
38 api: Arc<RpcClient>,
39}
40
41impl ReadApi {
42 pub(crate) fn new(api: Arc<RpcClient>) -> Self {
43 Self { api }
44 }
45
46 /// Get the objects owned by the given address.
47 /// Results are paginated.
48 ///
49 /// Note that if the address owns more than
50 /// [`QUERY_MAX_RESULT_LIMIT`](iota_json_rpc_api::QUERY_MAX_RESULT_LIMIT)
51 /// objects (default is 50), the pagination may not be accurate as the
52 /// previous page may have been updated before the next page is fetched.
53 ///
54 /// # Examples
55 ///
56 /// ```rust,no_run
57 /// use std::str::FromStr;
58 ///
59 /// use iota_sdk::IotaClientBuilder;
60 /// use iota_types::base_types::IotaAddress;
61 ///
62 /// #[tokio::main]
63 /// async fn main() -> Result<(), anyhow::Error> {
64 /// let iota = IotaClientBuilder::default().build_testnet().await?;
65 /// let address = IotaAddress::from_str("0x0000....0000")?;
66 /// let owned_objects = iota
67 /// .read_api()
68 /// .get_owned_objects(address, None, None, None)
69 /// .await?;
70 /// Ok(())
71 /// }
72 /// ```
73 pub async fn get_owned_objects(
74 &self,
75 address: IotaAddress,
76 query: impl Into<Option<IotaObjectResponseQuery>>,
77 cursor: impl Into<Option<ObjectID>>,
78 limit: impl Into<Option<usize>>,
79 ) -> IotaRpcResult<ObjectsPage> {
80 Ok(self
81 .api
82 .http
83 .get_owned_objects(address, query.into(), cursor.into(), limit.into())
84 .await?)
85 }
86
87 /// Get the dynamic fields owned by the given [ObjectID].
88 /// Results are paginated.
89 ///
90 /// If the field is a dynamic field, this method returns the ID of the Field
91 /// object, which contains both the name and the value.
92 ///
93 /// If the field is a dynamic object field, it returns the ID of the Object,
94 /// which is the value of the field.
95 ///
96 /// # Examples
97 ///
98 /// ```rust,no_run
99 /// use std::str::FromStr;
100 ///
101 /// use iota_sdk::IotaClientBuilder;
102 /// use iota_types::base_types::{IotaAddress, ObjectID};
103 ///
104 /// #[tokio::main]
105 /// async fn main() -> Result<(), anyhow::Error> {
106 /// let iota = IotaClientBuilder::default().build_testnet().await?;
107 /// let address = IotaAddress::from_str("0x0000....0000")?;
108 /// let owned_objects = iota
109 /// .read_api()
110 /// .get_owned_objects(address, None, None, None)
111 /// .await?;
112 /// // this code example assumes that there are previous owned objects
113 /// let object = owned_objects
114 /// .data
115 /// .get(0)
116 /// .expect(&format!("No owned objects for this address {}", address));
117 /// let object_data = object.data.as_ref().expect(&format!(
118 /// "No object data for this IotaObjectResponse {:?}",
119 /// object
120 /// ));
121 /// let object_id = object_data.object_id;
122 /// let dynamic_fields = iota
123 /// .read_api()
124 /// .get_dynamic_fields(object_id, None, None)
125 /// .await?;
126 /// Ok(())
127 /// }
128 /// ```
129 pub async fn get_dynamic_fields(
130 &self,
131 object_id: ObjectID,
132 cursor: impl Into<Option<ObjectID>>,
133 limit: impl Into<Option<usize>>,
134 ) -> IotaRpcResult<DynamicFieldPage> {
135 Ok(self
136 .api
137 .http
138 .get_dynamic_fields(object_id, cursor.into(), limit.into())
139 .await?)
140 }
141
142 /// Get information for a specified dynamic field object by its parent
143 /// object ID and field name.
144 pub async fn get_dynamic_field_object(
145 &self,
146 parent_object_id: ObjectID,
147 name: DynamicFieldName,
148 ) -> IotaRpcResult<IotaObjectResponse> {
149 Ok(self
150 .api
151 .http
152 .get_dynamic_field_object(parent_object_id, name)
153 .await?)
154 }
155
156 /// Get information for a specified dynamic field object by its parent
157 /// object ID and field name with options.
158 pub async fn get_dynamic_field_object_v2(
159 &self,
160 parent_object_id: ObjectID,
161 name: DynamicFieldName,
162 options: impl Into<Option<IotaObjectDataOptions>>,
163 ) -> IotaRpcResult<IotaObjectResponse> {
164 Ok(self
165 .api
166 .http
167 .get_dynamic_field_object_v2(parent_object_id, name, options.into())
168 .await?)
169 }
170
171 /// Get a parsed past object and version for the provided object ID.
172 ///
173 /// An object's version increases when the object is mutated, though it is
174 /// not guaranteed that it increases always by 1. A past object can be used
175 /// to understand how the object changed over time, i.e. what was the total
176 /// balance at a specific version.
177 ///
178 /// # Examples
179 ///
180 /// ```rust,no_run
181 /// use std::str::FromStr;
182 ///
183 /// use iota_json_rpc_types::IotaObjectDataOptions;
184 /// use iota_sdk::IotaClientBuilder;
185 /// use iota_types::base_types::{IotaAddress, ObjectID};
186 ///
187 /// #[tokio::main]
188 /// async fn main() -> Result<(), anyhow::Error> {
189 /// let iota = IotaClientBuilder::default().build_testnet().await?;
190 /// let address = IotaAddress::from_str("0x0000....0000")?;
191 /// let owned_objects = iota
192 /// .read_api()
193 /// .get_owned_objects(address, None, None, None)
194 /// .await?;
195 /// // this code example assumes that there are previous owned objects
196 /// let object = owned_objects
197 /// .data
198 /// .get(0)
199 /// .expect(&format!("No owned objects for this address {}", address));
200 /// let object_data = object.data.as_ref().expect(&format!(
201 /// "No object data for this IotaObjectResponse {:?}",
202 /// object
203 /// ));
204 /// let object_id = object_data.object_id;
205 /// let version = object_data.version;
206 /// let past_object = iota
207 /// .read_api()
208 /// .try_get_parsed_past_object(
209 /// object_id,
210 /// version,
211 /// IotaObjectDataOptions {
212 /// show_type: true,
213 /// show_owner: true,
214 /// show_previous_transaction: true,
215 /// show_display: true,
216 /// show_content: true,
217 /// show_bcs: true,
218 /// show_storage_rebate: true,
219 /// },
220 /// )
221 /// .await?;
222 /// Ok(())
223 /// }
224 /// ```
225 pub async fn try_get_parsed_past_object(
226 &self,
227 object_id: ObjectID,
228 version: SequenceNumber,
229 options: IotaObjectDataOptions,
230 ) -> IotaRpcResult<IotaPastObjectResponse> {
231 Ok(self
232 .api
233 .http
234 .try_get_past_object(object_id, version, Some(options))
235 .await?)
236 }
237
238 /// Get a list of parsed past objects.
239 ///
240 /// See [Self::try_get_parsed_past_object] for more details about past
241 /// objects.
242 ///
243 /// # Examples
244 ///
245 /// ```rust,no_run
246 /// use std::str::FromStr;
247 ///
248 /// use iota_json_rpc_types::{IotaGetPastObjectRequest, IotaObjectDataOptions};
249 /// use iota_sdk::IotaClientBuilder;
250 /// use iota_types::base_types::{IotaAddress, ObjectID};
251 ///
252 /// #[tokio::main]
253 /// async fn main() -> Result<(), anyhow::Error> {
254 /// let iota = IotaClientBuilder::default().build_testnet().await?;
255 /// let address = IotaAddress::from_str("0x0000....0000")?;
256 /// let owned_objects = iota
257 /// .read_api()
258 /// .get_owned_objects(address, None, None, None)
259 /// .await?;
260 /// // this code example assumes that there are previous owned objects
261 /// let object = owned_objects
262 /// .data
263 /// .get(0)
264 /// .expect(&format!("No owned objects for this address {}", address));
265 /// let object_data = object.data.as_ref().expect(&format!(
266 /// "No object data for this IotaObjectResponse {:?}",
267 /// object
268 /// ));
269 /// let object_id = object_data.object_id;
270 /// let version = object_data.version;
271 /// let past_object = iota
272 /// .read_api()
273 /// .try_get_parsed_past_object(
274 /// object_id,
275 /// version,
276 /// IotaObjectDataOptions {
277 /// show_type: true,
278 /// show_owner: true,
279 /// show_previous_transaction: true,
280 /// show_display: true,
281 /// show_content: true,
282 /// show_bcs: true,
283 /// show_storage_rebate: true,
284 /// },
285 /// )
286 /// .await?;
287 /// let past_object = past_object.into_object()?;
288 /// let multi_past_object = iota
289 /// .read_api()
290 /// .try_multi_get_parsed_past_object(
291 /// vec![IotaGetPastObjectRequest {
292 /// object_id: past_object.object_id,
293 /// version: past_object.version,
294 /// }],
295 /// IotaObjectDataOptions {
296 /// show_type: true,
297 /// show_owner: true,
298 /// show_previous_transaction: true,
299 /// show_display: true,
300 /// show_content: true,
301 /// show_bcs: true,
302 /// show_storage_rebate: true,
303 /// },
304 /// )
305 /// .await?;
306 /// Ok(())
307 /// }
308 /// ```
309 pub async fn try_multi_get_parsed_past_object(
310 &self,
311 past_objects: Vec<IotaGetPastObjectRequest>,
312 options: IotaObjectDataOptions,
313 ) -> IotaRpcResult<Vec<IotaPastObjectResponse>> {
314 Ok(self
315 .api
316 .http
317 .try_multi_get_past_objects(past_objects, Some(options))
318 .await?)
319 }
320
321 /// Get an object by object ID with optional fields enabled by
322 /// [IotaObjectDataOptions].
323 ///
324 /// # Examples
325 ///
326 /// ```rust,no_run
327 /// use std::str::FromStr;
328 ///
329 /// use iota_json_rpc_types::IotaObjectDataOptions;
330 /// use iota_sdk::IotaClientBuilder;
331 /// use iota_types::base_types::IotaAddress;
332 ///
333 /// #[tokio::main]
334 /// async fn main() -> Result<(), anyhow::Error> {
335 /// let iota = IotaClientBuilder::default().build_testnet().await?;
336 /// let address = IotaAddress::from_str("0x0000....0000")?;
337 /// let owned_objects = iota
338 /// .read_api()
339 /// .get_owned_objects(address, None, None, None)
340 /// .await?;
341 /// // this code example assumes that there are previous owned objects
342 /// let object = owned_objects
343 /// .data
344 /// .get(0)
345 /// .expect(&format!("No owned objects for this address {}", address));
346 /// let object_data = object.data.as_ref().expect(&format!(
347 /// "No object data for this IotaObjectResponse {:?}",
348 /// object
349 /// ));
350 /// let object_id = object_data.object_id;
351 /// let object = iota
352 /// .read_api()
353 /// .get_object_with_options(
354 /// object_id,
355 /// IotaObjectDataOptions {
356 /// show_type: true,
357 /// show_owner: true,
358 /// show_previous_transaction: true,
359 /// show_display: true,
360 /// show_content: true,
361 /// show_bcs: true,
362 /// show_storage_rebate: true,
363 /// },
364 /// )
365 /// .await?;
366 /// Ok(())
367 /// }
368 /// ```
369 pub async fn get_object_with_options(
370 &self,
371 object_id: ObjectID,
372 options: IotaObjectDataOptions,
373 ) -> IotaRpcResult<IotaObjectResponse> {
374 Ok(self.api.http.get_object(object_id, Some(options)).await?)
375 }
376
377 /// Get a list of objects by their object IDs with optional fields enabled
378 /// by [IotaObjectDataOptions].
379 ///
380 /// # Examples
381 ///
382 /// ```rust,no_run
383 /// use std::str::FromStr;
384 ///
385 /// use iota_json_rpc_types::IotaObjectDataOptions;
386 /// use iota_sdk::IotaClientBuilder;
387 /// use iota_types::base_types::IotaAddress;
388 /// #[tokio::main]
389 /// async fn main() -> Result<(), anyhow::Error> {
390 /// let iota = IotaClientBuilder::default().build_testnet().await?;
391 /// let address = IotaAddress::from_str("0x0000....0000")?;
392 /// let owned_objects = iota
393 /// .read_api()
394 /// .get_owned_objects(address, None, None, None)
395 /// .await?;
396 /// // this code example assumes that there are previous owned objects
397 /// let object = owned_objects
398 /// .data
399 /// .get(0)
400 /// .expect(&format!("No owned objects for this address {}", address));
401 /// let object_data = object.data.as_ref().expect(&format!(
402 /// "No object data for this IotaObjectResponse {:?}",
403 /// object
404 /// ));
405 /// let object_id = object_data.object_id;
406 /// let object_ids = vec![object_id]; // and other object ids
407 /// let object = iota
408 /// .read_api()
409 /// .multi_get_object_with_options(
410 /// object_ids,
411 /// IotaObjectDataOptions {
412 /// show_type: true,
413 /// show_owner: true,
414 /// show_previous_transaction: true,
415 /// show_display: true,
416 /// show_content: true,
417 /// show_bcs: true,
418 /// show_storage_rebate: true,
419 /// },
420 /// )
421 /// .await?;
422 /// Ok(())
423 /// }
424 /// ```
425 pub async fn multi_get_object_with_options(
426 &self,
427 object_ids: Vec<ObjectID>,
428 options: IotaObjectDataOptions,
429 ) -> IotaRpcResult<Vec<IotaObjectResponse>> {
430 Ok(self
431 .api
432 .http
433 .multi_get_objects(object_ids, Some(options))
434 .await?)
435 }
436
437 /// Get a [bcs] serialized object's bytes by object ID.
438 pub async fn get_move_object_bcs(&self, object_id: ObjectID) -> IotaRpcResult<Vec<u8>> {
439 let resp = self
440 .get_object_with_options(object_id, IotaObjectDataOptions::default().with_bcs())
441 .await?
442 .into_object()
443 .map_err(|e| {
444 Error::Data(format!("Can't get bcs of object {:?}: {:?}", object_id, e))
445 })?;
446 // unwrap: requested bcs data
447 let move_object = resp.bcs.unwrap();
448 let raw_move_obj = move_object.try_into_move().ok_or(Error::Data(format!(
449 "Object {:?} is not a MoveObject",
450 object_id
451 )))?;
452 Ok(raw_move_obj.bcs_bytes)
453 }
454
455 /// Get the total number of transaction blocks known to server.
456 ///
457 /// # Examples
458 ///
459 /// ```rust,no_run
460 /// use iota_sdk::IotaClientBuilder;
461 ///
462 /// #[tokio::main]
463 /// async fn main() -> Result<(), anyhow::Error> {
464 /// let iota = IotaClientBuilder::default().build_testnet().await?;
465 /// let total_transaction_blocks = iota.read_api().get_total_transaction_blocks().await?;
466 /// Ok(())
467 /// }
468 /// ```
469 pub async fn get_total_transaction_blocks(&self) -> IotaRpcResult<u64> {
470 Ok(*self.api.http.get_total_transaction_blocks().await?)
471 }
472
473 /// Get a transaction and its effects by its digest with optional fields
474 /// enabled by [IotaTransactionBlockResponseOptions].
475 pub async fn get_transaction_with_options(
476 &self,
477 digest: TransactionDigest,
478 options: IotaTransactionBlockResponseOptions,
479 ) -> IotaRpcResult<IotaTransactionBlockResponse> {
480 Ok(self
481 .api
482 .http
483 .get_transaction_block(digest, Some(options))
484 .await?)
485 }
486
487 /// Get a list of transactions and their effects by their digests with
488 /// optional fields enabled by [IotaTransactionBlockResponseOptions].
489 pub async fn multi_get_transactions_with_options(
490 &self,
491 digests: Vec<TransactionDigest>,
492 options: IotaTransactionBlockResponseOptions,
493 ) -> IotaRpcResult<Vec<IotaTransactionBlockResponse>> {
494 Ok(self
495 .api
496 .http
497 .multi_get_transaction_blocks(digests, Some(options))
498 .await?)
499 }
500
501 /// Get filtered transaction blocks information.
502 /// Results are paginated.
503 pub async fn query_transaction_blocks(
504 &self,
505 query: IotaTransactionBlockResponseQuery,
506 cursor: impl Into<Option<TransactionDigest>>,
507 limit: impl Into<Option<usize>>,
508 descending_order: bool,
509 ) -> IotaRpcResult<TransactionBlocksPage> {
510 Ok(self
511 .api
512 .http
513 .query_transaction_blocks(query, cursor.into(), limit.into(), Some(descending_order))
514 .await?)
515 }
516
517 /// Get the first four bytes of the chain's genesis checkpoint digest in hex
518 /// format.
519 pub async fn get_chain_identifier(&self) -> IotaRpcResult<String> {
520 Ok(self.api.http.get_chain_identifier().await?)
521 }
522
523 /// Get a checkpoint by its ID.
524 pub async fn get_checkpoint(&self, id: CheckpointId) -> IotaRpcResult<Checkpoint> {
525 Ok(self.api.http.get_checkpoint(id).await?)
526 }
527
528 /// Return a list of checkpoints.
529 /// Results are paginated.
530 pub async fn get_checkpoints(
531 &self,
532 cursor: impl Into<Option<BigInt<u64>>>,
533 limit: impl Into<Option<usize>>,
534 descending_order: bool,
535 ) -> IotaRpcResult<CheckpointPage> {
536 Ok(self
537 .api
538 .http
539 .get_checkpoints(cursor.into(), limit.into(), descending_order)
540 .await?)
541 }
542
543 /// Get the sequence number of the latest checkpoint that has been executed.
544 pub async fn get_latest_checkpoint_sequence_number(
545 &self,
546 ) -> IotaRpcResult<CheckpointSequenceNumber> {
547 Ok(*self
548 .api
549 .http
550 .get_latest_checkpoint_sequence_number()
551 .await?)
552 }
553
554 /// Get a stream of transactions.
555 pub fn get_transactions_stream(
556 &self,
557 query: IotaTransactionBlockResponseQuery,
558 cursor: impl Into<Option<TransactionDigest>>,
559 descending_order: bool,
560 ) -> impl Stream<Item = IotaTransactionBlockResponse> + '_ {
561 let cursor = cursor.into();
562
563 stream::unfold(
564 (vec![], cursor, true, query),
565 move |(mut data, cursor, first, query)| async move {
566 if let Some(item) = data.pop() {
567 Some((item, (data, cursor, false, query)))
568 } else if (cursor.is_none() && first) || cursor.is_some() {
569 let page = self
570 .query_transaction_blocks(
571 query.clone(),
572 cursor,
573 Some(100),
574 descending_order,
575 )
576 .await
577 .ok()?;
578 let mut data = page.data;
579 data.reverse();
580 data.pop()
581 .map(|item| (item, (data, page.next_cursor, false, query)))
582 } else {
583 None
584 }
585 },
586 )
587 }
588
589 /// Subscribe to a stream of transactions.
590 ///
591 /// This is only available through WebSockets.
592 pub async fn subscribe_transaction(
593 &self,
594 filter: TransactionFilter,
595 ) -> IotaRpcResult<impl Stream<Item = IotaRpcResult<IotaTransactionBlockEffects>>> {
596 let Some(c) = &self.api.ws else {
597 return Err(Error::Subscription(
598 "Subscription only supported by WebSocket client.".to_string(),
599 ));
600 };
601 let subscription: Subscription<IotaTransactionBlockEffects> =
602 c.subscribe_transaction(filter).await?;
603 Ok(subscription.map(|item| Ok(item?)))
604 }
605
606 /// Get move modules by package ID, keyed by name.
607 pub async fn get_normalized_move_modules_by_package(
608 &self,
609 package: ObjectID,
610 ) -> IotaRpcResult<BTreeMap<String, IotaMoveNormalizedModule>> {
611 Ok(self
612 .api
613 .http
614 .get_normalized_move_modules_by_package(package)
615 .await?)
616 }
617
618 // TODO(devx): we can probably cache this given an epoch
619 /// Get the reference gas price.
620 pub async fn get_reference_gas_price(&self) -> IotaRpcResult<u64> {
621 Ok(*self.api.http.get_reference_gas_price().await?)
622 }
623
624 /// Dry run a transaction block given the provided transaction data.
625 ///
626 /// This simulates running the transaction, including all standard checks,
627 /// without actually running it. This is useful for estimating the gas fees
628 /// of a transaction before executing it. You can also use it to identify
629 /// any side-effects of a transaction before you execute it on the network.
630 pub async fn dry_run_transaction_block(
631 &self,
632 tx: TransactionData,
633 ) -> IotaRpcResult<DryRunTransactionBlockResponse> {
634 Ok(self
635 .api
636 .http
637 .dry_run_transaction_block(Base64::from_bytes(&bcs::to_bytes(&tx)?))
638 .await?)
639 }
640
641 /// Use this function to inspect the current state of the network by running
642 /// a programmable transaction block without committing its effects on
643 /// chain.
644 ///
645 /// Unlike a dry run, this method will not validate whether the transaction
646 /// block would succeed or fail under normal circumstances, e.g.:
647 ///
648 /// - Transaction inputs are not checked for ownership (i.e. you can
649 /// construct calls involving objects you do not own)
650 /// - Calls are not checked for visibility (you can call private functions
651 /// on modules)
652 /// - Inputs of any type can be constructed and passed in, including coins
653 /// and other objects that would usually need to be constructed with a
654 /// move call
655 /// - Function returns do not need to be used, even if they do not have
656 /// `drop`
657 ///
658 /// This method's output includes a breakdown of results returned by every
659 /// transaction in the block, as well as the transaction's effects.
660 ///
661 /// To run an accurate simulation of a transaction and understand whether
662 /// it will successfully validate and run, use
663 /// [Self::dry_run_transaction_block] instead.
664 pub async fn dev_inspect_transaction_block(
665 &self,
666 sender_address: IotaAddress,
667 tx: TransactionKind,
668 gas_price: impl Into<Option<BigInt<u64>>>,
669 epoch: impl Into<Option<BigInt<u64>>>,
670 additional_args: impl Into<Option<DevInspectArgs>>,
671 ) -> IotaRpcResult<DevInspectResults> {
672 Ok(self
673 .api
674 .http
675 .dev_inspect_transaction_block(
676 sender_address,
677 Base64::from_bytes(&bcs::to_bytes(&tx)?),
678 gas_price.into(),
679 epoch.into(),
680 additional_args.into(),
681 )
682 .await?)
683 }
684
685 /// Get the protocol config by version.
686 ///
687 /// The version defaults to the current version.
688 pub async fn get_protocol_config(
689 &self,
690 version: impl Into<Option<BigInt<u64>>>,
691 ) -> IotaRpcResult<ProtocolConfigResponse> {
692 Ok(self.api.http.get_protocol_config(version.into()).await?)
693 }
694
695 /// Get an object by ID before the given version.
696 pub async fn try_get_object_before_version(
697 &self,
698 object_id: ObjectID,
699 version: SequenceNumber,
700 ) -> IotaRpcResult<IotaPastObjectResponse> {
701 Ok(self
702 .api
703 .http
704 .try_get_object_before_version(object_id, version)
705 .await?)
706 }
707}