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}