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