sui_graphql_client/
lib.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4#![doc = include_str!("../README.md")]
5
6pub mod error;
7pub mod faucet;
8pub mod query_types;
9pub mod streams;
10
11use error::Error;
12use query_types::ActiveValidatorsArgs;
13use query_types::ActiveValidatorsQuery;
14use query_types::BalanceArgs;
15use query_types::BalanceQuery;
16use query_types::ChainIdentifierQuery;
17use query_types::CheckpointArgs;
18use query_types::CheckpointId;
19use query_types::CheckpointQuery;
20use query_types::CheckpointsArgs;
21use query_types::CheckpointsQuery;
22use query_types::CoinMetadata;
23use query_types::CoinMetadataArgs;
24use query_types::CoinMetadataQuery;
25use query_types::DefaultSuinsNameQuery;
26use query_types::DefaultSuinsNameQueryArgs;
27use query_types::DryRunArgs;
28use query_types::DryRunQuery;
29use query_types::DynamicFieldArgs;
30use query_types::DynamicFieldConnectionArgs;
31use query_types::DynamicFieldQuery;
32use query_types::DynamicFieldsOwnerQuery;
33use query_types::DynamicObjectFieldQuery;
34use query_types::Epoch;
35use query_types::EpochArgs;
36use query_types::EpochQuery;
37use query_types::EpochSummaryQuery;
38use query_types::EventFilter;
39use query_types::EventsQuery;
40use query_types::EventsQueryArgs;
41use query_types::ExecuteTransactionArgs;
42use query_types::ExecuteTransactionQuery;
43use query_types::LatestPackageQuery;
44use query_types::MoveFunction;
45use query_types::MoveModule;
46use query_types::MovePackageVersionFilter;
47use query_types::NormalizedMoveFunctionQuery;
48use query_types::NormalizedMoveFunctionQueryArgs;
49use query_types::NormalizedMoveModuleQuery;
50use query_types::NormalizedMoveModuleQueryArgs;
51use query_types::ObjectFilter;
52use query_types::ObjectQuery;
53use query_types::ObjectQueryArgs;
54use query_types::ObjectsQuery;
55use query_types::ObjectsQueryArgs;
56use query_types::PackageArgs;
57use query_types::PackageByNameArgs;
58use query_types::PackageByNameQuery;
59use query_types::PackageCheckpointFilter;
60use query_types::PackageQuery;
61use query_types::PackageVersionsArgs;
62use query_types::PackageVersionsQuery;
63use query_types::PackagesQuery;
64use query_types::PackagesQueryArgs;
65use query_types::PageInfo;
66use query_types::ProtocolConfigQuery;
67use query_types::ProtocolConfigs;
68use query_types::ProtocolVersionArgs;
69use query_types::ResolveSuinsQuery;
70use query_types::ResolveSuinsQueryArgs;
71use query_types::ServiceConfig;
72use query_types::ServiceConfigQuery;
73use query_types::TransactionBlockArgs;
74use query_types::TransactionBlockEffectsQuery;
75use query_types::TransactionBlockQuery;
76use query_types::TransactionBlocksEffectsQuery;
77use query_types::TransactionBlocksQuery;
78use query_types::TransactionBlocksQueryArgs;
79use query_types::TransactionMetadata;
80use query_types::TransactionsFilter;
81use query_types::Validator;
82use streams::stream_paginated_query;
83
84use sui_types::framework::Coin;
85use sui_types::Address;
86use sui_types::CheckpointDigest;
87use sui_types::CheckpointSequenceNumber;
88use sui_types::CheckpointSummary;
89use sui_types::Event;
90use sui_types::MovePackage;
91use sui_types::Object;
92use sui_types::SignedTransaction;
93use sui_types::Transaction;
94use sui_types::TransactionDigest;
95use sui_types::TransactionEffects;
96use sui_types::TransactionKind;
97use sui_types::TypeTag;
98use sui_types::UserSignature;
99
100use base64ct::Encoding;
101use cynic::serde;
102use cynic::GraphQlResponse;
103use cynic::MutationBuilder;
104use cynic::Operation;
105use cynic::QueryBuilder;
106use futures::Stream;
107use reqwest::Url;
108use serde::de::DeserializeOwned;
109use serde::Serialize;
110use std::str::FromStr;
111
112use crate::error::Kind;
113use crate::error::Result;
114use crate::query_types::CheckpointTotalTxQuery;
115use query_types::EpochsArgs;
116use query_types::EpochsQuery;
117use query_types::TransactionBlockWithEffectsQuery;
118use query_types::TransactionBlocksWithEffectsQuery;
119
120const DEFAULT_ITEMS_PER_PAGE: i32 = 10;
121const MAINNET_HOST: &str = "https://sui-mainnet.mystenlabs.com/graphql";
122const TESTNET_HOST: &str = "https://sui-testnet.mystenlabs.com/graphql";
123const DEVNET_HOST: &str = "https://sui-devnet.mystenlabs.com/graphql";
124const LOCAL_HOST: &str = "http://localhost:9125/graphql";
125static USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"),);
126
127// ===========================================================================
128// Output Types
129// ===========================================================================
130
131/// The result of a dry run, which includes the effects of the transaction and any errors that may
132/// have occurred.
133#[derive(Debug)]
134pub struct DryRunResult {
135    pub effects: Option<TransactionEffects>,
136    pub error: Option<String>,
137}
138
139pub struct TransactionDataEffects {
140    pub tx: SignedTransaction,
141    pub effects: TransactionEffects,
142}
143
144/// The name part of a dynamic field, including its type, bcs, and json representation.
145#[derive(Clone, Debug)]
146pub struct DynamicFieldName {
147    /// The type name of this dynamic field name
148    pub type_: TypeTag,
149    /// The bcs bytes of this dynamic field name
150    pub bcs: Vec<u8>,
151    /// The json representation of the dynamic field name
152    pub json: Option<serde_json::Value>,
153}
154
155/// The output of a dynamic field query, that includes the name, value, and value's json
156/// representation.
157#[derive(Clone, Debug)]
158pub struct DynamicFieldOutput {
159    /// The name of the dynamic field
160    pub name: DynamicFieldName,
161    /// The dynamic field value typename and bcs
162    pub value: Option<(TypeTag, Vec<u8>)>,
163    /// The json representation of the dynamic field value object
164    pub value_as_json: Option<serde_json::Value>,
165}
166
167/// Helper struct for passing a value that has a type that implements Serialize, for the dynamic
168/// fields API.
169pub struct NameValue(Vec<u8>);
170/// Helper struct for passing a raw bcs value.
171pub struct BcsName(pub Vec<u8>);
172
173#[derive(Clone, Debug)]
174/// A page of items returned by the GraphQL server.
175pub struct Page<T> {
176    /// Information about the page, such as the cursor and whether there are more pages.
177    page_info: PageInfo,
178    /// The data returned by the server.
179    data: Vec<T>,
180}
181
182impl<T> Page<T> {
183    /// Return the page information.
184    pub fn page_info(&self) -> &PageInfo {
185        &self.page_info
186    }
187
188    /// Return the data in the page.
189    pub fn data(&self) -> &[T] {
190        &self.data
191    }
192
193    /// Create a new page with the provided data and page information.
194    pub fn new(page_info: PageInfo, data: Vec<T>) -> Self {
195        Self { page_info, data }
196    }
197
198    /// Check if the page has no data.
199    pub fn is_empty(&self) -> bool {
200        self.data.is_empty()
201    }
202
203    /// Create a page with no data.
204    pub fn new_empty() -> Self {
205        Self::new(PageInfo::default(), vec![])
206    }
207
208    /// Return a tuple of page info and the data.
209    pub fn into_parts(self) -> (PageInfo, Vec<T>) {
210        (self.page_info, self.data)
211    }
212}
213
214/// Pagination direction.
215#[derive(Clone, Debug, Default)]
216pub enum Direction {
217    #[default]
218    Forward,
219    Backward,
220}
221
222/// Pagination options for querying the GraphQL server. It defaults to forward pagination with the
223/// GraphQL server's max page size.
224#[derive(Clone, Debug, Default)]
225pub struct PaginationFilter {
226    /// The direction of pagination.
227    pub direction: Direction,
228    /// An opaque cursor used for pagination.
229    pub cursor: Option<String>,
230    /// The maximum number of items to return. If this is ommitted, it will lazily query the
231    /// service configuration for the max page size.
232    pub limit: Option<i32>,
233}
234
235impl<T: Serialize> From<T> for NameValue {
236    fn from(value: T) -> Self {
237        NameValue(bcs::to_bytes(&value).unwrap())
238    }
239}
240
241impl From<BcsName> for NameValue {
242    fn from(value: BcsName) -> Self {
243        NameValue(value.0)
244    }
245}
246
247impl DynamicFieldOutput {
248    /// Deserialize the name of the dynamic field into the specified type.
249    pub fn deserialize_name<T: DeserializeOwned>(&self, expected_type: &TypeTag) -> Result<T> {
250        assert_eq!(
251            expected_type, &self.name.type_,
252            "Expected type {}, but got {}",
253            expected_type, &self.name.type_
254        );
255
256        let bcs = &self.name.bcs;
257        bcs::from_bytes::<T>(bcs).map_err(Into::into)
258    }
259
260    /// Deserialize the value of the dynamic field into the specified type.
261    pub fn deserialize_value<T: DeserializeOwned>(&self, expected_type: &TypeTag) -> Result<T> {
262        let typetag = self.value.as_ref().map(|(typename, _)| typename);
263        assert_eq!(
264            Some(&expected_type),
265            typetag.as_ref(),
266            "Expected type {expected_type}, but got {typetag:?}"
267        );
268
269        if let Some((_, bcs)) = &self.value {
270            bcs::from_bytes::<T>(bcs).map_err(Into::into)
271        } else {
272            Err(Error::from_error(Kind::Deserialization, "Value is missing"))
273        }
274    }
275}
276
277/// The GraphQL client for interacting with the Sui blockchain.
278/// By default, it uses the `reqwest` crate as the HTTP client.
279pub struct Client {
280    /// The URL of the GraphQL server.
281    rpc: Url,
282    /// The reqwest client.
283    inner: reqwest::Client,
284
285    service_config: std::sync::OnceLock<ServiceConfig>,
286}
287
288impl Client {
289    // ===========================================================================
290    // Client Misc API
291    // ===========================================================================
292
293    /// Create a new GraphQL client with the provided server address.
294    pub fn new(server: &str) -> Result<Self> {
295        let rpc = reqwest::Url::parse(server)?;
296
297        let client = Client {
298            rpc,
299            inner: reqwest::Client::builder().user_agent(USER_AGENT).build()?,
300            service_config: Default::default(),
301        };
302        Ok(client)
303    }
304
305    /// Create a new GraphQL client connected to the `mainnet` GraphQL server: {MAINNET_HOST}.
306    pub fn new_mainnet() -> Self {
307        Self::new(MAINNET_HOST).expect("Invalid mainnet URL")
308    }
309
310    /// Create a new GraphQL client connected to the `testnet` GraphQL server: {TESTNET_HOST}.
311    pub fn new_testnet() -> Self {
312        Self::new(TESTNET_HOST).expect("Invalid testnet URL")
313    }
314
315    /// Create a new GraphQL client connected to the `devnet` GraphQL server: {DEVNET_HOST}.
316    pub fn new_devnet() -> Self {
317        Self::new(DEVNET_HOST).expect("Invalid devnet URL")
318    }
319
320    /// Create a new GraphQL client connected to the `localhost` GraphQL server:
321    /// {DEFAULT_LOCAL_HOST}.
322    pub fn new_localhost() -> Self {
323        Self::new(LOCAL_HOST).expect("Invalid localhost URL")
324    }
325
326    /// Set the server address for the GraphQL GraphQL client. It should be a valid URL with a host and
327    /// optionally a port number.
328    pub fn set_rpc_server(&mut self, server: &str) -> Result<()> {
329        let rpc = reqwest::Url::parse(server)?;
330        self.rpc = rpc;
331        Ok(())
332    }
333
334    /// Return the URL for the GraphQL server.
335    fn rpc_server(&self) -> &str {
336        self.rpc.as_str()
337    }
338
339    /// Handle pagination filters and return the appropriate values (after, before, first, last).
340    /// If limit is omitted, it will use the max page size from the service config.
341    pub async fn pagination_filter(
342        &self,
343        pagination_filter: PaginationFilter,
344    ) -> (Option<String>, Option<String>, Option<i32>, Option<i32>) {
345        let limit = pagination_filter
346            .limit
347            .unwrap_or(self.max_page_size().await.unwrap_or(DEFAULT_ITEMS_PER_PAGE));
348
349        let (after, before, first, last) = match pagination_filter.direction {
350            Direction::Forward => (pagination_filter.cursor, None, Some(limit), None),
351            Direction::Backward => (None, pagination_filter.cursor, None, Some(limit)),
352        };
353        (after, before, first, last)
354    }
355
356    /// Lazily fetch the max page size
357    pub async fn max_page_size(&self) -> Result<i32> {
358        self.service_config().await.map(|cfg| cfg.max_page_size)
359    }
360
361    /// Run a query on the GraphQL server and return the response.
362    /// This method returns [`cynic::GraphQlResponse`]  over the query type `T`, and it is
363    /// intended to be used with custom queries.
364    pub async fn run_query<T, V>(&self, operation: &Operation<T, V>) -> Result<GraphQlResponse<T>>
365    where
366        T: serde::de::DeserializeOwned,
367        V: serde::Serialize,
368    {
369        let res = self
370            .inner
371            .post(self.rpc_server())
372            .json(&operation)
373            .send()
374            .await?
375            .json::<GraphQlResponse<T>>()
376            .await?;
377        Ok(res)
378    }
379
380    // ===========================================================================
381    // Network info API
382    // ===========================================================================
383
384    /// Get the chain identifier.
385    pub async fn chain_id(&self) -> Result<String> {
386        let operation = ChainIdentifierQuery::build(());
387        let response = self.run_query(&operation).await?;
388
389        if let Some(errors) = response.errors {
390            return Err(Error::graphql_error(errors));
391        }
392
393        response
394            .data
395            .map(|e| e.chain_identifier)
396            .ok_or_else(Error::empty_response_error)
397    }
398
399    /// Get the reference gas price for the provided epoch or the last known one if no epoch is
400    /// provided.
401    ///
402    /// This will return `Ok(None)` if the epoch requested is not available in the GraphQL service
403    /// (e.g., due to pruning).
404    pub async fn reference_gas_price(&self, epoch: Option<u64>) -> Result<Option<u64>> {
405        let operation = EpochSummaryQuery::build(EpochArgs { id: epoch });
406        let response = self.run_query(&operation).await?;
407
408        if let Some(errors) = response.errors {
409            return Err(Error::graphql_error(errors));
410        }
411
412        response
413            .data
414            .and_then(|e| e.epoch)
415            .and_then(|e| e.reference_gas_price)
416            .map(|x| x.try_into())
417            .transpose()
418    }
419
420    /// Get the protocol configuration.
421    pub async fn protocol_config(&self, version: Option<u64>) -> Result<Option<ProtocolConfigs>> {
422        let operation = ProtocolConfigQuery::build(ProtocolVersionArgs { id: version });
423        let response = self.run_query(&operation).await?;
424        Ok(response.data.map(|p| p.protocol_config))
425    }
426
427    /// Get the GraphQL service configuration, including complexity limits, read and mutation limits,
428    /// supported versions, and others.
429    pub async fn service_config(&self) -> Result<&ServiceConfig> {
430        // If the value is already initialized, return it
431        if let Some(service_config) = self.service_config.get() {
432            return Ok(service_config);
433        }
434
435        // Otherwise, fetch and initialize it
436        let service_config = {
437            let operation = ServiceConfigQuery::build(());
438            let response = self.run_query(&operation).await?;
439
440            if let Some(errors) = response.errors {
441                return Err(Error::graphql_error(errors));
442            }
443
444            response
445                .data
446                .map(|s| s.service_config)
447                .ok_or_else(Error::empty_response_error)?
448        };
449
450        let service_config = self.service_config.get_or_init(move || service_config);
451
452        Ok(service_config)
453    }
454
455    /// Get the list of active validators for the provided epoch, including related metadata.
456    /// If no epoch is provided, it will return the active validators for the current epoch.
457    pub async fn active_validators(
458        &self,
459        epoch: Option<u64>,
460        pagination_filter: PaginationFilter,
461    ) -> Result<Page<Validator>> {
462        let (after, before, first, last) = self.pagination_filter(pagination_filter).await;
463
464        let operation = ActiveValidatorsQuery::build(ActiveValidatorsArgs {
465            id: epoch,
466            after: after.as_deref(),
467            before: before.as_deref(),
468            first,
469            last,
470        });
471        let response = self.run_query(&operation).await?;
472
473        if let Some(errors) = response.errors {
474            return Err(Error::graphql_error(errors));
475        }
476
477        if let Some(validators) = response
478            .data
479            .and_then(|d| d.epoch)
480            .and_then(|v| v.validator_set)
481        {
482            let page_info = validators.active_validators.page_info;
483            let nodes = validators
484                .active_validators
485                .nodes
486                .into_iter()
487                .collect::<Vec<_>>();
488            Ok(Page::new(page_info, nodes))
489        } else {
490            Ok(Page::new_empty())
491        }
492    }
493
494    /// The total number of transaction blocks in the network by the end of the provided
495    /// checkpoint digest.
496    pub async fn total_transaction_blocks_by_digest(
497        &self,
498        digest: CheckpointDigest,
499    ) -> Result<Option<u64>> {
500        self.internal_total_transaction_blocks(Some(digest.to_string()), None)
501            .await
502    }
503
504    /// The total number of transaction blocks in the network by the end of the provided checkpoint
505    /// sequence number.
506    pub async fn total_transaction_blocks_by_seq_num(&self, seq_num: u64) -> Result<Option<u64>> {
507        self.internal_total_transaction_blocks(None, Some(seq_num))
508            .await
509    }
510
511    /// The total number of transaction blocks in the network by the end of the last known
512    /// checkpoint.
513    pub async fn total_transaction_blocks(&self) -> Result<Option<u64>> {
514        self.internal_total_transaction_blocks(None, None).await
515    }
516
517    /// Internal function to get the total number of transaction blocks based on the provided
518    /// checkpoint digest or sequence number.
519    async fn internal_total_transaction_blocks(
520        &self,
521        digest: Option<String>,
522        seq_num: Option<u64>,
523    ) -> Result<Option<u64>> {
524        if digest.is_some() && seq_num.is_some() {
525            return Err(Error::from_error(
526                Kind::Other,
527                "Conflicting arguments: either digest or seq_num can be provided, but not both.",
528            ));
529        }
530
531        let operation = CheckpointTotalTxQuery::build(CheckpointArgs {
532            id: CheckpointId {
533                digest,
534                sequence_number: seq_num,
535            },
536        });
537        let response = self.run_query(&operation).await?;
538
539        if let Some(errors) = response.errors {
540            return Err(Error::graphql_error(errors));
541        }
542
543        Ok(response
544            .data
545            .and_then(|x| x.checkpoint)
546            .and_then(|c| c.network_total_transactions))
547    }
548
549    // ===========================================================================
550    // Balance API
551    // ===========================================================================
552
553    /// Get the balance of all the coins owned by address for the provided coin type.
554    /// Coin type will default to `0x2::coin::Coin<0x2::sui::SUI>` if not provided.
555    pub async fn balance(&self, address: Address, coin_type: Option<&str>) -> Result<Option<u128>> {
556        let operation = BalanceQuery::build(BalanceArgs {
557            address,
558            coin_type: coin_type.map(|x| x.to_string()),
559        });
560        let response = self.run_query(&operation).await?;
561
562        if let Some(errors) = response.errors {
563            return Err(Error::graphql_error(errors));
564        }
565
566        let total_balance = response
567            .data
568            .map(|b| b.owner.and_then(|o| o.balance.map(|b| b.total_balance)))
569            .ok_or_else(Error::empty_response_error)?
570            .flatten()
571            .map(|x| x.0.parse::<u128>())
572            .transpose()?;
573        Ok(total_balance)
574    }
575
576    // ===========================================================================
577    // Coin API
578    // ===========================================================================
579
580    /// Get the list of coins for the specified address.
581    ///
582    /// If `coin_type` is not provided, it will default to `0x2::coin::Coin`, which will return all
583    /// coins. For SUI coin, pass in the coin type: `0x2::coin::Coin<0x2::sui::SUI>`.
584    pub async fn coins(
585        &self,
586        owner: Address,
587        coin_type: Option<&str>,
588        pagination_filter: PaginationFilter,
589    ) -> Result<Page<Coin<'static>>> {
590        let response = self
591            .objects(
592                Some(ObjectFilter {
593                    type_: Some(coin_type.unwrap_or("0x2::coin::Coin")),
594                    owner: Some(owner),
595                    object_ids: None,
596                }),
597                pagination_filter,
598            )
599            .await?;
600
601        Ok(Page::new(
602            response.page_info,
603            response
604                .data
605                .iter()
606                .flat_map(Coin::try_from_object)
607                .map(|c| c.into_owned())
608                .collect::<Vec<_>>(),
609        ))
610    }
611
612    /// Get the list of coins for the specified address as a stream.
613    ///
614    /// If `coin_type` is not provided, it will default to `0x2::coin::Coin`, which will return all
615    /// coins. For SUI coin, pass in the coin type: `0x2::coin::Coin<0x2::sui::SUI>`.
616    pub async fn coins_stream(
617        &self,
618        address: Address,
619        coin_type: Option<&'static str>,
620        streaming_direction: Direction,
621    ) -> impl Stream<Item = Result<Coin<'static>>> + use<'_> {
622        stream_paginated_query(
623            move |filter| self.coins(address, coin_type, filter),
624            streaming_direction,
625        )
626    }
627
628    /// Get the coin metadata for the coin type.
629    pub async fn coin_metadata(&self, coin_type: &str) -> Result<Option<CoinMetadata>> {
630        let operation = CoinMetadataQuery::build(CoinMetadataArgs { coin_type });
631        let response = self.run_query(&operation).await?;
632
633        if let Some(errors) = response.errors {
634            return Err(Error::graphql_error(errors));
635        }
636
637        Ok(response.data.and_then(|x| x.coin_metadata))
638    }
639
640    /// Get total supply for the coin type.
641    pub async fn total_supply(&self, coin_type: &str) -> Result<Option<u64>> {
642        let coin_metadata = self.coin_metadata(coin_type).await?;
643
644        coin_metadata
645            .and_then(|c| c.supply)
646            .map(|c| c.try_into())
647            .transpose()
648    }
649
650    // ===========================================================================
651    // Checkpoints API
652    // ===========================================================================
653
654    /// Get the [`CheckpointSummary`] for a given checkpoint digest or checkpoint id. If none is
655    /// provided, it will use the last known checkpoint id.
656    pub async fn checkpoint(
657        &self,
658        digest: Option<CheckpointDigest>,
659        seq_num: Option<u64>,
660    ) -> Result<Option<CheckpointSummary>> {
661        if digest.is_some() && seq_num.is_some() {
662            return Err(Error::from_error(
663                Kind::Other,
664                "either digest or seq_num must be provided",
665            ));
666        }
667
668        let operation = CheckpointQuery::build(CheckpointArgs {
669            id: CheckpointId {
670                digest: digest.map(|d| d.to_string()),
671                sequence_number: seq_num,
672            },
673        });
674        let response = self.run_query(&operation).await?;
675
676        if let Some(errors) = response.errors {
677            return Err(Error::graphql_error(errors));
678        }
679
680        response
681            .data
682            .map(|c| c.checkpoint.map(|c| c.try_into()).transpose())
683            .ok_or(Error::empty_response_error())?
684    }
685
686    /// Get a page of [`CheckpointSummary`] for the provided parameters.
687    pub async fn checkpoints(
688        &self,
689        pagination_filter: PaginationFilter,
690    ) -> Result<Page<CheckpointSummary>> {
691        let (after, before, first, last) = self.pagination_filter(pagination_filter).await;
692
693        let operation = CheckpointsQuery::build(CheckpointsArgs {
694            after: after.as_deref(),
695            before: before.as_deref(),
696            first,
697            last,
698        });
699        let response = self.run_query(&operation).await?;
700
701        if let Some(errors) = response.errors {
702            return Err(Error::graphql_error(errors));
703        }
704
705        if let Some(checkpoints) = response.data {
706            let cc = checkpoints.checkpoints;
707            let page_info = cc.page_info;
708            let nodes = cc
709                .nodes
710                .into_iter()
711                .map(|c| c.try_into())
712                .collect::<Result<Vec<CheckpointSummary>, _>>()?;
713
714            Ok(Page::new(page_info, nodes))
715        } else {
716            Ok(Page::new_empty())
717        }
718    }
719
720    /// Get a stream of [`CheckpointSummary`]. Note that this will fetch all checkpoints which may
721    /// trigger a lot of requests.
722    pub async fn checkpoints_stream(
723        &self,
724        streaming_direction: Direction,
725    ) -> impl Stream<Item = Result<CheckpointSummary>> + '_ {
726        stream_paginated_query(move |filter| self.checkpoints(filter), streaming_direction)
727    }
728
729    /// Return the sequence number of the latest checkpoint that has been executed.
730    pub async fn latest_checkpoint_sequence_number(
731        &self,
732    ) -> Result<Option<CheckpointSequenceNumber>> {
733        Ok(self
734            .checkpoint(None, None)
735            .await?
736            .map(|c| c.sequence_number))
737    }
738
739    // ===========================================================================
740    // Dynamic Field(s) API
741    // ===========================================================================
742
743    /// Access a dynamic field on an object using its name. Names are arbitrary Move values whose
744    /// type have copy, drop, and store, and are specified using their type, and their BCS
745    /// contents, Base64 encoded.
746    ///
747    /// The `name` argument can be either a [`BcsName`] for passing raw bcs bytes or a type that
748    /// implements Serialize.
749    ///
750    /// This returns [`DynamicFieldOutput`] which contains the name, the value as json, and object.
751    ///
752    /// # Example
753    /// ```rust,ignore
754    /// let client = sui_graphql_client::Client::new_devnet();
755    /// let address = Address::from_str("0x5").unwrap();
756    /// let df = client.dynamic_field_with_name(address, "u64", 2u64).await.unwrap();
757    ///
758    /// // alternatively, pass in the bcs bytes
759    /// let bcs = base64ct::Base64::decode_vec("AgAAAAAAAAA=").unwrap();
760    /// let df = client.dynamic_field(address, "u64", BcsName(bcs)).await.unwrap();
761    /// ```
762    pub async fn dynamic_field(
763        &self,
764        address: Address,
765        type_: TypeTag,
766        name: impl Into<NameValue>,
767    ) -> Result<Option<DynamicFieldOutput>> {
768        let bcs = name.into().0;
769        let operation = DynamicFieldQuery::build(DynamicFieldArgs {
770            address,
771            name: crate::query_types::DynamicFieldName {
772                type_: type_.to_string(),
773                bcs: crate::query_types::Base64(base64ct::Base64::encode_string(&bcs)),
774            },
775        });
776
777        let response = self.run_query(&operation).await?;
778
779        if let Some(errors) = response.errors {
780            return Err(Error::graphql_error(errors));
781        }
782
783        let result = response
784            .data
785            .and_then(|d| d.owner)
786            .and_then(|o| o.dynamic_field)
787            .map(|df| df.try_into())
788            .transpose()?;
789
790        Ok(result)
791    }
792
793    /// Access a dynamic object field on an object using its name. Names are arbitrary Move values whose
794    /// type have copy, drop, and store, and are specified using their type, and their BCS
795    /// contents, Base64 encoded.
796    ///
797    /// The `name` argument can be either a [`BcsName`] for passing raw bcs bytes or a type that
798    /// implements Serialize.
799    ///
800    /// This returns [`DynamicFieldOutput`] which contains the name, the value as json, and object.
801    pub async fn dynamic_object_field(
802        &self,
803        address: Address,
804        type_: TypeTag,
805        name: impl Into<NameValue>,
806    ) -> Result<Option<DynamicFieldOutput>> {
807        let bcs = name.into().0;
808        let operation = DynamicObjectFieldQuery::build(DynamicFieldArgs {
809            address,
810            name: crate::query_types::DynamicFieldName {
811                type_: type_.to_string(),
812                bcs: crate::query_types::Base64(base64ct::Base64::encode_string(&bcs)),
813            },
814        });
815
816        let response = self.run_query(&operation).await?;
817
818        if let Some(errors) = response.errors {
819            return Err(Error::graphql_error(errors));
820        }
821
822        let result: Option<DynamicFieldOutput> = response
823            .data
824            .and_then(|d| d.owner)
825            .and_then(|o| o.dynamic_object_field)
826            .map(|df| df.try_into())
827            .transpose()?;
828        Ok(result)
829    }
830
831    /// Get a page of dynamic fields for the provided address. Note that this will also fetch
832    /// dynamic fields on wrapped objects.
833    ///
834    /// This returns [`Page`] of [`DynamicFieldOutput`]s.
835    pub async fn dynamic_fields(
836        &self,
837        address: Address,
838        pagination_filter: PaginationFilter,
839    ) -> Result<Page<DynamicFieldOutput>> {
840        let (after, before, first, last) = self.pagination_filter(pagination_filter).await;
841        let operation = DynamicFieldsOwnerQuery::build(DynamicFieldConnectionArgs {
842            address,
843            after: after.as_deref(),
844            before: before.as_deref(),
845            first,
846            last,
847        });
848        let response = self.run_query(&operation).await?;
849
850        if let Some(errors) = response.errors {
851            return Err(Error::graphql_error(errors));
852        }
853
854        let Some(DynamicFieldsOwnerQuery { owner: Some(dfs) }) = response.data else {
855            return Ok(Page::new_empty());
856        };
857
858        Ok(Page::new(
859            dfs.dynamic_fields.page_info,
860            dfs.dynamic_fields
861                .nodes
862                .into_iter()
863                .map(TryInto::try_into)
864                .collect::<Result<Vec<_>>>()?,
865        ))
866    }
867
868    /// Get a stream of dynamic fields for the provided address. Note that this will also fetch
869    /// dynamic fields on wrapped objects.
870    pub async fn dynamic_fields_stream(
871        &self,
872        address: Address,
873        streaming_direction: Direction,
874    ) -> impl Stream<Item = Result<DynamicFieldOutput>> + '_ {
875        stream_paginated_query(
876            move |filter| self.dynamic_fields(address, filter),
877            streaming_direction,
878        )
879    }
880
881    // ===========================================================================
882    // Epoch API
883    // ===========================================================================
884
885    /// Return the epoch information for the provided epoch. If no epoch is provided, it will
886    /// return the last known epoch.
887    pub async fn epoch(&self, epoch: Option<u64>) -> Result<Option<Epoch>> {
888        let operation = EpochQuery::build(EpochArgs { id: epoch });
889        let response = self.run_query(&operation).await?;
890
891        if let Some(errors) = response.errors {
892            return Err(Error::graphql_error(errors));
893        }
894
895        Ok(response.data.and_then(|d| d.epoch))
896    }
897
898    /// Return a page of epochs.
899    pub async fn epochs(&self, pagination_filter: PaginationFilter) -> Result<Page<Epoch>> {
900        let (after, before, first, last) = self.pagination_filter(pagination_filter).await;
901        let operation = EpochsQuery::build(EpochsArgs {
902            after: after.as_deref(),
903            before: before.as_deref(),
904            first,
905            last,
906        });
907        let response = self.run_query(&operation).await?;
908
909        if let Some(errors) = response.errors {
910            return Err(Error::graphql_error(errors));
911        }
912
913        if let Some(epochs) = response.data {
914            Ok(Page::new(epochs.epochs.page_info, epochs.epochs.nodes))
915        } else {
916            Ok(Page::new_empty())
917        }
918    }
919
920    /// Return a stream of epochs based on the (optional) object filter.
921    pub async fn epochs_stream(
922        &self,
923        streaming_direction: Direction,
924    ) -> impl Stream<Item = Result<Epoch>> + '_ {
925        stream_paginated_query(
926            move |pag_filter| self.epochs(pag_filter),
927            streaming_direction,
928        )
929    }
930
931    /// Return the number of checkpoints in this epoch. This will return `Ok(None)` if the epoch
932    /// requested is not available in the GraphQL service (e.g., due to pruning).
933    pub async fn epoch_total_checkpoints(&self, epoch: Option<u64>) -> Result<Option<u64>> {
934        let response = self.epoch_summary(epoch).await?;
935
936        if let Some(errors) = response.errors {
937            return Err(Error::graphql_error(errors));
938        }
939
940        Ok(response
941            .data
942            .and_then(|d| d.epoch)
943            .and_then(|e| e.total_checkpoints))
944    }
945
946    /// Return the number of transaction blocks in this epoch. This will return `Ok(None)` if the
947    /// epoch requested is not available in the GraphQL service (e.g., due to pruning).
948    pub async fn epoch_total_transaction_blocks(&self, epoch: Option<u64>) -> Result<Option<u64>> {
949        let response = self.epoch_summary(epoch).await?;
950
951        if let Some(errors) = response.errors {
952            return Err(Error::graphql_error(errors));
953        }
954
955        Ok(response
956            .data
957            .and_then(|d| d.epoch)
958            .and_then(|e| e.total_transactions))
959    }
960
961    /// Internal method for getting the epoch summary that is called in a few other APIs for
962    /// convenience.
963    async fn epoch_summary(
964        &self,
965        epoch: Option<u64>,
966    ) -> Result<GraphQlResponse<EpochSummaryQuery>> {
967        let operation = EpochSummaryQuery::build(EpochArgs { id: epoch });
968        self.run_query(&operation).await
969    }
970
971    // ===========================================================================
972    // Events API
973    // ===========================================================================
974
975    /// Return a page of tuple (event, transaction digest) based on the (optional) event filter.
976    pub async fn events(
977        &self,
978        filter: Option<EventFilter>,
979        pagination_filter: PaginationFilter,
980    ) -> Result<Page<(Event, TransactionDigest)>> {
981        let (after, before, first, last) = self.pagination_filter(pagination_filter).await;
982
983        let operation = EventsQuery::build(EventsQueryArgs {
984            filter,
985            after: after.as_deref(),
986            before: before.as_deref(),
987            first,
988            last,
989        });
990
991        let response = self.run_query(&operation).await?;
992
993        if let Some(errors) = response.errors {
994            return Err(Error::graphql_error(errors));
995        }
996
997        if let Some(events) = response.data {
998            let ec = events.events;
999            let page_info = ec.page_info;
1000
1001            let events_with_digests = ec
1002                .nodes
1003                .into_iter()
1004                .map(|node| -> Result<(Event, TransactionDigest)> {
1005                    let event =
1006                        bcs::from_bytes::<Event>(&base64ct::Base64::decode_vec(&node.bcs.0)?)?;
1007
1008                    let tx_digest = node
1009                        .transaction_block
1010                        .ok_or_else(Error::empty_response_error)?
1011                        .digest
1012                        .ok_or_else(|| {
1013                            Error::from_error(
1014                                Kind::Deserialization,
1015                                "Expected a transaction digest for this event, but it is missing.",
1016                            )
1017                        })?;
1018
1019                    let tx_digest = TransactionDigest::from_base58(&tx_digest)?;
1020
1021                    Ok((event, tx_digest))
1022                })
1023                .collect::<Result<Vec<_>>>()?;
1024
1025            Ok(Page::new(page_info, events_with_digests))
1026        } else {
1027            Ok(Page::new_empty())
1028        }
1029    }
1030
1031    /// Return a stream of events based on the (optional) event filter.
1032    pub async fn events_stream(
1033        &self,
1034        filter: Option<EventFilter>,
1035        streaming_direction: Direction,
1036    ) -> impl Stream<Item = Result<(Event, TransactionDigest)>> + '_ {
1037        stream_paginated_query(
1038            move |pag_filter| self.events(filter.clone(), pag_filter),
1039            streaming_direction,
1040        )
1041    }
1042
1043    // ===========================================================================
1044    // Objects API
1045    // ===========================================================================
1046
1047    /// Return an object based on the provided [`Address`].
1048    ///
1049    /// If the object does not exist (e.g., due to pruning), this will return `Ok(None)`.
1050    /// Similarly, if this is not an object but an address, it will return `Ok(None)`.
1051    pub async fn object(&self, address: Address, version: Option<u64>) -> Result<Option<Object>> {
1052        let operation = ObjectQuery::build(ObjectQueryArgs { address, version });
1053
1054        let response = self.run_query(&operation).await?;
1055
1056        if let Some(errors) = response.errors {
1057            return Err(Error::graphql_error(errors));
1058        }
1059
1060        if let Some(object) = response.data {
1061            let obj = object.object;
1062            let bcs = obj
1063                .and_then(|o| o.bcs)
1064                .map(|bcs| base64ct::Base64::decode_vec(bcs.0.as_str()))
1065                .transpose()?;
1066
1067            let object = bcs
1068                .map(|b| bcs::from_bytes::<sui_types::Object>(&b))
1069                .transpose()?;
1070
1071            Ok(object)
1072        } else {
1073            Ok(None)
1074        }
1075    }
1076
1077    /// Return a page of objects based on the provided parameters.
1078    ///
1079    /// Use this function together with the [`ObjectFilter::owner`] to get the objects owned by an
1080    /// address.
1081    ///
1082    /// # Example
1083    ///
1084    /// ```rust,ignore
1085    /// let filter = ObjectFilter {
1086    ///     type_: None,
1087    ///     owner: Some(Address::from_str("test").unwrap().into()),
1088    ///     object_ids: None,
1089    /// };
1090    ///
1091    /// let owned_objects = client.objects(None, None, Some(filter), None, None).await;
1092    /// ```
1093    pub async fn objects(
1094        &self,
1095        filter: Option<ObjectFilter<'_>>,
1096        pagination_filter: PaginationFilter,
1097    ) -> Result<Page<Object>> {
1098        let (after, before, first, last) = self.pagination_filter(pagination_filter).await;
1099        let operation = ObjectsQuery::build(ObjectsQueryArgs {
1100            after: after.as_deref(),
1101            before: before.as_deref(),
1102            filter,
1103            first,
1104            last,
1105        });
1106
1107        let response = self.run_query(&operation).await?;
1108        if let Some(errors) = response.errors {
1109            return Err(Error::graphql_error(errors));
1110        }
1111
1112        if let Some(objects) = response.data {
1113            let oc = objects.objects;
1114            let page_info = oc.page_info;
1115            let bcs = oc
1116                .nodes
1117                .iter()
1118                .map(|o| &o.bcs)
1119                .filter_map(|b64| {
1120                    b64.as_ref()
1121                        .map(|b| base64ct::Base64::decode_vec(b.0.as_str()))
1122                })
1123                .collect::<Result<Vec<_>, base64ct::Error>>()?;
1124            let objects = bcs
1125                .iter()
1126                .map(|b| bcs::from_bytes::<sui_types::Object>(b))
1127                .collect::<Result<Vec<_>, bcs::Error>>()?;
1128
1129            Ok(Page::new(page_info, objects))
1130        } else {
1131            Ok(Page::new_empty())
1132        }
1133    }
1134
1135    /// Return a stream of objects based on the (optional) object filter.
1136    pub async fn objects_stream<'a>(
1137        &'a self,
1138        filter: Option<ObjectFilter<'a>>,
1139        streaming_direction: Direction,
1140    ) -> impl Stream<Item = Result<Object>> + 'a {
1141        stream_paginated_query(
1142            move |pag_filter| self.objects(filter.clone(), pag_filter),
1143            streaming_direction,
1144        )
1145    }
1146
1147    /// Return the object's bcs content [`Vec<u8>`] based on the provided [`Address`].
1148    pub async fn object_bcs(&self, object_id: Address) -> Result<Option<Vec<u8>>> {
1149        let operation = ObjectQuery::build(ObjectQueryArgs {
1150            address: object_id,
1151            version: None,
1152        });
1153
1154        let response = self.run_query(&operation).await.unwrap();
1155
1156        if let Some(errors) = response.errors {
1157            return Err(Error::graphql_error(errors));
1158        }
1159
1160        if let Some(object) = response.data.map(|d| d.object) {
1161            Ok(object
1162                .and_then(|o| o.bcs)
1163                .map(|bcs| base64ct::Base64::decode_vec(bcs.0.as_str()))
1164                .transpose()?)
1165        } else {
1166            Ok(None)
1167        }
1168    }
1169
1170    /// Return the contents' JSON of an object that is a Move object.
1171    ///
1172    /// If the object does not exist (e.g., due to pruning), this will return `Ok(None)`.
1173    /// Similarly, if this is not an object but an address, it will return `Ok(None)`.
1174    pub async fn move_object_contents(
1175        &self,
1176        address: Address,
1177        version: Option<u64>,
1178    ) -> Result<Option<serde_json::Value>> {
1179        let operation = ObjectQuery::build(ObjectQueryArgs { address, version });
1180
1181        let response = self.run_query(&operation).await?;
1182
1183        if let Some(errors) = response.errors {
1184            return Err(Error::graphql_error(errors));
1185        }
1186
1187        if let Some(object) = response.data {
1188            Ok(object
1189                .object
1190                .and_then(|o| o.as_move_object)
1191                .and_then(|o| o.contents)
1192                .and_then(|mv| mv.json))
1193        } else {
1194            Ok(None)
1195        }
1196    }
1197    /// Return the BCS of an object that is a Move object.
1198    ///
1199    /// If the object does not exist (e.g., due to pruning), this will return `Ok(None)`.
1200    /// Similarly, if this is not an object but an address, it will return `Ok(None)`.
1201    pub async fn move_object_contents_bcs(
1202        &self,
1203        address: Address,
1204        version: Option<u64>,
1205    ) -> Result<Option<Vec<u8>>> {
1206        let operation = ObjectQuery::build(ObjectQueryArgs { address, version });
1207
1208        let response = self.run_query(&operation).await?;
1209
1210        if let Some(errors) = response.errors {
1211            return Err(Error::graphql_error(errors));
1212        }
1213
1214        if let Some(object) = response.data {
1215            Ok(object
1216                .object
1217                .and_then(|o| o.as_move_object)
1218                .and_then(|o| o.contents)
1219                .map(|bcs| base64ct::Base64::decode_vec(bcs.bcs.0.as_str()))
1220                .transpose()?)
1221        } else {
1222            Ok(None)
1223        }
1224    }
1225
1226    // ===========================================================================
1227    // Package API
1228    // ===========================================================================
1229
1230    /// The package corresponding to the given address (at the optionally given version).
1231    /// When no version is given, the package is loaded directly from the address given. Otherwise,
1232    /// the address is translated before loading to point to the package whose original ID matches
1233    /// the package at address, but whose version is version. For non-system packages, this
1234    /// might result in a different address than address because different versions of a package,
1235    /// introduced by upgrades, exist at distinct addresses.
1236    ///
1237    /// Note that this interpretation of version is different from a historical object read (the
1238    /// interpretation of version for the object query).
1239    pub async fn package(
1240        &self,
1241        address: Address,
1242        version: Option<u64>,
1243    ) -> Result<Option<MovePackage>> {
1244        let operation = PackageQuery::build(PackageArgs { address, version });
1245
1246        let response = self.run_query(&operation).await?;
1247
1248        if let Some(errors) = response.errors {
1249            return Err(Error::graphql_error(errors));
1250        }
1251
1252        Ok(response
1253            .data
1254            .and_then(|x| x.package)
1255            .and_then(|x| x.package_bcs)
1256            .map(|bcs| base64ct::Base64::decode_vec(bcs.0.as_str()))
1257            .transpose()?
1258            .map(|bcs| bcs::from_bytes::<MovePackage>(&bcs))
1259            .transpose()?)
1260    }
1261
1262    /// Fetch all versions of package at address (packages that share this package's original ID),
1263    /// optionally bounding the versions exclusively from below with afterVersion, or from above
1264    /// with beforeVersion.
1265    pub async fn package_versions(
1266        &self,
1267        address: Address,
1268        pagination_filter: PaginationFilter,
1269        after_version: Option<u64>,
1270        before_version: Option<u64>,
1271    ) -> Result<Page<MovePackage>> {
1272        let (after, before, first, last) = self.pagination_filter(pagination_filter).await;
1273        let operation = PackageVersionsQuery::build(PackageVersionsArgs {
1274            address,
1275            after: after.as_deref(),
1276            before: before.as_deref(),
1277            first,
1278            last,
1279            filter: Some(MovePackageVersionFilter {
1280                after_version,
1281                before_version,
1282            }),
1283        });
1284
1285        let response = self.run_query(&operation).await?;
1286
1287        if let Some(errors) = response.errors {
1288            return Err(Error::graphql_error(errors));
1289        }
1290
1291        if let Some(packages) = response.data {
1292            let pc = packages.package_versions;
1293            let page_info = pc.page_info;
1294            let bcs = pc
1295                .nodes
1296                .iter()
1297                .map(|p| &p.package_bcs)
1298                .filter_map(|b64| {
1299                    b64.as_ref()
1300                        .map(|b| base64ct::Base64::decode_vec(b.0.as_str()))
1301                })
1302                .collect::<Result<Vec<_>, base64ct::Error>>()?;
1303            let packages = bcs
1304                .iter()
1305                .map(|b| bcs::from_bytes::<MovePackage>(b))
1306                .collect::<Result<Vec<_>, bcs::Error>>()?;
1307
1308            Ok(Page::new(page_info, packages))
1309        } else {
1310            Ok(Page::new_empty())
1311        }
1312    }
1313
1314    /// Fetch the latest version of the package at address.
1315    /// This corresponds to the package with the highest version that shares its original ID with
1316    /// the package at address.
1317    pub async fn package_latest(&self, address: Address) -> Result<Option<MovePackage>> {
1318        let operation = LatestPackageQuery::build(PackageArgs {
1319            address,
1320            version: None,
1321        });
1322
1323        let response = self.run_query(&operation).await?;
1324
1325        if let Some(errors) = response.errors {
1326            return Err(Error::graphql_error(errors));
1327        }
1328
1329        let pkg = response
1330            .data
1331            .and_then(|x| x.latest_package)
1332            .and_then(|x| x.package_bcs)
1333            .map(|bcs| base64ct::Base64::decode_vec(bcs.0.as_str()))
1334            .transpose()?
1335            .map(|bcs| bcs::from_bytes::<MovePackage>(&bcs))
1336            .transpose()?;
1337
1338        Ok(pkg)
1339    }
1340
1341    /// Fetch a package by its name (using Move Registry Service)
1342    pub async fn package_by_name(&self, name: &str) -> Result<Option<MovePackage>> {
1343        let operation = PackageByNameQuery::build(PackageByNameArgs { name });
1344
1345        let response = self.run_query(&operation).await?;
1346
1347        if let Some(errors) = response.errors {
1348            return Err(Error::graphql_error(errors));
1349        }
1350
1351        Ok(response
1352            .data
1353            .and_then(|x| x.package_by_name)
1354            .and_then(|x| x.package_bcs)
1355            .and_then(|bcs| base64ct::Base64::decode_vec(bcs.0.as_str()).ok())
1356            .and_then(|bcs| bcs::from_bytes::<MovePackage>(&bcs).ok()))
1357    }
1358
1359    /// The Move packages that exist in the network, optionally filtered to be strictly before
1360    /// beforeCheckpoint and/or strictly after afterCheckpoint.
1361    ///
1362    /// This query returns all versions of a given user package that appear between the specified
1363    /// checkpoints, but only records the latest versions of system packages.
1364    pub async fn packages(
1365        &self,
1366        pagination_filter: PaginationFilter,
1367        after_checkpoint: Option<u64>,
1368        before_checkpoint: Option<u64>,
1369    ) -> Result<Page<MovePackage>> {
1370        let (after, before, first, last) = self.pagination_filter(pagination_filter).await;
1371
1372        let operation = PackagesQuery::build(PackagesQueryArgs {
1373            after: after.as_deref(),
1374            before: before.as_deref(),
1375            first,
1376            last,
1377            filter: Some(PackageCheckpointFilter {
1378                after_checkpoint,
1379                before_checkpoint,
1380            }),
1381        });
1382
1383        let response = self.run_query(&operation).await?;
1384
1385        if let Some(errors) = response.errors {
1386            return Err(Error::graphql_error(errors));
1387        }
1388
1389        if let Some(packages) = response.data {
1390            let pc = packages.packages;
1391            let page_info = pc.page_info;
1392            let bcs = pc
1393                .nodes
1394                .iter()
1395                .map(|p| &p.package_bcs)
1396                .filter_map(|b64| {
1397                    b64.as_ref()
1398                        .map(|b| base64ct::Base64::decode_vec(b.0.as_str()))
1399                })
1400                .collect::<Result<Vec<_>, base64ct::Error>>()?;
1401            let packages = bcs
1402                .iter()
1403                .map(|b| bcs::from_bytes::<MovePackage>(b))
1404                .collect::<Result<Vec<_>, bcs::Error>>()?;
1405
1406            Ok(Page::new(page_info, packages))
1407        } else {
1408            Ok(Page::new_empty())
1409        }
1410    }
1411
1412    // ===========================================================================
1413    // Dry Run API
1414    // ===========================================================================
1415
1416    /// Dry run a [`Transaction`] and return the transaction effects and dry run error (if any).
1417    ///
1418    /// `skipChecks` optional flag disables the usual verification checks that prevent access to
1419    /// objects that are owned by addresses other than the sender, and calling non-public,
1420    /// non-entry functions, and some other checks. Defaults to false.
1421    pub async fn dry_run_tx(
1422        &self,
1423        tx: &Transaction,
1424        skip_checks: Option<bool>,
1425    ) -> Result<DryRunResult> {
1426        let tx_bytes = base64ct::Base64::encode_string(&bcs::to_bytes(&tx)?);
1427        self.dry_run(tx_bytes, skip_checks, None).await
1428    }
1429
1430    /// Dry run a [`TransactionKind`] and return the transaction effects and dry run error (if any).
1431    ///
1432    /// `skipChecks` optional flag disables the usual verification checks that prevent access to
1433    /// objects that are owned by addresses other than the sender, and calling non-public,
1434    /// non-entry functions, and some other checks. Defaults to false.
1435    ///
1436    /// `tx_meta` is the transaction metadata.
1437    pub async fn dry_run_tx_kind(
1438        &self,
1439        tx_kind: &TransactionKind,
1440        skip_checks: Option<bool>,
1441        tx_meta: TransactionMetadata,
1442    ) -> Result<DryRunResult> {
1443        let tx_bytes = base64ct::Base64::encode_string(&bcs::to_bytes(&tx_kind)?);
1444        self.dry_run(tx_bytes, skip_checks, Some(tx_meta)).await
1445    }
1446
1447    /// Internal implementation of the dry run API.
1448    async fn dry_run(
1449        &self,
1450        tx_bytes: String,
1451        skip_checks: Option<bool>,
1452        tx_meta: Option<TransactionMetadata>,
1453    ) -> Result<DryRunResult> {
1454        let skip_checks = skip_checks.unwrap_or(false);
1455        let operation = DryRunQuery::build(DryRunArgs {
1456            tx_bytes,
1457            skip_checks,
1458            tx_meta,
1459        });
1460        let response = self.run_query(&operation).await?;
1461
1462        // Query errors
1463        if let Some(errors) = response.errors {
1464            return Err(Error::graphql_error(errors));
1465        }
1466
1467        // Dry Run errors
1468        let error = response
1469            .data
1470            .as_ref()
1471            .and_then(|tx| tx.dry_run_transaction_block.error.clone());
1472
1473        let effects = response
1474            .data
1475            .map(|tx| tx.dry_run_transaction_block)
1476            .and_then(|tx| tx.transaction)
1477            .and_then(|tx| tx.effects)
1478            .and_then(|bcs| bcs.bcs)
1479            .map(|bcs| base64ct::Base64::decode_vec(bcs.0.as_str()))
1480            .transpose()?
1481            .map(|bcs| bcs::from_bytes::<TransactionEffects>(&bcs))
1482            .transpose()?;
1483
1484        Ok(DryRunResult { effects, error })
1485    }
1486
1487    // ===========================================================================
1488    // Transaction API
1489    // ===========================================================================
1490
1491    /// Get a transaction by its digest.
1492    pub async fn transaction(
1493        &self,
1494        digest: TransactionDigest,
1495    ) -> Result<Option<SignedTransaction>> {
1496        let operation = TransactionBlockQuery::build(TransactionBlockArgs {
1497            digest: digest.to_string(),
1498        });
1499        let response = self.run_query(&operation).await?;
1500
1501        if let Some(errors) = response.errors {
1502            return Err(Error::graphql_error(errors));
1503        }
1504
1505        response
1506            .data
1507            .and_then(|d| d.transaction_block)
1508            .map(|tx| tx.try_into())
1509            .transpose()
1510    }
1511
1512    /// Get a transaction's effects by its digest.
1513    pub async fn transaction_effects(
1514        &self,
1515        digest: TransactionDigest,
1516    ) -> Result<Option<TransactionEffects>> {
1517        let operation = TransactionBlockEffectsQuery::build(TransactionBlockArgs {
1518            digest: digest.to_string(),
1519        });
1520        let response = self.run_query(&operation).await?;
1521
1522        response
1523            .data
1524            .and_then(|d| d.transaction_block)
1525            .map(|tx| tx.try_into())
1526            .transpose()
1527    }
1528
1529    /// Get a transaction's data and effects by its digest.
1530    pub async fn transaction_data_effects(
1531        &self,
1532        digest: TransactionDigest,
1533    ) -> Result<Option<TransactionDataEffects>> {
1534        let operation = TransactionBlockWithEffectsQuery::build(TransactionBlockArgs {
1535            digest: digest.to_string(),
1536        });
1537        let response = self.run_query(&operation).await?;
1538
1539        let tx = response
1540            .data
1541            .and_then(|d| d.transaction_block)
1542            .map(|tx| (tx.bcs, tx.effects, tx.signatures));
1543
1544        match tx {
1545            Some((Some(bcs), Some(effects), Some(sigs))) => {
1546                let bcs = base64ct::Base64::decode_vec(bcs.0.as_str())?;
1547                let effects = base64ct::Base64::decode_vec(effects.bcs.unwrap().0.as_str())?;
1548                let signatures = sigs
1549                    .iter()
1550                    .map(|s| UserSignature::from_base64(&s.0))
1551                    .collect::<Result<Vec<_>, _>>()?;
1552                let transaction: Transaction = bcs::from_bytes(&bcs)?;
1553                let tx = SignedTransaction {
1554                    transaction,
1555                    signatures,
1556                };
1557                let effects: TransactionEffects = bcs::from_bytes(&effects)?;
1558                Ok(Some(TransactionDataEffects { tx, effects }))
1559            }
1560            _ => Ok(None),
1561        }
1562    }
1563
1564    /// Get a page of transactions based on the provided filters.
1565    pub async fn transactions(
1566        &self,
1567        filter: Option<TransactionsFilter<'_>>,
1568        pagination_filter: PaginationFilter,
1569    ) -> Result<Page<SignedTransaction>> {
1570        let (after, before, first, last) = self.pagination_filter(pagination_filter).await;
1571
1572        let operation = TransactionBlocksQuery::build(TransactionBlocksQueryArgs {
1573            after: after.as_deref(),
1574            before: before.as_deref(),
1575            filter,
1576            first,
1577            last,
1578        });
1579
1580        let response = self.run_query(&operation).await?;
1581
1582        if let Some(errors) = response.errors {
1583            return Err(Error::graphql_error(errors));
1584        }
1585
1586        if let Some(txb) = response.data {
1587            let txc = txb.transaction_blocks;
1588            let page_info = txc.page_info;
1589
1590            let transactions = txc
1591                .nodes
1592                .into_iter()
1593                .map(|n| n.try_into())
1594                .collect::<Result<Vec<_>>>()?;
1595            let page = Page::new(page_info, transactions);
1596            Ok(page)
1597        } else {
1598            Ok(Page::new_empty())
1599        }
1600    }
1601
1602    /// Get a page of transactions' effects based on the provided filters.
1603    pub async fn transactions_effects(
1604        &self,
1605        filter: Option<TransactionsFilter<'_>>,
1606        pagination_filter: PaginationFilter,
1607    ) -> Result<Page<TransactionEffects>> {
1608        let (after, before, first, last) = self.pagination_filter(pagination_filter).await;
1609
1610        let operation = TransactionBlocksEffectsQuery::build(TransactionBlocksQueryArgs {
1611            after: after.as_deref(),
1612            before: before.as_deref(),
1613            filter,
1614            first,
1615            last,
1616        });
1617
1618        let response = self.run_query(&operation).await?;
1619
1620        if let Some(txb) = response.data {
1621            let txc = txb.transaction_blocks;
1622            let page_info = txc.page_info;
1623
1624            let transactions = txc
1625                .nodes
1626                .into_iter()
1627                .map(|n| n.try_into())
1628                .collect::<Result<Vec<_>>>()?;
1629            let page = Page::new(page_info, transactions);
1630            Ok(page)
1631        } else {
1632            Ok(Page::new_empty())
1633        }
1634    }
1635
1636    /// Get a page of transactions' data and effects based on the provided filters.
1637    pub async fn transactions_data_effects(
1638        &self,
1639        filter: Option<TransactionsFilter<'_>>,
1640        pagination_filter: PaginationFilter,
1641    ) -> Result<Page<TransactionDataEffects>> {
1642        let (after, before, first, last) = self.pagination_filter(pagination_filter).await;
1643
1644        let operation = TransactionBlocksWithEffectsQuery::build(TransactionBlocksQueryArgs {
1645            after: after.as_deref(),
1646            before: before.as_deref(),
1647            filter,
1648            first,
1649            last,
1650        });
1651
1652        let response = self.run_query(&operation).await?;
1653
1654        if let Some(errors) = response.errors {
1655            return Err(Error::graphql_error(errors));
1656        }
1657
1658        if let Some(txb) = response.data {
1659            let txc = txb.transaction_blocks;
1660            let page_info = txc.page_info;
1661
1662            let transactions = {
1663                txc.nodes
1664                    .iter()
1665                    .map(|node| {
1666                        match (
1667                            node.bcs.as_ref(),
1668                            node.effects.as_ref(),
1669                            node.signatures.as_ref(),
1670                        ) {
1671                            (Some(bcs), Some(effects), Some(sigs)) => {
1672                                let bcs = base64ct::Base64::decode_vec(bcs.0.as_str())?;
1673                                let effects = base64ct::Base64::decode_vec(
1674                                    effects.bcs.as_ref().unwrap().0.as_str(),
1675                                )?;
1676
1677                                let sigs = sigs
1678                                    .iter()
1679                                    .map(|s| UserSignature::from_base64(&s.0))
1680                                    .collect::<Result<Vec<_>, _>>()?;
1681                                let tx: Transaction = bcs::from_bytes(&bcs)?;
1682                                let tx = SignedTransaction {
1683                                    transaction: tx,
1684                                    signatures: sigs,
1685                                };
1686                                let effects: TransactionEffects = bcs::from_bytes(&effects)?;
1687                                Ok(TransactionDataEffects { tx, effects })
1688                            }
1689                            (_, _, _) => Err(Error::empty_response_error()),
1690                        }
1691                    })
1692                    .collect::<Result<Vec<_>>>()?
1693            };
1694
1695            let page = Page::new(page_info, transactions);
1696            Ok(page)
1697        } else {
1698            Ok(Page::new_empty())
1699        }
1700    }
1701
1702    /// Get a stream of transactions based on the (optional) transaction filter.
1703    pub async fn transactions_stream<'a>(
1704        &'a self,
1705        filter: Option<TransactionsFilter<'a>>,
1706        streaming_direction: Direction,
1707    ) -> impl Stream<Item = Result<SignedTransaction>> + 'a {
1708        stream_paginated_query(
1709            move |pag_filter| self.transactions(filter.clone(), pag_filter),
1710            streaming_direction,
1711        )
1712    }
1713
1714    /// Get a stream of transactions' effects based on the (optional) transaction filter.
1715    pub async fn transactions_effects_stream<'a>(
1716        &'a self,
1717        filter: Option<TransactionsFilter<'a>>,
1718        streaming_direction: Direction,
1719    ) -> impl Stream<Item = Result<TransactionEffects>> + 'a {
1720        stream_paginated_query(
1721            move |pag_filter| self.transactions_effects(filter.clone(), pag_filter),
1722            streaming_direction,
1723        )
1724    }
1725
1726    /// Execute a transaction.
1727    pub async fn execute_tx(
1728        &self,
1729        signatures: Vec<UserSignature>,
1730        tx: &Transaction,
1731    ) -> Result<Option<TransactionEffects>> {
1732        let operation = ExecuteTransactionQuery::build(ExecuteTransactionArgs {
1733            signatures: signatures.iter().map(|s| s.to_base64()).collect(),
1734            tx_bytes: base64ct::Base64::encode_string(bcs::to_bytes(tx).unwrap().as_ref()),
1735        });
1736
1737        let response = self.run_query(&operation).await?;
1738
1739        if let Some(errors) = response.errors {
1740            return Err(Error::graphql_error(errors));
1741        }
1742
1743        if let Some(data) = response.data {
1744            let result = data.execute_transaction_block;
1745            let bcs = base64ct::Base64::decode_vec(result.effects.bcs.0.as_str())?;
1746            let effects: TransactionEffects = bcs::from_bytes(&bcs)?;
1747
1748            Ok(Some(effects))
1749        } else {
1750            Ok(None)
1751        }
1752    }
1753
1754    // ===========================================================================
1755    // Normalized Move Package API
1756    // ===========================================================================
1757    /// Return the normalized Move function data for the provided package, module, and function.
1758    pub async fn normalized_move_function(
1759        &self,
1760        package: &str,
1761        module: &str,
1762        function: &str,
1763        version: Option<u64>,
1764    ) -> Result<Option<MoveFunction>> {
1765        let operation = NormalizedMoveFunctionQuery::build(NormalizedMoveFunctionQueryArgs {
1766            address: Address::from_str(package)?,
1767            module,
1768            function,
1769            version,
1770        });
1771        let response = self.run_query(&operation).await?;
1772
1773        if let Some(errors) = response.errors {
1774            return Err(Error::graphql_error(errors));
1775        }
1776
1777        Ok(response
1778            .data
1779            .and_then(|p| p.package)
1780            .and_then(|p| p.module)
1781            .and_then(|m| m.function))
1782    }
1783
1784    /// Return the normalized Move module data for the provided module.
1785    // TODO: do we want to self paginate everything and return all the data, or keep pagination
1786    // options?
1787    #[allow(clippy::too_many_arguments)]
1788    pub async fn normalized_move_module(
1789        &self,
1790        package: &str,
1791        module: &str,
1792        version: Option<u64>,
1793        pagination_filter_enums: PaginationFilter,
1794        pagination_filter_friends: PaginationFilter,
1795        pagination_filter_functions: PaginationFilter,
1796        pagination_filter_structs: PaginationFilter,
1797    ) -> Result<Option<MoveModule>> {
1798        let (after_enums, before_enums, first_enums, last_enums) =
1799            self.pagination_filter(pagination_filter_enums).await;
1800        let (after_friends, before_friends, first_friends, last_friends) =
1801            self.pagination_filter(pagination_filter_friends).await;
1802        let (after_functions, before_functions, first_functions, last_functions) =
1803            self.pagination_filter(pagination_filter_functions).await;
1804        let (after_structs, before_structs, first_structs, last_structs) =
1805            self.pagination_filter(pagination_filter_structs).await;
1806        let operation = NormalizedMoveModuleQuery::build(NormalizedMoveModuleQueryArgs {
1807            package: Address::from_str(package)?,
1808            module,
1809            version,
1810            after_enums: after_enums.as_deref(),
1811            after_functions: after_functions.as_deref(),
1812            after_structs: after_structs.as_deref(),
1813            after_friends: after_friends.as_deref(),
1814            before_enums: before_enums.as_deref(),
1815            before_functions: before_functions.as_deref(),
1816            before_structs: before_structs.as_deref(),
1817            before_friends: before_friends.as_deref(),
1818            first_enums,
1819            first_functions,
1820            first_structs,
1821            first_friends,
1822            last_enums,
1823            last_functions,
1824            last_structs,
1825            last_friends,
1826        });
1827        let response = self.run_query(&operation).await?;
1828
1829        if let Some(errors) = response.errors {
1830            return Err(Error::graphql_error(errors));
1831        }
1832
1833        Ok(response.data.and_then(|p| p.package).and_then(|p| p.module))
1834    }
1835
1836    // ===========================================================================
1837    // SuiNS
1838    // ===========================================================================
1839
1840    /// Get the address for the provided Suins domain name.
1841    pub async fn resolve_suins_to_address(&self, domain: &str) -> Result<Option<Address>> {
1842        let operation = ResolveSuinsQuery::build(ResolveSuinsQueryArgs { name: domain });
1843
1844        let response = self.run_query(&operation).await?;
1845
1846        if let Some(errors) = response.errors {
1847            return Err(Error::graphql_error(errors));
1848        }
1849        Ok(response
1850            .data
1851            .and_then(|d| d.resolve_suins_address)
1852            .map(|a| a.address))
1853    }
1854
1855    /// Get the default Suins domain name for the provided address.
1856    pub async fn default_suins_name(&self, address: Address) -> Result<Option<String>> {
1857        let operation = DefaultSuinsNameQuery::build(DefaultSuinsNameQueryArgs { address });
1858
1859        let response = self.run_query(&operation).await?;
1860
1861        if let Some(errors) = response.errors {
1862            return Err(Error::graphql_error(errors));
1863        }
1864        Ok(response
1865            .data
1866            .and_then(|d| d.address)
1867            .and_then(|a| a.default_suins_name))
1868    }
1869}
1870
1871// This function is used in tests to create a new client instance for the local server.
1872#[cfg(test)]
1873mod tests {
1874    use base64ct::Encoding;
1875    use futures::StreamExt;
1876    use sui_types::Ed25519PublicKey;
1877    use sui_types::TypeTag;
1878
1879    use crate::faucet::FaucetClient;
1880    use crate::BcsName;
1881    use crate::Client;
1882    use crate::Direction;
1883    use crate::PaginationFilter;
1884    use crate::DEVNET_HOST;
1885    use crate::LOCAL_HOST;
1886    use crate::MAINNET_HOST;
1887    use crate::TESTNET_HOST;
1888    use tokio::time;
1889
1890    const NUM_COINS_FROM_FAUCET: usize = 5;
1891
1892    fn test_client() -> Client {
1893        let network = std::env::var("NETWORK").unwrap_or_else(|_| "local".to_string());
1894        match network.as_str() {
1895            "mainnet" => Client::new_mainnet(),
1896            "testnet" => Client::new_testnet(),
1897            "devnet" => Client::new_devnet(),
1898            "local" => Client::new_localhost(),
1899            _ => Client::new(&network).expect("Invalid network URL: {network}"),
1900        }
1901    }
1902
1903    #[test]
1904    fn test_rpc_server() {
1905        let mut client = Client::new_mainnet();
1906        assert_eq!(client.rpc_server(), MAINNET_HOST);
1907        client.set_rpc_server(TESTNET_HOST).unwrap();
1908        assert_eq!(client.rpc_server(), TESTNET_HOST);
1909        client.set_rpc_server(DEVNET_HOST).unwrap();
1910        assert_eq!(client.rpc_server(), DEVNET_HOST);
1911        client.set_rpc_server(LOCAL_HOST).unwrap();
1912        assert_eq!(client.rpc_server(), LOCAL_HOST);
1913
1914        assert!(client.set_rpc_server("localhost:9125/graphql").is_ok());
1915        assert!(client.set_rpc_server("9125/graphql").is_err());
1916    }
1917
1918    #[tokio::test]
1919    async fn test_balance_query() {
1920        let client = test_client();
1921        let balance = client.balance("0x1".parse().unwrap(), None).await;
1922        assert!(
1923            balance.is_ok(),
1924            "Balance query failed for {} network",
1925            client.rpc_server()
1926        );
1927    }
1928
1929    #[tokio::test]
1930    async fn test_chain_id() {
1931        let client = test_client();
1932        let chain_id = client.chain_id().await;
1933        assert!(chain_id.is_ok());
1934    }
1935
1936    #[tokio::test]
1937    async fn test_reference_gas_price_query() {
1938        let client = test_client();
1939        let rgp = client.reference_gas_price(None).await;
1940        assert!(
1941            rgp.is_ok(),
1942            "Reference gas price query failed for {} network",
1943            client.rpc_server()
1944        );
1945    }
1946
1947    #[tokio::test]
1948    async fn test_protocol_config_query() {
1949        let client = test_client();
1950        let pc = client.protocol_config(None).await;
1951        assert!(pc.is_ok());
1952
1953        // test specific version
1954        let pc = client.protocol_config(Some(50)).await;
1955        assert!(pc.is_ok());
1956        let pc = pc.unwrap();
1957        if let Some(pc) = pc {
1958            assert_eq!(
1959                pc.protocol_version,
1960                50,
1961                "Protocol version query mismatch for {} network. Expected: 50, received: {}",
1962                client.rpc_server(),
1963                pc.protocol_version
1964            );
1965        }
1966    }
1967
1968    #[tokio::test]
1969    async fn test_service_config_query() {
1970        let client = test_client();
1971        let sc = client.service_config().await;
1972        assert!(
1973            sc.is_ok(),
1974            "Service config query failed for {} network",
1975            client.rpc_server()
1976        );
1977    }
1978
1979    #[tokio::test]
1980    async fn test_active_validators() {
1981        let client = test_client();
1982        let av = client
1983            .active_validators(None, PaginationFilter::default())
1984            .await;
1985        assert!(
1986            av.is_ok(),
1987            "Active validators query failed for {} network. Error: {}",
1988            client.rpc_server(),
1989            av.unwrap_err()
1990        );
1991
1992        assert!(
1993            !av.unwrap().is_empty(),
1994            "Active validators query returned None for {} network",
1995            client.rpc_server()
1996        );
1997    }
1998
1999    #[tokio::test]
2000    async fn test_coin_metadata_query() {
2001        let client = test_client();
2002        let cm = client.coin_metadata("0x2::sui::SUI").await;
2003        assert!(
2004            cm.is_ok(),
2005            "Coin metadata query failed for {} network",
2006            client.rpc_server()
2007        );
2008    }
2009
2010    #[tokio::test]
2011    async fn test_checkpoint_query() {
2012        let client = test_client();
2013        let c = client.checkpoint(None, None).await;
2014        assert!(
2015            c.is_ok(),
2016            "Checkpoint query failed for {} network. Error: {}",
2017            client.rpc_server(),
2018            c.unwrap_err()
2019        );
2020    }
2021    #[tokio::test]
2022    async fn test_checkpoints_query() {
2023        let client = test_client();
2024        let c = client.checkpoints(PaginationFilter::default()).await;
2025        assert!(
2026            c.is_ok(),
2027            "Checkpoints query failed for {} network. Error: {}",
2028            client.rpc_server(),
2029            c.unwrap_err()
2030        );
2031    }
2032
2033    #[tokio::test]
2034    async fn test_latest_checkpoint_sequence_number_query() {
2035        let client = test_client();
2036        let last_checkpoint = client.latest_checkpoint_sequence_number().await;
2037        assert!(
2038            last_checkpoint.is_ok(),
2039            "Latest checkpoint sequence number query failed for {} network. Error: {}",
2040            client.rpc_server(),
2041            last_checkpoint.unwrap_err()
2042        );
2043    }
2044
2045    #[tokio::test]
2046    async fn test_epoch_query() {
2047        let client = test_client();
2048        let e = client.epoch(None).await;
2049        assert!(
2050            e.is_ok(),
2051            "Epoch query failed for {} network. Error: {}",
2052            client.rpc_server(),
2053            e.unwrap_err()
2054        );
2055
2056        assert!(
2057            e.unwrap().is_some(),
2058            "Epoch query returned None for {} network",
2059            client.rpc_server()
2060        );
2061    }
2062
2063    #[tokio::test]
2064    async fn test_epoch_total_checkpoints_query() {
2065        let client = test_client();
2066        let e = client.epoch_total_checkpoints(None).await;
2067        assert!(
2068            e.is_ok(),
2069            "Epoch total checkpoints query failed for {} network. Error: {}",
2070            client.rpc_server(),
2071            e.unwrap_err()
2072        );
2073    }
2074
2075    #[tokio::test]
2076    async fn test_epoch_total_transaction_blocks_query() {
2077        let client = test_client();
2078        let e = client.epoch_total_transaction_blocks(None).await;
2079        assert!(
2080            e.is_ok(),
2081            "Epoch total transaction blocks query failed for {} network. Error: {}",
2082            client.rpc_server(),
2083            e.unwrap_err()
2084        );
2085    }
2086
2087    #[tokio::test]
2088    async fn test_epoch_summary_query() {
2089        let client = test_client();
2090        let e = client.epoch_summary(None).await;
2091        assert!(
2092            e.is_ok(),
2093            "Epoch summary query failed for {} network. Error: {}",
2094            client.rpc_server(),
2095            e.unwrap_err()
2096        );
2097    }
2098
2099    #[tokio::test]
2100    async fn test_events_query() {
2101        let client = test_client();
2102        let events = client.events(None, PaginationFilter::default()).await;
2103        assert!(
2104            events.is_ok(),
2105            "Events query failed for {} network. Error: {}",
2106            client.rpc_server(),
2107            events.unwrap_err()
2108        );
2109        assert!(
2110            !events.unwrap().is_empty(),
2111            "Events query returned no data for {} network",
2112            client.rpc_server()
2113        );
2114    }
2115
2116    #[tokio::test]
2117    async fn test_objects_query() {
2118        let client = test_client();
2119        let objects = client.objects(None, PaginationFilter::default()).await;
2120        assert!(
2121            objects.is_ok(),
2122            "Objects query failed for {} network. Error: {}",
2123            client.rpc_server(),
2124            objects.unwrap_err()
2125        );
2126    }
2127
2128    #[tokio::test]
2129    async fn test_object_query() {
2130        let client = test_client();
2131        let object = client.object("0x5".parse().unwrap(), None).await;
2132        assert!(
2133            object.is_ok(),
2134            "Object query failed for {} network. Error: {}",
2135            client.rpc_server(),
2136            object.unwrap_err()
2137        );
2138    }
2139
2140    #[tokio::test]
2141    async fn test_object_bcs_query() {
2142        let client = test_client();
2143        let object_bcs = client.object_bcs("0x5".parse().unwrap()).await;
2144        assert!(
2145            object_bcs.is_ok(),
2146            "Object bcs query failed for {} network. Error: {}",
2147            client.rpc_server(),
2148            object_bcs.unwrap_err()
2149        );
2150    }
2151
2152    #[tokio::test]
2153    async fn test_coins_query() {
2154        let client = test_client();
2155        let coins = client
2156            .coins("0x1".parse().unwrap(), None, PaginationFilter::default())
2157            .await;
2158        assert!(
2159            coins.is_ok(),
2160            "Coins query failed for {} network. Error: {}",
2161            client.rpc_server(),
2162            coins.unwrap_err()
2163        );
2164    }
2165
2166    #[tokio::test]
2167    async fn test_coins_stream() {
2168        let client = test_client();
2169        let faucet = match client.rpc_server() {
2170            LOCAL_HOST => FaucetClient::local(),
2171            TESTNET_HOST => FaucetClient::testnet(),
2172            DEVNET_HOST => FaucetClient::devnet(),
2173            _ => return,
2174        };
2175        let key = Ed25519PublicKey::generate(rand::thread_rng());
2176        let address = key.derive_address();
2177        faucet.request(address).await.unwrap();
2178
2179        const MAX_RETRIES: u32 = 10;
2180        const RETRY_DELAY: time::Duration = time::Duration::from_secs(1);
2181
2182        let mut num_coins = 0;
2183        for attempt in 0..MAX_RETRIES {
2184            let mut stream = client
2185                .coins_stream(address, None, Direction::default())
2186                .await;
2187
2188            while let Some(result) = stream.next().await {
2189                match result {
2190                    Ok(_) => num_coins += 1,
2191                    Err(_) => {
2192                        if attempt < MAX_RETRIES - 1 {
2193                            time::sleep(RETRY_DELAY).await;
2194                            num_coins = 0;
2195                            break;
2196                        }
2197                    }
2198                }
2199            }
2200        }
2201
2202        assert!(num_coins >= NUM_COINS_FROM_FAUCET);
2203    }
2204
2205    #[tokio::test]
2206    async fn test_transaction_effects_query() {
2207        let client = test_client();
2208        let transactions = client
2209            .transactions(None, PaginationFilter::default())
2210            .await
2211            .unwrap();
2212        let tx_digest = transactions.data()[0].transaction.digest();
2213        let effects = client.transaction_effects(tx_digest).await.unwrap();
2214        assert!(
2215            effects.is_some(),
2216            "Transaction effects query failed for {} network.",
2217            client.rpc_server(),
2218        );
2219    }
2220
2221    #[tokio::test]
2222    async fn test_transactions_effects_query() {
2223        let client = test_client();
2224        let txs_effects = client
2225            .transactions_effects(None, PaginationFilter::default())
2226            .await;
2227        assert!(
2228            txs_effects.is_ok(),
2229            "Transactions effects query failed for {} network. Error: {}",
2230            client.rpc_server(),
2231            txs_effects.unwrap_err()
2232        );
2233    }
2234
2235    #[tokio::test]
2236    async fn test_transactions_query() {
2237        let client = test_client();
2238        let transactions = client.transactions(None, PaginationFilter::default()).await;
2239        assert!(
2240            transactions.is_ok(),
2241            "Transactions query failed for {} network. Error: {}",
2242            client.rpc_server(),
2243            transactions.unwrap_err()
2244        );
2245    }
2246
2247    #[tokio::test]
2248    async fn test_total_supply() {
2249        let client = test_client();
2250        let ts = client.total_supply("0x2::sui::SUI").await;
2251        assert!(
2252            ts.is_ok(),
2253            "Total supply query failed for {} network. Error: {}",
2254            client.rpc_server(),
2255            ts.unwrap_err()
2256        );
2257        assert_eq!(
2258            ts.unwrap().unwrap(),
2259            10_000_000_000,
2260            "Total supply mismatch for {} network",
2261            client.rpc_server()
2262        );
2263    }
2264
2265    // This needs the tx builder to be able to be tested properly
2266    #[tokio::test]
2267    async fn test_dry_run() {
2268        let client = Client::new_testnet();
2269        // this tx bytes works on testnet
2270        let tx_bytes = "AAACAAiA8PoCAAAAAAAg7q6yDns6nPznaKLd9pUD2K6NFiiibC10pDVQHJKdP2kCAgABAQAAAQECAAABAQBGLuHCJ/xjZfhC4vTJt/Zrvq1gexKLaKf3aVzyIkxRaAFUHzz8ftiZdY25qP4f9zySuT1K/qyTWjbGiTu0i0Z1ZFA4gwUAAAAAILeG86EeQm3qY3ajat3iUnY2Gbrk/NbdwV/d9MZviAwwRi7hwif8Y2X4QuL0ybf2a76tYHsSi2in92lc8iJMUWjoAwAAAAAAAECrPAAAAAAAAA==";
2271
2272        let dry_run = client.dry_run(tx_bytes.to_string(), None, None).await;
2273
2274        assert!(dry_run.is_ok());
2275    }
2276
2277    #[tokio::test]
2278    async fn test_dynamic_field_query() {
2279        let client = test_client();
2280        let bcs = base64ct::Base64::decode_vec("AgAAAAAAAAA=").unwrap();
2281        let dynamic_field = client
2282            .dynamic_field("0x5".parse().unwrap(), TypeTag::U64, BcsName(bcs))
2283            .await;
2284
2285        assert!(dynamic_field.is_ok());
2286
2287        let dynamic_field = client
2288            .dynamic_field("0x5".parse().unwrap(), TypeTag::U64, 2u64)
2289            .await;
2290
2291        assert!(dynamic_field.is_ok());
2292    }
2293
2294    #[tokio::test]
2295    async fn test_dynamic_fields_query() {
2296        let client = test_client();
2297        let dynamic_fields = client
2298            .dynamic_fields("0x5".parse().unwrap(), PaginationFilter::default())
2299            .await;
2300        assert!(
2301            dynamic_fields.is_ok(),
2302            "Dynamic fields query failed for {} network. Error: {}",
2303            client.rpc_server(),
2304            dynamic_fields.unwrap_err()
2305        );
2306    }
2307
2308    #[tokio::test]
2309    async fn test_total_transaction_blocks() {
2310        let client = test_client();
2311        let total_transaction_blocks = client.total_transaction_blocks().await;
2312        assert!(
2313            total_transaction_blocks
2314                .as_ref()
2315                .is_ok_and(|f| f.is_some_and(|tx| tx > 0)),
2316            "Total transaction blocks query failed for {} network. Error: {}",
2317            client.rpc_server(),
2318            total_transaction_blocks.unwrap_err()
2319        );
2320
2321        let chckp = client.latest_checkpoint_sequence_number().await;
2322        assert!(
2323            chckp.is_ok(),
2324            "Latest checkpoint sequence number query failed for {} network. Error: {}",
2325            client.rpc_server(),
2326            chckp.unwrap_err()
2327        );
2328        let chckp_id = chckp.unwrap().unwrap();
2329        let total_transaction_blocks = client
2330            .total_transaction_blocks_by_seq_num(chckp_id)
2331            .await
2332            .unwrap()
2333            .unwrap();
2334        assert!(total_transaction_blocks > 0);
2335
2336        let chckp = client
2337            .checkpoint(None, Some(chckp_id))
2338            .await
2339            .unwrap()
2340            .unwrap();
2341
2342        let digest = chckp.digest();
2343        let total_transaction_blocks_by_digest =
2344            client.total_transaction_blocks_by_digest(digest).await;
2345        assert!(total_transaction_blocks_by_digest.is_ok());
2346        assert_eq!(
2347            total_transaction_blocks_by_digest.unwrap().unwrap(),
2348            total_transaction_blocks
2349        );
2350    }
2351
2352    #[tokio::test]
2353    async fn test_package() {
2354        let client = test_client();
2355        let package = client.package("0x2".parse().unwrap(), None).await;
2356        assert!(
2357            package.is_ok(),
2358            "Package query failed for {} network. Error: {}",
2359            client.rpc_server(),
2360            package.unwrap_err()
2361        );
2362
2363        assert!(
2364            package.unwrap().is_some(),
2365            "Package query returned None for {} network",
2366            client.rpc_server()
2367        );
2368    }
2369
2370    #[tokio::test]
2371    #[ignore] // don't know which name is not malformed
2372    async fn test_package_by_name() {
2373        let client = Client::new_testnet();
2374        let package = client.package_by_name("sui@sui").await;
2375        assert!(package.is_ok());
2376    }
2377
2378    #[tokio::test]
2379    async fn test_latest_package_query() {
2380        let client = test_client();
2381        let package = client.package_latest("0x2".parse().unwrap()).await;
2382        assert!(
2383            package.is_ok(),
2384            "Latest package query failed for {} network. Error: {}",
2385            client.rpc_server(),
2386            package.unwrap_err()
2387        );
2388
2389        assert!(
2390            package.unwrap().is_some(),
2391            "Latest package for 0x2 query returned None for {} network",
2392            client.rpc_server()
2393        );
2394    }
2395
2396    #[tokio::test]
2397    async fn test_packages_query() {
2398        let client = test_client();
2399        let packages = client
2400            .packages(PaginationFilter::default(), None, None)
2401            .await;
2402        assert!(
2403            packages.is_ok(),
2404            "Packages query failed for {} network. Error: {}",
2405            client.rpc_server(),
2406            packages.unwrap_err()
2407        );
2408
2409        assert!(
2410            !packages.unwrap().is_empty(),
2411            "Packages query returned no data for {} network",
2412            client.rpc_server()
2413        );
2414    }
2415}