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