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