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