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