1#![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
78pub enum WaitForTx {
80 Indexed,
83 Finalized,
86}
87
88#[derive(Debug, Clone)]
91pub struct Client {
92 rpc: Url,
94 inner: reqwest::Client,
96 service_config: std::sync::OnceLock<ServiceConfig>,
97}
98
99impl Client {
100 fn rpc_server(&self) -> &Url {
102 &self.rpc
103 }
104
105 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 pub async fn service_config(&self) -> Result<&ServiceConfig> {
116 if let Some(service_config) = self.service_config.get() {
118 return Ok(service_config);
119 }
120
121 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 pub fn new_mainnet() -> Self {
414 Self::new(MAINNET_HOST).expect("Invalid mainnet URL")
415 }
416
417 pub fn new_testnet() -> Self {
420 Self::new(TESTNET_HOST).expect("Invalid testnet URL")
421 }
422
423 pub fn new_devnet() -> Self {
426 Self::new(DEVNET_HOST).expect("Invalid devnet URL")
427 }
428
429 pub fn new_localnet() -> Self {
432 Self::new(LOCAL_HOST).expect("Invalid localhost URL")
433 }
434
435 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 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 pub async fn max_page_size(&self) -> Result<i32> {
467 self.service_config().await.map(|cfg| cfg.max_page_size)
468 }
469
470 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 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 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 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 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 pub async fn total_transaction_blocks(&self) -> Result<Option<u64>> {
554 self.internal_total_transaction_blocks(None, None).await
555 }
556
557 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 #[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 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 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 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 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 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 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#[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 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 #[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}