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