sui_sdk/
apis.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use std::collections::BTreeMap;
5use std::future;
6use std::sync::Arc;
7use std::time::Duration;
8use std::time::Instant;
9
10use fastcrypto::encoding::Base64;
11use futures::StreamExt;
12use futures::stream;
13use futures_core::Stream;
14use jsonrpsee::core::client::Subscription;
15use sui_json_rpc_api::{
16    CoinReadApiClient, GovernanceReadApiClient, IndexerApiClient, MoveUtilsClient, ReadApiClient,
17    WriteApiClient,
18};
19use sui_json_rpc_types::CheckpointPage;
20use sui_json_rpc_types::DevInspectArgs;
21use sui_json_rpc_types::SuiData;
22use sui_json_rpc_types::ZkLoginIntentScope;
23use sui_json_rpc_types::ZkLoginVerifyResult;
24use sui_json_rpc_types::{
25    Balance, Checkpoint, CheckpointId, Coin, CoinPage, DelegatedStake, DevInspectResults,
26    DryRunTransactionBlockResponse, DynamicFieldPage, EventFilter, EventPage, ObjectsPage,
27    ProtocolConfigResponse, SuiCoinMetadata, SuiCommittee, SuiEvent, SuiGetPastObjectRequest,
28    SuiMoveNormalizedModule, SuiObjectDataOptions, SuiObjectResponse, SuiObjectResponseQuery,
29    SuiPastObjectResponse, SuiTransactionBlockEffects, SuiTransactionBlockResponse,
30    SuiTransactionBlockResponseOptions, SuiTransactionBlockResponseQuery, TransactionBlocksPage,
31    TransactionFilter,
32};
33use sui_types::balance::Supply;
34use sui_types::base_types::{ObjectID, SequenceNumber, SuiAddress, TransactionDigest};
35use sui_types::dynamic_field::DynamicFieldName;
36use sui_types::event::EventID;
37use sui_types::messages_checkpoint::CheckpointSequenceNumber;
38use sui_types::sui_serde::BigInt;
39use sui_types::sui_system_state::sui_system_state_summary::SuiSystemStateSummary;
40use sui_types::transaction::{Transaction, TransactionData, TransactionKind};
41use sui_types::transaction_driver_types::ExecuteTransactionRequestType;
42use tracing::debug;
43
44use crate::RpcClient;
45use crate::error::{Error, SuiRpcResult};
46
47const WAIT_FOR_LOCAL_EXECUTION_MIN_INTERVAL: Duration = Duration::from_millis(100);
48const WAIT_FOR_LOCAL_EXECUTION_MAX_INTERVAL: Duration = Duration::from_secs(2);
49
50/// The main read API structure with functions for retrieving data about different objects and transactions
51#[derive(Debug)]
52pub struct ReadApi {
53    api: Arc<RpcClient>,
54}
55
56impl ReadApi {
57    pub(crate) fn new(api: Arc<RpcClient>) -> Self {
58        Self { api }
59    }
60    /// Return a paginated response with the objects owned by the given address, or an error upon failure.
61    ///
62    /// Note that if the address owns more than `QUERY_MAX_RESULT_LIMIT` objects (default is 50),
63    /// the pagination is not accurate, because previous page may have been updated when the next page is fetched.
64    ///
65    /// # Examples
66    ///
67    /// ```rust,no_run
68    /// use sui_sdk::SuiClientBuilder;
69    /// use sui_types::base_types::SuiAddress;
70    /// use std::str::FromStr;
71    ///
72    /// #[tokio::main]
73    /// async fn main() -> Result<(), anyhow::Error> {
74    ///     let sui = SuiClientBuilder::default().build_localnet().await?;
75    ///     let address = SuiAddress::from_str("0x0000....0000")?;
76    ///     let owned_objects = sui
77    ///         .read_api()
78    ///         .get_owned_objects(address, None, None, None)
79    ///         .await?;
80    ///     Ok(())
81    /// }
82    /// ```
83    pub async fn get_owned_objects(
84        &self,
85        address: SuiAddress,
86        query: Option<SuiObjectResponseQuery>,
87        cursor: Option<ObjectID>,
88        limit: Option<usize>,
89    ) -> SuiRpcResult<ObjectsPage> {
90        Ok(self
91            .api
92            .http
93            .get_owned_objects(address, query, cursor, limit)
94            .await?)
95    }
96
97    /// Return a paginated response with the dynamic fields owned by the given [ObjectID], or an error upon failure.
98    ///
99    /// The return type is a list of `DynamicFieldInfo` objects, where the field name is always present,
100    /// represented as a Move `Value`.
101    ///
102    /// If the field is a dynamic field, returns the ID of the Field object (which contains both the name and the value).
103    /// If the field is a dynamic object field, it returns the ID of the Object (the value of the field).
104    ///
105    /// # Examples
106    ///
107    /// ```rust,no_run
108    /// use sui_sdk::SuiClientBuilder;
109    /// use sui_types::base_types::{ObjectID, SuiAddress};
110    /// use std::str::FromStr;
111    ///
112    /// #[tokio::main]
113    /// async fn main() -> Result<(), anyhow::Error> {
114    ///     let sui = SuiClientBuilder::default().build_localnet().await?;
115    ///     let address = SuiAddress::from_str("0x0000....0000")?;
116    ///     let owned_objects = sui
117    ///         .read_api()
118    ///         .get_owned_objects(address, None, None, None)
119    ///         .await?;
120    ///     // this code example assumes that there are previous owned objects
121    ///     let object = owned_objects.data.get(0).expect(&format!(
122    ///         "No owned objects for this address {}",
123    ///         address
124    ///     ));
125    ///     let object_data = object.data.as_ref().expect(&format!(
126    ///         "No object data for this SuiObjectResponse {:?}",
127    ///         object
128    ///     ));
129    ///     let object_id = object_data.object_id;
130    ///     let dynamic_fields = sui
131    ///         .read_api()
132    ///         .get_dynamic_fields(object_id, None, None)
133    ///         .await?;
134    ///     Ok(())
135    /// }
136    /// ```
137    pub async fn get_dynamic_fields(
138        &self,
139        object_id: ObjectID,
140        cursor: Option<ObjectID>,
141        limit: Option<usize>,
142    ) -> SuiRpcResult<DynamicFieldPage> {
143        Ok(self
144            .api
145            .http
146            .get_dynamic_fields(object_id, cursor, limit)
147            .await?)
148    }
149
150    /// Return the dynamic field object information for a specified object.
151    pub async fn get_dynamic_field_object(
152        &self,
153        parent_object_id: ObjectID,
154        name: DynamicFieldName,
155    ) -> SuiRpcResult<SuiObjectResponse> {
156        Ok(self
157            .api
158            .http
159            .get_dynamic_field_object(parent_object_id, name)
160            .await?)
161    }
162
163    /// Return a parsed past object for the provided [ObjectID] and version, or an error upon failure.
164    ///
165    /// An object's version increases (though it is not guaranteed that it increases always by 1) when
166    /// the object is mutated. A past object can be used to understand how the object changed over time,
167    /// i.e. what was the total balance at a specific version.
168    ///
169    /// # Examples
170    ///
171    /// ```rust,no_run
172    /// use sui_sdk::SuiClientBuilder;
173    /// use sui_types::base_types::{ObjectID, SuiAddress};
174    /// use sui_json_rpc_types::SuiObjectDataOptions;
175    /// use std::str::FromStr;
176    ///
177    /// #[tokio::main]
178    /// async fn main() -> Result<(), anyhow::Error> {
179    ///     let sui = SuiClientBuilder::default().build_localnet().await?;
180    ///     let address = SuiAddress::from_str("0x0000....0000")?;
181    ///     let owned_objects = sui
182    ///         .read_api()
183    ///         .get_owned_objects(address, None, None, None)
184    ///         .await?;
185    ///     // this code example assumes that there are previous owned objects
186    ///     let object = owned_objects.data.get(0).expect(&format!(
187    ///         "No owned objects for this address {}",
188    ///         address
189    ///     ));
190    ///     let object_data = object.data.as_ref().expect(&format!(
191    ///         "No object data for this SuiObjectResponse {:?}",
192    ///         object
193    ///     ));
194    ///     let object_id = object_data.object_id;
195    ///     let version = object_data.version;
196    ///     let past_object = sui
197    ///         .read_api()
198    ///         .try_get_parsed_past_object(
199    ///             object_id,
200    ///             version,
201    ///             SuiObjectDataOptions {
202    ///                 show_type: true,
203    ///                 show_owner: true,
204    ///                 show_previous_transaction: true,
205    ///                 show_display: true,
206    ///                 show_content: true,
207    ///                 show_bcs: true,
208    ///                 show_storage_rebate: true,
209    ///             },
210    ///         )
211    ///         .await?;
212    ///     Ok(())
213    /// }
214    ///```
215    pub async fn try_get_parsed_past_object(
216        &self,
217        object_id: ObjectID,
218        version: SequenceNumber,
219        options: SuiObjectDataOptions,
220    ) -> SuiRpcResult<SuiPastObjectResponse> {
221        Ok(self
222            .api
223            .http
224            .try_get_past_object(object_id, version, Some(options))
225            .await?)
226    }
227
228    /// Return a list of [SuiPastObjectResponse] objects, or an error upon failure.
229    ///
230    /// See [this function](ReadApi::try_get_parsed_past_object) for more details about past objects.
231    ///
232    /// # Examples
233    ///
234    /// ```rust,no_run
235    /// use sui_sdk::SuiClientBuilder;
236    /// use sui_types::base_types::{ObjectID, SuiAddress};
237    /// use sui_json_rpc_types::{SuiObjectDataOptions, SuiGetPastObjectRequest};
238    /// use std::str::FromStr;
239    ///
240    /// #[tokio::main]
241    /// async fn main() -> Result<(), anyhow::Error> {
242    ///     let sui = SuiClientBuilder::default().build_localnet().await?;
243    ///     let address = SuiAddress::from_str("0x0000....0000")?;
244    ///     let owned_objects = sui
245    ///         .read_api()
246    ///         .get_owned_objects(address, None, None, None)
247    ///         .await?;
248    ///     // this code example assumes that there are previous owned objects
249    ///     let object = owned_objects.data.get(0).expect(&format!(
250    ///         "No owned objects for this address {}",
251    ///         address
252    ///     ));
253    ///     let object_data = object.data.as_ref().expect(&format!(
254    ///         "No object data for this SuiObjectResponse {:?}",
255    ///         object
256    ///     ));
257    ///     let object_id = object_data.object_id;
258    ///     let version = object_data.version;
259    ///     let past_object = sui
260    ///         .read_api()
261    ///         .try_get_parsed_past_object(
262    ///             object_id,
263    ///             version,
264    ///             SuiObjectDataOptions {
265    ///                 show_type: true,
266    ///                 show_owner: true,
267    ///                 show_previous_transaction: true,
268    ///                 show_display: true,
269    ///                 show_content: true,
270    ///                 show_bcs: true,
271    ///                 show_storage_rebate: true,
272    ///             },
273    ///         )
274    ///         .await?;
275    ///     let past_object = past_object.into_object()?;
276    ///     let multi_past_object = sui
277    ///         .read_api()
278    ///         .try_multi_get_parsed_past_object(
279    ///             vec![SuiGetPastObjectRequest {
280    ///                 object_id: past_object.object_id,
281    ///                 version: past_object.version,
282    ///             }],
283    ///             SuiObjectDataOptions {
284    ///                 show_type: true,
285    ///                 show_owner: true,
286    ///                 show_previous_transaction: true,
287    ///                 show_display: true,
288    ///                 show_content: true,
289    ///                 show_bcs: true,
290    ///                 show_storage_rebate: true,
291    ///             },
292    ///         )
293    ///         .await?;
294    ///     Ok(())
295    /// }
296    /// ```
297    pub async fn try_multi_get_parsed_past_object(
298        &self,
299        past_objects: Vec<SuiGetPastObjectRequest>,
300        options: SuiObjectDataOptions,
301    ) -> SuiRpcResult<Vec<SuiPastObjectResponse>> {
302        Ok(self
303            .api
304            .http
305            .try_multi_get_past_objects(past_objects, Some(options))
306            .await?)
307    }
308
309    /// Return a [SuiObjectResponse] based on the provided [ObjectID] and [SuiObjectDataOptions], or an error upon failure.
310    ///
311    /// The [SuiObjectResponse] contains two fields:
312    /// 1) `data` for the object's data (see [SuiObjectData](sui_json_rpc_types::SuiObjectData)),
313    /// 2) `error` for the error (if any) (see [SuiObjectResponseError](sui_types::error::SuiObjectResponseError)).
314    ///
315    /// # Examples
316    ///
317    /// ```rust,no_run
318    /// use sui_sdk::SuiClientBuilder;
319    /// use sui_types::base_types::SuiAddress;
320    /// use sui_json_rpc_types::SuiObjectDataOptions;
321    /// use std::str::FromStr;
322    ///
323    /// #[tokio::main]
324    /// async fn main() -> Result<(), anyhow::Error> {
325    ///     let sui = SuiClientBuilder::default().build_localnet().await?;
326    ///     let address = SuiAddress::from_str("0x0000....0000")?;
327    ///     let owned_objects = sui
328    ///         .read_api()
329    ///         .get_owned_objects(address, None, None, None)
330    ///         .await?;
331    ///     // this code example assumes that there are previous owned objects
332    ///     let object = owned_objects.data.get(0).expect(&format!(
333    ///         "No owned objects for this address {}",
334    ///         address
335    ///     ));
336    ///     let object_data = object.data.as_ref().expect(&format!(
337    ///         "No object data for this SuiObjectResponse {:?}",
338    ///         object
339    ///     ));
340    ///     let object_id = object_data.object_id;
341    ///     let object = sui.read_api().get_object_with_options(object_id,
342    ///             SuiObjectDataOptions {
343    ///                 show_type: true,
344    ///                 show_owner: true,
345    ///                 show_previous_transaction: true,
346    ///                 show_display: true,
347    ///                 show_content: true,
348    ///                 show_bcs: true,
349    ///                 show_storage_rebate: true,
350    ///             },
351    ///         ).await?;
352    ///     Ok(())
353    /// }
354    /// ```
355    pub async fn get_object_with_options(
356        &self,
357        object_id: ObjectID,
358        options: SuiObjectDataOptions,
359    ) -> SuiRpcResult<SuiObjectResponse> {
360        Ok(self.api.http.get_object(object_id, Some(options)).await?)
361    }
362
363    /// Return a list of [SuiObjectResponse] from the given vector of [ObjectID]s and [SuiObjectDataOptions], or an error upon failure.
364    ///
365    /// If only one object is needed, use the [get_object_with_options](ReadApi::get_object_with_options) function instead.
366    ///
367    /// # Examples
368    ///
369    /// ```rust,no_run
370    /// use sui_sdk::SuiClientBuilder;
371    /// use sui_types::base_types::SuiAddress;
372    /// use sui_json_rpc_types::SuiObjectDataOptions;
373    /// use std::str::FromStr;
374    /// #[tokio::main]
375    /// async fn main() -> Result<(), anyhow::Error> {
376    ///     let sui = SuiClientBuilder::default().build_localnet().await?;
377    ///     let address = SuiAddress::from_str("0x0000....0000")?;
378    ///     let owned_objects = sui
379    ///         .read_api()
380    ///         .get_owned_objects(address, None, None, None)
381    ///         .await?;
382    ///     // this code example assumes that there are previous owned objects
383    ///     let object = owned_objects.data.get(0).expect(&format!(
384    ///         "No owned objects for this address {}",
385    ///         address
386    ///     ));
387    ///     let object_data = object.data.as_ref().expect(&format!(
388    ///         "No object data for this SuiObjectResponse {:?}",
389    ///         object
390    ///     ));
391    ///     let object_id = object_data.object_id;
392    ///     let object_ids = vec![object_id]; // and other object ids
393    ///     let object = sui.read_api().multi_get_object_with_options(object_ids,
394    ///             SuiObjectDataOptions {
395    ///                 show_type: true,
396    ///                 show_owner: true,
397    ///                 show_previous_transaction: true,
398    ///                 show_display: true,
399    ///                 show_content: true,
400    ///                 show_bcs: true,
401    ///                 show_storage_rebate: true,
402    ///             },
403    ///         ).await?;
404    ///     Ok(())
405    /// }
406    /// ```
407    pub async fn multi_get_object_with_options(
408        &self,
409        object_ids: Vec<ObjectID>,
410        options: SuiObjectDataOptions,
411    ) -> SuiRpcResult<Vec<SuiObjectResponse>> {
412        Ok(self
413            .api
414            .http
415            .multi_get_objects(object_ids, Some(options))
416            .await?)
417    }
418
419    /// Return An object's bcs content [`Vec<u8>`] based on the provided [ObjectID], or an error upon failure.
420    pub async fn get_move_object_bcs(&self, object_id: ObjectID) -> SuiRpcResult<Vec<u8>> {
421        let resp = self
422            .get_object_with_options(object_id, SuiObjectDataOptions::default().with_bcs())
423            .await?
424            .into_object()
425            .map_err(|e| {
426                Error::DataError(format!("Can't get bcs of object {:?}: {:?}", object_id, e))
427            })?;
428        // unwrap: requested bcs data
429        let move_object = resp.bcs.unwrap();
430        let raw_move_obj = move_object.try_into_move().ok_or(Error::DataError(format!(
431            "Object {:?} is not a MoveObject",
432            object_id
433        )))?;
434        Ok(raw_move_obj.bcs_bytes)
435    }
436
437    /// Return the total number of transaction blocks known to server, or an error upon failure.
438    ///
439    /// # Examples
440    ///
441    /// ```rust,no_run
442    /// use sui_sdk::SuiClientBuilder;
443    ///
444    /// #[tokio::main]
445    /// async fn main() -> Result<(), anyhow::Error> {
446    ///     let sui = SuiClientBuilder::default().build_localnet().await?;
447    ///     let total_transaction_blocks = sui
448    ///         .read_api()
449    ///         .get_total_transaction_blocks()
450    ///         .await?;
451    ///     Ok(())
452    /// }
453    /// ```
454    pub async fn get_total_transaction_blocks(&self) -> SuiRpcResult<u64> {
455        Ok(*self.api.http.get_total_transaction_blocks().await?)
456    }
457
458    /// Return a transaction and its effects in a [SuiTransactionBlockResponse] based on its
459    /// [TransactionDigest], or an error upon failure.
460    pub async fn get_transaction_with_options(
461        &self,
462        digest: TransactionDigest,
463        options: SuiTransactionBlockResponseOptions,
464    ) -> SuiRpcResult<SuiTransactionBlockResponse> {
465        Ok(self
466            .api
467            .http
468            .get_transaction_block(digest, Some(options))
469            .await?)
470    }
471    /// Return a list of [SuiTransactionBlockResponse] based on the given vector of [TransactionDigest], or an error upon failure.
472    ///
473    /// If only one transaction data is needed, use the
474    /// [get_transaction_with_options](ReadApi::get_transaction_with_options) function instead.
475    pub async fn multi_get_transactions_with_options(
476        &self,
477        digests: Vec<TransactionDigest>,
478        options: SuiTransactionBlockResponseOptions,
479    ) -> SuiRpcResult<Vec<SuiTransactionBlockResponse>> {
480        Ok(self
481            .api
482            .http
483            .multi_get_transaction_blocks(digests, Some(options))
484            .await?)
485    }
486
487    /// Return the [SuiCommittee] information for the provided `epoch`, or an error upon failure.
488    ///
489    /// The [SuiCommittee] contains the validators list and their information (name and stakes).
490    ///
491    /// The argument `epoch` is either a known epoch id or `None` for the current epoch.
492    ///
493    /// # Examples
494    ///
495    /// ```rust,no_run
496    /// use sui_sdk::SuiClientBuilder;
497    ///
498    /// #[tokio::main]
499    /// async fn main() -> Result<(), anyhow::Error> {
500    ///     let sui = SuiClientBuilder::default().build_localnet().await?;
501    ///     let committee_info = sui
502    ///         .read_api()
503    ///         .get_committee_info(None)
504    ///         .await?;
505    ///     Ok(())
506    /// }
507    /// ```
508    pub async fn get_committee_info(
509        &self,
510        epoch: Option<BigInt<u64>>,
511    ) -> SuiRpcResult<SuiCommittee> {
512        Ok(self.api.http.get_committee_info(epoch).await?)
513    }
514
515    /// Return a paginated response with all transaction blocks information, or an error upon failure.
516    pub async fn query_transaction_blocks(
517        &self,
518        query: SuiTransactionBlockResponseQuery,
519        cursor: Option<TransactionDigest>,
520        limit: Option<usize>,
521        descending_order: bool,
522    ) -> SuiRpcResult<TransactionBlocksPage> {
523        Ok(self
524            .api
525            .http
526            .query_transaction_blocks(query, cursor, limit, Some(descending_order))
527            .await?)
528    }
529
530    /// Return the first four bytes of the chain's genesis checkpoint digest, or an error upon failure.
531    pub async fn get_chain_identifier(&self) -> SuiRpcResult<String> {
532        Ok(self.api.http.get_chain_identifier().await?)
533    }
534
535    /// Return a checkpoint, or an error upon failure.
536    ///
537    /// A Sui checkpoint is a sequence of transaction sets that a quorum of validators
538    /// agree upon as having been executed within the Sui system.
539    pub async fn get_checkpoint(&self, id: CheckpointId) -> SuiRpcResult<Checkpoint> {
540        Ok(self.api.http.get_checkpoint(id).await?)
541    }
542
543    /// Return a paginated list of checkpoints, or an error upon failure.
544    pub async fn get_checkpoints(
545        &self,
546        cursor: Option<BigInt<u64>>,
547        limit: Option<usize>,
548        descending_order: bool,
549    ) -> SuiRpcResult<CheckpointPage> {
550        Ok(self
551            .api
552            .http
553            .get_checkpoints(cursor, limit, descending_order)
554            .await?)
555    }
556
557    /// Return the sequence number of the latest checkpoint that has been executed, or an error upon failure.
558    pub async fn get_latest_checkpoint_sequence_number(
559        &self,
560    ) -> SuiRpcResult<CheckpointSequenceNumber> {
561        Ok(*self
562            .api
563            .http
564            .get_latest_checkpoint_sequence_number()
565            .await?)
566    }
567
568    /// Return a stream of [SuiTransactionBlockResponse], or an error upon failure.
569    pub fn get_transactions_stream(
570        &self,
571        query: SuiTransactionBlockResponseQuery,
572        cursor: Option<TransactionDigest>,
573        descending_order: bool,
574    ) -> impl Stream<Item = SuiTransactionBlockResponse> + '_ {
575        stream::unfold(
576            (vec![], cursor, true, query),
577            move |(mut data, cursor, first, query)| async move {
578                if let Some(item) = data.pop() {
579                    Some((item, (data, cursor, false, query)))
580                } else if (cursor.is_none() && first) || cursor.is_some() {
581                    let page = self
582                        .query_transaction_blocks(
583                            query.clone(),
584                            cursor,
585                            Some(100),
586                            descending_order,
587                        )
588                        .await
589                        .ok()?;
590                    let mut data = page.data;
591                    data.reverse();
592                    data.pop()
593                        .map(|item| (item, (data, page.next_cursor, false, query)))
594                } else {
595                    None
596                }
597            },
598        )
599    }
600
601    /// Subscribe to a stream of transactions.
602    ///
603    /// This is only available through WebSockets.
604    pub async fn subscribe_transaction(
605        &self,
606        filter: TransactionFilter,
607    ) -> SuiRpcResult<impl Stream<Item = SuiRpcResult<SuiTransactionBlockEffects>>> {
608        let Some(c) = &self.api.ws else {
609            return Err(Error::Subscription(
610                "Subscription only supported by WebSocket client.".to_string(),
611            ));
612        };
613        let subscription: Subscription<SuiTransactionBlockEffects> =
614            c.subscribe_transaction(filter).await?;
615        Ok(subscription.map(|item| Ok(item?)))
616    }
617
618    /// Return a map consisting of the move package name and the normalized module, or an error upon failure.
619    pub async fn get_normalized_move_modules_by_package(
620        &self,
621        package: ObjectID,
622    ) -> SuiRpcResult<BTreeMap<String, SuiMoveNormalizedModule>> {
623        Ok(self
624            .api
625            .http
626            .get_normalized_move_modules_by_package(package)
627            .await?)
628    }
629
630    // TODO(devx): we can probably cache this given an epoch
631    /// Return the reference gas price, or an error upon failure.
632    pub async fn get_reference_gas_price(&self) -> SuiRpcResult<u64> {
633        Ok(*self.api.http.get_reference_gas_price().await?)
634    }
635
636    /// Dry run a transaction block given the provided transaction data. Returns an error upon failure.
637    ///
638    /// Simulate running the transaction, including all standard checks, without actually running it.
639    /// This is useful for estimating the gas fees of a transaction before executing it.
640    /// You can also use it to identify any side-effects of a transaction before you execute it on the network.
641    pub async fn dry_run_transaction_block(
642        &self,
643        tx: TransactionData,
644    ) -> SuiRpcResult<DryRunTransactionBlockResponse> {
645        Ok(self
646            .api
647            .http
648            .dry_run_transaction_block(Base64::from_bytes(&bcs::to_bytes(&tx)?))
649            .await?)
650    }
651
652    /// Return the inspection of the transaction block, or an error upon failure.
653    ///
654    /// Use this function to inspect the current state of the network by running a programmable
655    /// transaction block without committing its effects on chain.  Unlike
656    /// [dry_run_transaction_block](ReadApi::dry_run_transaction_block),
657    /// dev inspect will not validate whether the transaction block
658    /// would succeed or fail under normal circumstances, e.g.:
659    ///
660    /// - Transaction inputs are not checked for ownership (i.e. you can
661    ///   construct calls involving objects you do not own).
662    /// - Calls are not checked for visibility (you can call private functions on modules)
663    /// - Inputs of any type can be constructed and passed in, (including
664    ///   Coins and other objects that would usually need to be constructed
665    ///   with a move call).
666    /// - Function returns do not need to be used, even if they do not have `drop`.
667    ///
668    /// Dev inspect's output includes a breakdown of results returned by every transaction
669    /// in the block, as well as the transaction's effects.
670    ///
671    /// To run an accurate simulation of a transaction and understand whether
672    /// it will successfully validate and run,
673    /// use the [dry_run_transaction_block](ReadApi::dry_run_transaction_block) function instead.
674    pub async fn dev_inspect_transaction_block(
675        &self,
676        sender_address: SuiAddress,
677        tx: TransactionKind,
678        gas_price: Option<BigInt<u64>>,
679        epoch: Option<BigInt<u64>>,
680        additional_args: Option<DevInspectArgs>,
681    ) -> SuiRpcResult<DevInspectResults> {
682        Ok(self
683            .api
684            .http
685            .dev_inspect_transaction_block(
686                sender_address,
687                Base64::from_bytes(&bcs::to_bytes(&tx)?),
688                gas_price,
689                epoch,
690                additional_args,
691            )
692            .await?)
693    }
694
695    /// Return the protocol config, or an error upon failure.
696    pub async fn get_protocol_config(
697        &self,
698        version: Option<BigInt<u64>>,
699    ) -> SuiRpcResult<ProtocolConfigResponse> {
700        Ok(self.api.http.get_protocol_config(version).await?)
701    }
702
703    pub async fn try_get_object_before_version(
704        &self,
705        object_id: ObjectID,
706        version: SequenceNumber,
707    ) -> SuiRpcResult<SuiPastObjectResponse> {
708        Ok(self
709            .api
710            .http
711            .try_get_object_before_version(object_id, version)
712            .await?)
713    }
714
715    /// Verify a zkLogin signature against bytes that is parsed using intent_scope, and the sui address.
716    pub async fn verify_zklogin_signature(
717        &self,
718        bytes: String,
719        signature: String,
720        intent_scope: ZkLoginIntentScope,
721        address: SuiAddress,
722    ) -> SuiRpcResult<ZkLoginVerifyResult> {
723        Ok(self
724            .api
725            .http
726            .verify_zklogin_signature(bytes, signature, intent_scope, address)
727            .await?)
728    }
729}
730
731/// Coin Read API provides the functionality needed to get information from the Sui network regarding the coins owned by an address.
732#[derive(Debug, Clone)]
733pub struct CoinReadApi {
734    api: Arc<RpcClient>,
735}
736
737impl CoinReadApi {
738    pub(crate) fn new(api: Arc<RpcClient>) -> Self {
739        Self { api }
740    }
741
742    /// Return a paginated response with the coins for the given address, or an error upon failure.
743    ///
744    /// The coins can be filtered by `coin_type` (e.g., 0x168da5bf1f48dafc111b0a488fa454aca95e0b5e::usdc::USDC)
745    /// or use `None` for the default `Coin<SUI>`.
746    ///
747    /// # Examples
748    ///
749    /// ```rust,no_run
750    /// use sui_sdk::SuiClientBuilder;
751    /// use sui_types::base_types::SuiAddress;
752    /// use std::str::FromStr;
753    ///
754    /// #[tokio::main]
755    /// async fn main() -> Result<(), anyhow::Error> {
756    ///     let sui = SuiClientBuilder::default().build_localnet().await?;
757    ///     let address = SuiAddress::from_str("0x0000....0000")?;
758    ///     let coins = sui
759    ///         .coin_read_api()
760    ///         .get_coins(address, None, None, None)
761    ///         .await?;
762    ///     Ok(())
763    /// }
764    /// ```
765    pub async fn get_coins(
766        &self,
767        owner: SuiAddress,
768        coin_type: Option<String>,
769        cursor: Option<String>,
770        limit: Option<usize>,
771    ) -> SuiRpcResult<CoinPage> {
772        Ok(self
773            .api
774            .http
775            .get_coins(owner, coin_type, cursor, limit)
776            .await?)
777    }
778    /// Return a paginated response with all the coins for the given address, or an error upon failure.
779    ///
780    /// This function includes all coins. If needed to filter by coin type, use the `get_coins` method instead.
781    ///
782    /// # Examples
783    ///
784    /// ```rust,no_run
785    /// use sui_sdk::SuiClientBuilder;
786    /// use sui_types::base_types::SuiAddress;
787    /// use std::str::FromStr;
788    ///
789    /// #[tokio::main]
790    /// async fn main() -> Result<(), anyhow::Error> {
791    ///     let sui = SuiClientBuilder::default().build_localnet().await?;
792    ///     let address = SuiAddress::from_str("0x0000....0000")?;
793    ///     let coins = sui
794    ///         .coin_read_api()
795    ///         .get_all_coins(address, None, None)
796    ///         .await?;
797    ///     Ok(())
798    /// }
799    /// ```
800    pub async fn get_all_coins(
801        &self,
802        owner: SuiAddress,
803        cursor: Option<String>,
804        limit: Option<usize>,
805    ) -> SuiRpcResult<CoinPage> {
806        Ok(self.api.http.get_all_coins(owner, cursor, limit).await?)
807    }
808
809    /// Return the coins for the given address as a stream.
810    ///
811    /// The coins can be filtered by `coin_type` (e.g., 0x168da5bf1f48dafc111b0a488fa454aca95e0b5e::usdc::USDC)
812    /// or use `None` for the default `Coin<SUI>`.
813    ///
814    /// # Examples
815    ///
816    /// ```rust,no_run
817    /// use sui_sdk::SuiClientBuilder;
818    /// use sui_types::base_types::SuiAddress;
819    /// use std::str::FromStr;
820    ///
821    /// #[tokio::main]
822    /// async fn main() -> Result<(), anyhow::Error> {
823    ///     let sui = SuiClientBuilder::default().build_localnet().await?;
824    ///     let address = SuiAddress::from_str("0x0000....0000")?;
825    ///     let coins = sui
826    ///         .coin_read_api()
827    ///         .get_coins_stream(address, None);
828    ///     Ok(())
829    /// }
830    /// ```
831    pub fn get_coins_stream(
832        &self,
833        owner: SuiAddress,
834        coin_type: Option<String>,
835    ) -> impl Stream<Item = Coin> + '_ {
836        stream::unfold(
837            (
838                vec![],
839                /* cursor */ None,
840                /* has_next_page */ true,
841                coin_type,
842            ),
843            move |(mut data, cursor, has_next_page, coin_type)| async move {
844                if let Some(item) = data.pop() {
845                    Some((item, (data, cursor, has_next_page, coin_type)))
846                } else if has_next_page {
847                    let page = self
848                        .get_coins(owner, coin_type.clone(), cursor, Some(100))
849                        .await
850                        .ok()?;
851                    let mut data = page.data;
852                    data.reverse();
853                    data.pop().map(|item| {
854                        (
855                            item,
856                            (data, page.next_cursor, page.has_next_page, coin_type),
857                        )
858                    })
859                } else {
860                    None
861                }
862            },
863        )
864    }
865
866    /// Return a list of coins for the given address, or an error upon failure.
867    ///
868    /// Note that the function selects coins to meet or exceed the requested `amount`.
869    /// If that it is not possible, it will fail with an insufficient fund error.
870    ///
871    /// The coins can be filtered by `coin_type` (e.g., 0x168da5bf1f48dafc111b0a488fa454aca95e0b5e::usdc::USDC)
872    /// or use `None` to use the default `Coin<SUI>`.
873    ///
874    /// # Examples
875    ///
876    /// ```rust,no_run
877    /// use sui_sdk::SuiClientBuilder;
878    /// use sui_types::base_types::SuiAddress;
879    /// use std::str::FromStr;
880    ///
881    /// #[tokio::main]
882    /// async fn main() -> Result<(), anyhow::Error> {
883    ///     let sui = SuiClientBuilder::default().build_localnet().await?;
884    ///     let address = SuiAddress::from_str("0x0000....0000")?;
885    ///     let coins = sui
886    ///         .coin_read_api()
887    ///         .select_coins(address, None, 5, vec![])
888    ///         .await?;
889    ///     Ok(())
890    /// }
891    /// ```
892    pub async fn select_coins(
893        &self,
894        address: SuiAddress,
895        coin_type: Option<String>,
896        amount: u128,
897        exclude: Vec<ObjectID>,
898    ) -> SuiRpcResult<Vec<Coin>> {
899        let mut total = 0u128;
900        let coins = self
901            .get_coins_stream(address, coin_type)
902            .filter(|coin: &Coin| future::ready(!exclude.contains(&coin.coin_object_id)))
903            .take_while(|coin: &Coin| {
904                let ready = future::ready(total < amount);
905                total += coin.balance as u128;
906                ready
907            })
908            .collect::<Vec<_>>()
909            .await;
910
911        if total < amount {
912            return Err(Error::InsufficientFund { address, amount });
913        }
914        Ok(coins)
915    }
916
917    /// Return the balance for the given coin type owned by address, or an error upon failure.
918    ///
919    /// Note that this function sums up all the balances of all the coins matching
920    /// the given coin type. By default, if `coin_type` is set to `None`,
921    /// it will use the default `Coin<SUI>`.
922    ///
923    /// # Examples
924    ///
925    /// ```rust,no_run
926    /// use sui_sdk::SuiClientBuilder;
927    /// use sui_types::base_types::SuiAddress;
928    /// use std::str::FromStr;
929    ///
930    /// #[tokio::main]
931    /// async fn main() -> Result<(), anyhow::Error> {
932    ///     let sui = SuiClientBuilder::default().build_localnet().await?;
933    ///     let address = SuiAddress::from_str("0x0000....0000")?;
934    ///     let balance = sui
935    ///         .coin_read_api()
936    ///         .get_balance(address, None)
937    ///         .await?;
938    ///     Ok(())
939    /// }
940    /// ```
941    pub async fn get_balance(
942        &self,
943        owner: SuiAddress,
944        coin_type: Option<String>,
945    ) -> SuiRpcResult<Balance> {
946        Ok(self.api.http.get_balance(owner, coin_type).await?)
947    }
948
949    /// Return a list of balances for each coin type owned by the given address,
950    /// or an error upon failure.
951    ///
952    /// Note that this function groups the coins by coin type, and sums up all their balances.
953    ///
954    /// # Examples
955    ///
956    /// ```rust,no_run
957    /// use sui_sdk::SuiClientBuilder;
958    /// use sui_types::base_types::SuiAddress;
959    /// use std::str::FromStr;
960    ///
961    /// #[tokio::main]
962    /// async fn main() -> Result<(), anyhow::Error> {
963    ///     let sui = SuiClientBuilder::default().build_localnet().await?;
964    ///     let address = SuiAddress::from_str("0x0000....0000")?;
965    ///     let all_balances = sui
966    ///         .coin_read_api()
967    ///         .get_all_balances(address)
968    ///         .await?;
969    ///     Ok(())
970    /// }
971    /// ```
972    pub async fn get_all_balances(&self, owner: SuiAddress) -> SuiRpcResult<Vec<Balance>> {
973        Ok(self.api.http.get_all_balances(owner).await?)
974    }
975
976    /// Return the coin metadata (name, symbol, description, decimals, etc.) for a given coin type,
977    /// or an error upon failure.
978    ///
979    /// # Examples
980    ///
981    /// ```rust,no_run
982    /// use sui_sdk::SuiClientBuilder;
983    /// #[tokio::main]
984    /// async fn main() -> Result<(), anyhow::Error> {
985    ///     let sui = SuiClientBuilder::default().build_localnet().await?;
986    ///     let coin_metadata = sui
987    ///         .coin_read_api()
988    ///         .get_coin_metadata("0x2::sui::SUI".to_string())
989    ///         .await?;
990    ///     Ok(())
991    /// }
992    /// ```
993    pub async fn get_coin_metadata(
994        &self,
995        coin_type: String,
996    ) -> SuiRpcResult<Option<SuiCoinMetadata>> {
997        Ok(self.api.http.get_coin_metadata(coin_type).await?)
998    }
999
1000    /// Return the total supply for a given coin type, or an error upon failure.
1001    ///
1002    /// # Examples
1003    ///
1004    /// ```rust,no_run
1005    /// use sui_sdk::SuiClientBuilder;
1006    ///
1007    /// #[tokio::main]
1008    /// async fn main() -> Result<(), anyhow::Error> {
1009    ///     let sui = SuiClientBuilder::default().build_localnet().await?;
1010    ///     let total_supply = sui
1011    ///         .coin_read_api()
1012    ///         .get_total_supply("0x2::sui::SUI".to_string())
1013    ///         .await?;
1014    ///     Ok(())
1015    /// }
1016    /// ```
1017    pub async fn get_total_supply(&self, coin_type: String) -> SuiRpcResult<Supply> {
1018        Ok(self.api.http.get_total_supply(coin_type).await?)
1019    }
1020}
1021
1022/// Event API provides the functionality to fetch, query, or subscribe to events on the Sui network.
1023#[derive(Clone)]
1024pub struct EventApi {
1025    api: Arc<RpcClient>,
1026}
1027
1028impl EventApi {
1029    pub(crate) fn new(api: Arc<RpcClient>) -> Self {
1030        Self { api }
1031    }
1032
1033    /// Return a stream of events, or an error upon failure.
1034    ///
1035    /// Subscription is only possible via WebSockets.
1036    /// For a list of possible event filters, see [EventFilter].
1037    ///
1038    /// # Examples
1039    ///
1040    /// ```rust, no_run
1041    /// use futures::StreamExt;
1042    /// use std::str::FromStr;
1043    /// use sui_json_rpc_types::EventFilter;
1044    /// use sui_sdk::SuiClientBuilder;
1045    /// use sui_types::base_types::SuiAddress;
1046    /// #[tokio::main]
1047    /// async fn main() -> Result<(), anyhow::Error> {
1048    ///     let sui = SuiClientBuilder::default()
1049    ///         .ws_url("wss://rpc.mainnet.sui.io:443")
1050    ///         .build("https://fullnode.mainnet.sui.io:443")
1051    ///         .await?;
1052    ///     let mut subscribe_all = sui
1053    ///         .event_api()
1054    ///         .subscribe_event(EventFilter::All([]))
1055    ///         .await?;
1056    ///     loop {
1057    ///         println!("{:?}", subscribe_all.next().await);
1058    ///     }
1059    ///     Ok(())
1060    /// }
1061    /// ```
1062    pub async fn subscribe_event(
1063        &self,
1064        filter: EventFilter,
1065    ) -> SuiRpcResult<impl Stream<Item = SuiRpcResult<SuiEvent>>> {
1066        match &self.api.ws {
1067            Some(c) => {
1068                let subscription: Subscription<SuiEvent> = c.subscribe_event(filter).await?;
1069                Ok(subscription.map(|item| Ok(item?)))
1070            }
1071            _ => Err(Error::Subscription(
1072                "Subscription only supported by WebSocket client.".to_string(),
1073            )),
1074        }
1075    }
1076
1077    /// Return a list of events for the given transaction digest, or an error upon failure.
1078    pub async fn get_events(&self, digest: TransactionDigest) -> SuiRpcResult<Vec<SuiEvent>> {
1079        Ok(self.api.http.get_events(digest).await?)
1080    }
1081
1082    /// Return a paginated response with events for the given event filter, or an error upon failure.
1083    ///
1084    /// The ordering of the events can be set with the `descending_order` argument.
1085    /// For a list of possible event filters, see [EventFilter].
1086    pub async fn query_events(
1087        &self,
1088        query: EventFilter,
1089        cursor: Option<EventID>,
1090        limit: Option<usize>,
1091        descending_order: bool,
1092    ) -> SuiRpcResult<EventPage> {
1093        Ok(self
1094            .api
1095            .http
1096            .query_events(query, cursor, limit, Some(descending_order))
1097            .await?)
1098    }
1099
1100    /// Return a stream of events for the given event filter.
1101    ///
1102    /// The ordering of the events can be set with the `descending_order` argument.
1103    /// For a list of possible event filters, see [EventFilter].
1104    pub fn get_events_stream(
1105        &self,
1106        query: EventFilter,
1107        cursor: Option<EventID>,
1108        descending_order: bool,
1109    ) -> impl Stream<Item = SuiEvent> + '_ {
1110        stream::unfold(
1111            (vec![], cursor, true, query),
1112            move |(mut data, cursor, first, query)| async move {
1113                if let Some(item) = data.pop() {
1114                    Some((item, (data, cursor, false, query)))
1115                } else if (cursor.is_none() && first) || cursor.is_some() {
1116                    let page = self
1117                        .query_events(query.clone(), cursor, Some(100), descending_order)
1118                        .await
1119                        .ok()?;
1120                    let mut data = page.data;
1121                    data.reverse();
1122                    data.pop()
1123                        .map(|item| (item, (data, page.next_cursor, false, query)))
1124                } else {
1125                    None
1126                }
1127            },
1128        )
1129    }
1130}
1131
1132/// Quorum API that provides functionality to execute a transaction block and submit it to the fullnode(s).
1133#[derive(Clone)]
1134pub struct QuorumDriverApi {
1135    api: Arc<RpcClient>,
1136}
1137
1138impl QuorumDriverApi {
1139    pub(crate) fn new(api: Arc<RpcClient>) -> Self {
1140        Self { api }
1141    }
1142
1143    /// Execute a transaction with a FullNode client. `request_type`
1144    /// defaults to `ExecuteTransactionRequestType::WaitForLocalExecution`.
1145    /// When `ExecuteTransactionRequestType::WaitForLocalExecution` is used,
1146    /// but returned `confirmed_local_execution` is false, the client will
1147    /// keep retry for WAIT_FOR_LOCAL_EXECUTION_RETRY_COUNT times. If it
1148    /// still fails, it will return an error.
1149    pub async fn execute_transaction_block(
1150        &self,
1151        tx: Transaction,
1152        options: SuiTransactionBlockResponseOptions,
1153        request_type: Option<ExecuteTransactionRequestType>,
1154    ) -> SuiRpcResult<SuiTransactionBlockResponse> {
1155        let tx_digest = *tx.digest();
1156        let (tx_bytes, signatures) = tx.to_tx_bytes_and_signatures();
1157        let request_type = request_type.unwrap_or_else(|| options.default_execution_request_type());
1158
1159        debug!(?tx_digest, "Submitting a transaction for execution");
1160        let start = Instant::now();
1161        let response = self
1162            .api
1163            .http
1164            .execute_transaction_block(
1165                tx_bytes.clone(),
1166                signatures.clone(),
1167                Some(options.clone()),
1168                // Ignore the request type as we emulate WaitForLocalExecution below.
1169                // It will default to WaitForEffectsCert on the RPC nodes.
1170                None,
1171            )
1172            .await?;
1173        debug!(?tx_digest, "Transaction executed");
1174
1175        if let ExecuteTransactionRequestType::WaitForEffectsCert = request_type {
1176            return Ok(response);
1177        }
1178
1179        // JSON-RPC ignores WaitForLocalExecution, so simulate it by polling for the transaction.
1180        debug!(?tx_digest, "Waiting for local execution on full node");
1181        let wait_for_local_execution_timeout: Duration = if cfg!(msim) {
1182            // In simtests, fullnodes can stop receiving checkpoints for > 30s.
1183            Duration::from_secs(120)
1184        } else {
1185            Duration::from_secs(60)
1186        };
1187        let mut poll_response = tokio::time::timeout(wait_for_local_execution_timeout, async {
1188            let mut backoff = mysten_common::backoff::ExponentialBackoff::new(
1189                WAIT_FOR_LOCAL_EXECUTION_MIN_INTERVAL,
1190                WAIT_FOR_LOCAL_EXECUTION_MAX_INTERVAL,
1191            );
1192            loop {
1193                // Intentionally waiting for a short delay (MIN_INTERVAL) before the 1st iteration,
1194                // to leave time for the checkpoint containing the transaction to be certified, propagate
1195                // to the full node, and get executed.
1196                tokio::time::sleep(backoff.next().unwrap()).await;
1197
1198                if let Ok(poll_response) = self
1199                    .api
1200                    .http
1201                    .get_transaction_block(*tx.digest(), Some(options.clone()))
1202                    .await
1203                {
1204                    break poll_response;
1205                } else {
1206                    debug!(
1207                        ?tx_digest,
1208                        "Failed to get transaction content from the full node"
1209                    );
1210                }
1211            }
1212        })
1213        .await
1214        .map_err(|_| {
1215            Error::FailToConfirmTransactionStatus(*tx.digest(), start.elapsed().as_secs())
1216        })?;
1217
1218        poll_response.confirmed_local_execution = Some(true);
1219        Ok(poll_response)
1220    }
1221}
1222
1223/// Governance API provides the staking functionality.
1224#[derive(Debug, Clone)]
1225pub struct GovernanceApi {
1226    api: Arc<RpcClient>,
1227}
1228
1229impl GovernanceApi {
1230    pub(crate) fn new(api: Arc<RpcClient>) -> Self {
1231        Self { api }
1232    }
1233
1234    /// Return a list of [DelegatedStake] objects for the given address, or an error upon failure.
1235    pub async fn get_stakes(&self, owner: SuiAddress) -> SuiRpcResult<Vec<DelegatedStake>> {
1236        Ok(self.api.http.get_stakes(owner).await?)
1237    }
1238
1239    /// Return the [SuiCommittee] information for the given `epoch`, or an error upon failure.
1240    ///
1241    /// The argument `epoch` is the known epoch id or `None` for the current epoch.
1242    ///
1243    /// # Examples
1244    ///
1245    /// ```rust,no_run
1246    /// use sui_sdk::SuiClientBuilder;
1247    ///
1248    /// #[tokio::main]
1249    /// async fn main() -> Result<(), anyhow::Error> {
1250    ///     let sui = SuiClientBuilder::default().build_localnet().await?;
1251    ///     let committee_info = sui
1252    ///         .governance_api()
1253    ///         .get_committee_info(None)
1254    ///         .await?;
1255    ///     Ok(())
1256    /// }
1257    /// ```
1258    pub async fn get_committee_info(
1259        &self,
1260        epoch: Option<BigInt<u64>>,
1261    ) -> SuiRpcResult<SuiCommittee> {
1262        Ok(self.api.http.get_committee_info(epoch).await?)
1263    }
1264
1265    /// Return the latest SUI system state object on-chain, or an error upon failure.
1266    ///
1267    /// Use this method to access system's information, such as the current epoch,
1268    /// the protocol version, the reference gas price, the total stake, active validators,
1269    /// and much more. See the [SuiSystemStateSummary] for all the available fields.
1270    pub async fn get_latest_sui_system_state(&self) -> SuiRpcResult<SuiSystemStateSummary> {
1271        Ok(self.api.http.get_latest_sui_system_state().await?)
1272    }
1273
1274    /// Return the reference gas price for the network, or an error upon failure.
1275    pub async fn get_reference_gas_price(&self) -> SuiRpcResult<u64> {
1276        Ok(*self.api.http.get_reference_gas_price().await?)
1277    }
1278}