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}