iota_sdk_graphql_client/
lib.rs

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