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