sui_sdk/
apis.rs

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