sui_rpc_api/client/
mod.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use bytes::Bytes;
5use fastcrypto::traits::ToFromBytes;
6use futures::stream::Stream;
7use futures::stream::TryStreamExt;
8use prost_types::FieldMask;
9use prost_types::value::Kind as ProtoValueKind;
10use std::time::Duration;
11use sui_rpc::field::FieldMaskUtil;
12use sui_rpc::proto::TryFromProtoError;
13use sui_rpc::proto::sui::rpc::v2::{self as proto, GetServiceInfoRequest};
14use sui_types::base_types::{ObjectID, SequenceNumber, SuiAddress};
15use sui_types::digests::ChainIdentifier;
16use sui_types::digests::TransactionDigest;
17use sui_types::effects::{TransactionEffects, TransactionEvents};
18use sui_types::full_checkpoint_content::Checkpoint;
19use sui_types::messages_checkpoint::{CertifiedCheckpointSummary, CheckpointSequenceNumber};
20use sui_types::object::Object;
21use sui_types::signature::GenericSignature;
22use sui_types::transaction::Transaction;
23use sui_types::transaction::TransactionData;
24use tap::Pipe;
25use tonic::Status;
26use tonic::metadata::MetadataMap;
27
28pub use sui_rpc::client::HeadersInterceptor;
29pub use sui_rpc::client::ResponseExt;
30
31pub type Result<T, E = tonic::Status> = std::result::Result<T, E>;
32pub type BoxError = Box<dyn std::error::Error + Send + Sync + 'static>;
33
34pub struct Page<T> {
35    pub items: Vec<T>,
36    pub next_page_token: Option<Bytes>,
37}
38
39#[derive(Clone)]
40pub struct Client(sui_rpc::Client);
41
42impl Client {
43    pub fn new<T>(uri: T) -> Result<Self>
44    where
45        T: TryInto<http::Uri>,
46        T::Error: Into<BoxError>,
47    {
48        sui_rpc::Client::new(uri).map(Self)
49    }
50
51    pub fn with_headers(self, headers: HeadersInterceptor) -> Self {
52        Self(self.0.with_headers(headers))
53    }
54
55    pub fn inner_mut(&mut self) -> &mut sui_rpc::Client {
56        &mut self.0
57    }
58
59    pub fn into_inner(self) -> sui_rpc::Client {
60        self.0
61    }
62
63    pub async fn get_latest_checkpoint(&mut self) -> Result<CertifiedCheckpointSummary> {
64        self.get_checkpoint_internal(None).await
65    }
66
67    pub async fn get_checkpoint_summary(
68        &mut self,
69        sequence_number: CheckpointSequenceNumber,
70    ) -> Result<CertifiedCheckpointSummary> {
71        self.get_checkpoint_internal(Some(sequence_number)).await
72    }
73
74    async fn get_checkpoint_internal(
75        &mut self,
76        sequence_number: Option<CheckpointSequenceNumber>,
77    ) -> Result<CertifiedCheckpointSummary> {
78        let mut request = proto::GetCheckpointRequest::default()
79            .with_read_mask(FieldMask::from_paths(["summary.bcs", "signature"]));
80        request.checkpoint_id = sequence_number.map(|sequence_number| {
81            proto::get_checkpoint_request::CheckpointId::SequenceNumber(sequence_number)
82        });
83
84        let (metadata, checkpoint, _extentions) = self
85            .0
86            .ledger_client()
87            .get_checkpoint(request)
88            .await?
89            .into_parts();
90
91        let checkpoint = checkpoint
92            .checkpoint
93            .ok_or_else(|| tonic::Status::not_found("no checkpoint returned"))?;
94        certified_checkpoint_summary_try_from_proto(&checkpoint)
95            .map_err(|e| status_from_error_with_metadata(e, metadata))
96    }
97
98    pub async fn get_full_checkpoint(
99        &mut self,
100        sequence_number: CheckpointSequenceNumber,
101    ) -> Result<Checkpoint> {
102        let request = proto::GetCheckpointRequest::by_sequence_number(sequence_number)
103            .with_read_mask(Checkpoint::proto_field_mask());
104
105        let (metadata, response, _extentions) = self
106            .0
107            .ledger_client()
108            .max_decoding_message_size(128 * 1024 * 1024)
109            .get_checkpoint(request)
110            .await?
111            .into_parts();
112
113        let checkpoint = response
114            .checkpoint
115            .ok_or_else(|| tonic::Status::not_found("no checkpoint returned"))?;
116        sui_types::full_checkpoint_content::Checkpoint::try_from(&checkpoint)
117            .map_err(|e| status_from_error_with_metadata(e, metadata))
118    }
119
120    pub async fn get_object(&mut self, object_id: ObjectID) -> Result<Object> {
121        self.get_object_internal(object_id, None).await
122    }
123
124    pub async fn get_object_with_version(
125        &mut self,
126        object_id: ObjectID,
127        version: SequenceNumber,
128    ) -> Result<Object> {
129        self.get_object_internal(object_id, Some(version.value()))
130            .await
131    }
132
133    async fn get_object_internal(
134        &mut self,
135        object_id: ObjectID,
136        version: Option<u64>,
137    ) -> Result<Object> {
138        let mut request = proto::GetObjectRequest::new(&object_id.into())
139            .with_read_mask(FieldMask::from_paths(["bcs"]));
140        request.version = version;
141
142        let (metadata, object, _extentions) = self
143            .0
144            .ledger_client()
145            .get_object(request)
146            .await?
147            .into_parts();
148
149        let object = object
150            .object
151            .ok_or_else(|| tonic::Status::not_found("no object returned"))?;
152        object_try_from_proto(&object).map_err(|e| status_from_error_with_metadata(e, metadata))
153    }
154
155    pub async fn batch_get_objects(&self, ids: &[ObjectID]) -> Result<Vec<Object>> {
156        let request = proto::BatchGetObjectsRequest::default()
157            .with_requests(
158                ids.iter()
159                    .map(|id| proto::GetObjectRequest::new(&(*id).into()))
160                    .collect(),
161            )
162            .with_read_mask(FieldMask::from_paths(["bcs"]));
163
164        let (metadata, response, _extentions) = self
165            .0
166            .clone()
167            .ledger_client()
168            .batch_get_objects(request)
169            .await?
170            .into_parts();
171
172        let objects = response
173            .objects
174            .into_iter()
175            .map(|o| o.to_result())
176            .collect::<Result<Vec<_>, _>>()
177            .map_err(|e| Status::not_found(e.message))?;
178
179        let objects = objects
180            .iter()
181            .map(object_try_from_proto)
182            .collect::<Result<_, _>>()
183            .map_err(|e| status_from_error_with_metadata(e, metadata))?;
184        Ok(objects)
185    }
186
187    pub async fn execute_transaction(
188        &mut self,
189        transaction: &Transaction,
190    ) -> Result<ExecutedTransaction> {
191        let request = Self::create_executed_transaction_request(transaction)?;
192
193        let (metadata, response, _extentions) = self
194            .0
195            .execution_client()
196            .execute_transaction(request)
197            .await?
198            .into_parts();
199
200        execute_transaction_response_try_from_proto(&response)
201            .map_err(|e| status_from_error_with_metadata(e, metadata))
202    }
203
204    pub async fn execute_transaction_and_wait_for_checkpoint(
205        &self,
206        transaction: &Transaction,
207    ) -> Result<ExecutedTransaction> {
208        const WAIT_FOR_CHECKPOINT_TIMEOUT: Duration = Duration::from_secs(30);
209
210        let request = Self::create_executed_transaction_request(transaction)?;
211
212        let (metadata, response, _extentions) = self
213            .0
214            .clone()
215            .execute_transaction_and_wait_for_checkpoint(request, WAIT_FOR_CHECKPOINT_TIMEOUT)
216            .await
217            .map_err(|e| Status::from_error(e.into()))?
218            .into_parts();
219
220        execute_transaction_response_try_from_proto(&response)
221            .map_err(|e| status_from_error_with_metadata(e, metadata))
222    }
223
224    fn create_executed_transaction_request(
225        transaction: &Transaction,
226    ) -> Result<proto::ExecuteTransactionRequest> {
227        let signatures = transaction
228            .inner()
229            .tx_signatures
230            .iter()
231            .map(|signature| {
232                let mut message = proto::UserSignature::default();
233                message.bcs = Some(signature.as_ref().to_vec().into());
234                message
235            })
236            .collect();
237
238        let request = proto::ExecuteTransactionRequest::new({
239            let mut tx = proto::Transaction::default();
240            tx.bcs = Some(
241                proto::Bcs::serialize(&transaction.inner().intent_message.value)
242                    .map_err(|e| Status::from_error(e.into()))?,
243            );
244            tx
245        })
246        .with_signatures(signatures)
247        .with_read_mask(ExecutedTransaction::proto_read_mask());
248
249        Ok(request)
250    }
251
252    pub async fn simulate_transaction(
253        &self,
254        tx: &TransactionData,
255        checks: bool,
256    ) -> Result<SimulateTransactionResponse> {
257        let mut request = proto::SimulateTransactionRequest::default();
258        request.set_checks(if checks {
259            proto::simulate_transaction_request::TransactionChecks::Enabled
260        } else {
261            proto::simulate_transaction_request::TransactionChecks::Disabled
262        });
263        request.set_transaction(
264            proto::Transaction::default()
265                .with_bcs(proto::Bcs::serialize(&tx).map_err(|e| Status::from_error(e.into()))?),
266        );
267
268        let (metadata, response, _extentions) = self
269            .0
270            .clone()
271            .execution_client()
272            .simulate_transaction(request)
273            .await?
274            .into_parts();
275
276        let transaction = executed_transaction_try_from_proto(response.transaction())
277            .map_err(|e| status_from_error_with_metadata(e, metadata))?;
278
279        Ok(SimulateTransactionResponse {
280            transaction,
281            command_outputs: response.command_outputs,
282            suggested_gas_price: response.suggested_gas_price,
283        })
284    }
285
286    pub async fn get_transaction(
287        &mut self,
288        digest: &TransactionDigest,
289    ) -> Result<ExecutedTransaction> {
290        let request = proto::GetTransactionRequest::new(&(*digest).into())
291            .with_read_mask(ExecutedTransaction::proto_read_mask());
292
293        let (metadata, resp, _extentions) = self
294            .0
295            .ledger_client()
296            .get_transaction(request)
297            .await?
298            .into_parts();
299
300        let transaction = resp
301            .transaction
302            .ok_or_else(|| tonic::Status::not_found("no transaction returned"))?;
303        executed_transaction_try_from_proto(&transaction)
304            .map_err(|e| status_from_error_with_metadata(e, metadata))
305    }
306
307    pub async fn get_chain_identifier(&self) -> Result<ChainIdentifier> {
308        let response = self
309            .0
310            .clone()
311            .ledger_client()
312            .get_service_info(GetServiceInfoRequest::default())
313            .await?
314            .into_inner();
315        let chain_id = response
316            .chain_id()
317            .parse::<sui_sdk_types::Digest>()
318            .map_err(|e| TryFromProtoError::invalid("chain_id", e))
319            .map_err(|e| Status::from_error(e.into()))?;
320
321        Ok(ChainIdentifier::from(
322            sui_types::digests::CheckpointDigest::from(chain_id),
323        ))
324    }
325
326    pub async fn get_owned_objects(
327        &self,
328        owner: SuiAddress,
329        object_type: Option<move_core_types::language_storage::StructTag>,
330        page_size: Option<u32>,
331        page_token: Option<Bytes>,
332    ) -> Result<Page<Object>> {
333        let mut request = proto::ListOwnedObjectsRequest::default()
334            .with_owner(owner.to_string())
335            .with_read_mask(FieldMask::from_paths(["bcs"]));
336        if let Some(object_type) = object_type {
337            request.set_object_type(object_type.to_canonical_string(true));
338        }
339
340        if let Some(page_size) = page_size {
341            request.set_page_size(page_size);
342        }
343
344        if let Some(page_token) = page_token {
345            request.set_page_token(page_token);
346        }
347
348        let (metadata, response, _extentions) = self
349            .0
350            .clone()
351            .state_client()
352            .list_owned_objects(request)
353            .await?
354            .into_parts();
355
356        let objects = response
357            .objects()
358            .iter()
359            .map(object_try_from_proto)
360            .collect::<Result<_, _>>()
361            .map_err(|e| status_from_error_with_metadata(e, metadata))?;
362
363        Ok(Page {
364            items: objects,
365            next_page_token: response.next_page_token,
366        })
367    }
368
369    pub fn list_owned_objects(
370        &self,
371        owner: SuiAddress,
372        object_type: Option<move_core_types::language_storage::StructTag>,
373    ) -> impl Stream<Item = Result<Object>> + 'static {
374        let mut request = proto::ListOwnedObjectsRequest::default()
375            .with_owner(owner.to_string())
376            .with_read_mask(FieldMask::from_paths(["bcs"]));
377
378        if let Some(object_type) = object_type {
379            request.set_object_type(object_type.to_canonical_string(true));
380        }
381
382        self.0
383            .list_owned_objects(request)
384            .and_then(|object| async move {
385                object_try_from_proto(&object).map_err(|e| Status::from_error(e.into()))
386            })
387    }
388
389    pub async fn get_dynamic_fields(
390        &self,
391        parent: ObjectID,
392        page_size: Option<u32>,
393        page_token: Option<Bytes>,
394    ) -> Result<proto::ListDynamicFieldsResponse> {
395        let mut request = proto::ListDynamicFieldsRequest::default()
396            .with_parent(parent.to_string())
397            .with_read_mask(FieldMask::from_paths(["*"]));
398
399        if let Some(page_size) = page_size {
400            request.set_page_size(page_size);
401        }
402
403        if let Some(page_token) = page_token {
404            request.set_page_token(page_token);
405        }
406
407        let response = self
408            .0
409            .clone()
410            .state_client()
411            .list_dynamic_fields(request)
412            .await?
413            .into_inner();
414
415        Ok(response)
416    }
417
418    pub async fn get_reference_gas_price(&self) -> Result<u64> {
419        let request = proto::GetEpochRequest::default()
420            .with_read_mask(FieldMask::from_paths(["epoch", "reference_gas_price"]));
421
422        let response = self
423            .0
424            .clone()
425            .ledger_client()
426            .get_epoch(request)
427            .await?
428            .into_inner();
429
430        Ok(response.epoch().reference_gas_price())
431    }
432
433    /// Wait for a transaction to be available in the ledger AND indexed (equivalent to WaitForLocalExecution)
434    pub async fn wait_for_transaction(
435        &self,
436        digest: &sui_types::digests::TransactionDigest,
437    ) -> Result<(), anyhow::Error> {
438        const WAIT_FOR_LOCAL_EXECUTION_TIMEOUT: Duration = Duration::from_secs(30);
439        const WAIT_FOR_LOCAL_EXECUTION_DELAY: Duration = Duration::from_millis(200);
440        const WAIT_FOR_LOCAL_EXECUTION_INTERVAL: Duration = Duration::from_millis(500);
441
442        let mut client = self.0.clone();
443        let mut client = client.ledger_client();
444
445        tokio::time::timeout(WAIT_FOR_LOCAL_EXECUTION_TIMEOUT, async {
446            // Apply a short delay to give the full node a chance to catch up.
447            tokio::time::sleep(WAIT_FOR_LOCAL_EXECUTION_DELAY).await;
448
449            let mut interval = tokio::time::interval(WAIT_FOR_LOCAL_EXECUTION_INTERVAL);
450            loop {
451                interval.tick().await;
452
453                let request = proto::GetTransactionRequest::default()
454                    .with_digest(digest.to_string())
455                    .with_read_mask(prost_types::FieldMask::from_paths(["digest", "checkpoint"]));
456
457                if let Ok(response) = client.get_transaction(request).await {
458                    let tx = response.into_inner().transaction;
459                    if let Some(executed_tx) = tx {
460                        // Check that transaction is indexed (checkpoint field is populated)
461                        if executed_tx.checkpoint.is_some() {
462                            break;
463                        }
464                    }
465                }
466            }
467        })
468        .await
469        .map_err(|_| anyhow::anyhow!("Timeout waiting for transaction indexing: {}", digest))?;
470
471        Ok(())
472    }
473
474    pub async fn get_protocol_config(&self, epoch: Option<u64>) -> Result<proto::ProtocolConfig> {
475        let mut request = proto::GetEpochRequest::default();
476        if let Some(epoch) = epoch {
477            request.set_epoch(epoch);
478        }
479        request.set_read_mask(FieldMask::from_paths([
480            proto::Epoch::path_builder().epoch(),
481            proto::Epoch::path_builder().protocol_config().finish(),
482        ]));
483        let mut response = self
484            .0
485            .clone()
486            .ledger_client()
487            .get_epoch(request)
488            .await?
489            .into_inner();
490
491        Ok(response
492            .epoch_mut()
493            .protocol_config
494            .take()
495            .unwrap_or_default())
496    }
497
498    pub async fn get_system_state(&self, epoch: Option<u64>) -> Result<Box<proto::SystemState>> {
499        let mut request = proto::GetEpochRequest::default();
500        if let Some(epoch) = epoch {
501            request.set_epoch(epoch);
502        }
503        request.set_read_mask(FieldMask::from_paths([
504            proto::Epoch::path_builder().epoch(),
505            proto::Epoch::path_builder().system_state().finish(),
506        ]));
507        let mut response = self
508            .0
509            .clone()
510            .ledger_client()
511            .get_epoch(request)
512            .await?
513            .into_inner();
514
515        Ok(response.epoch_mut().system_state.take().unwrap_or_default())
516    }
517
518    pub async fn get_system_state_summary(
519        &self,
520        epoch: Option<u64>,
521    ) -> Result<sui_types::sui_system_state::sui_system_state_summary::SuiSystemStateSummary> {
522        let system_state = self.get_system_state(epoch).await?;
523        system_state
524            .as_ref()
525            .try_into()
526            .map_err(|e: TryFromProtoError| tonic::Status::from_error(e.into()))
527    }
528
529    pub async fn get_committee(
530        &self,
531        epoch: Option<u64>,
532    ) -> Result<sui_types::committee::Committee> {
533        let mut request = proto::GetEpochRequest::default();
534        if let Some(epoch) = epoch {
535            request.set_epoch(epoch);
536        }
537        request.set_read_mask(FieldMask::from_paths([
538            proto::Epoch::path_builder().epoch(),
539            proto::Epoch::path_builder().committee().finish(),
540        ]));
541        let response = self
542            .0
543            .clone()
544            .ledger_client()
545            .get_epoch(request)
546            .await?
547            .into_inner();
548
549        response
550            .epoch()
551            .committee()
552            .try_into()
553            .map_err(|e: TryFromProtoError| tonic::Status::from_error(e.into()))
554    }
555
556    pub async fn get_coin_info(
557        &self,
558        coin_type: &move_core_types::language_storage::StructTag,
559    ) -> Result<proto::GetCoinInfoResponse> {
560        let resp = self
561            .0
562            .clone()
563            .state_client()
564            .get_coin_info(
565                proto::GetCoinInfoRequest::default()
566                    .with_coin_type(coin_type.to_canonical_string(true)),
567            )
568            .await?
569            .into_inner();
570        Ok(resp)
571    }
572
573    pub async fn get_balance(
574        &self,
575        owner: SuiAddress,
576        coin_type: &move_core_types::language_storage::StructTag,
577    ) -> Result<proto::Balance> {
578        let resp = self
579            .0
580            .clone()
581            .state_client()
582            .get_balance(
583                proto::GetBalanceRequest::default()
584                    .with_owner(owner.to_string())
585                    .with_coin_type(coin_type.to_canonical_string(true)),
586            )
587            .await?
588            .into_inner();
589
590        Ok(resp.balance.unwrap_or_default())
591    }
592
593    pub fn list_balances(
594        &self,
595        owner: SuiAddress,
596    ) -> impl Stream<Item = Result<proto::Balance>> + 'static {
597        self.0
598            .list_balances(proto::ListBalancesRequest::default().with_owner(owner.to_string()))
599    }
600
601    pub async fn list_delegated_stake(
602        &self,
603        owner: SuiAddress,
604    ) -> Result<Vec<sui_rpc::client::DelegatedStake>> {
605        self.0.clone().list_delegated_stake(&owner.into()).await
606    }
607
608    pub fn transaction_builder(&self) -> sui_transaction_builder::TransactionBuilder {
609        sui_transaction_builder::TransactionBuilder::new(std::sync::Arc::new(self.clone()) as _)
610    }
611}
612
613#[derive(Clone, Debug, serde::Serialize)]
614pub struct ExecutedTransaction {
615    pub transaction: TransactionData,
616    pub signatures: Vec<GenericSignature>,
617    pub effects: TransactionEffects,
618    pub clever_error: Option<proto::CleverError>,
619    pub events: Option<TransactionEvents>,
620    pub event_json: Vec<Option<serde_json::Value>>,
621    pub changed_objects: Vec<proto::ChangedObject>,
622    #[allow(unused)]
623    unchanged_loaded_runtime_objects: Vec<proto::ObjectReference>,
624    pub balance_changes: Vec<sui_sdk_types::BalanceChange>,
625    pub checkpoint: Option<u64>,
626    #[allow(unused)]
627    #[serde(skip)]
628    timestamp: Option<prost_types::Timestamp>,
629}
630
631impl ExecutedTransaction {
632    fn proto_read_mask() -> FieldMask {
633        use proto::ExecutedTransaction;
634        FieldMask::from_paths([
635            ExecutedTransaction::path_builder()
636                .transaction()
637                .bcs()
638                .finish(),
639            ExecutedTransaction::path_builder()
640                .signatures()
641                .bcs()
642                .finish(),
643            ExecutedTransaction::path_builder().effects().bcs().finish(),
644            ExecutedTransaction::path_builder()
645                .effects()
646                .status()
647                .error()
648                .abort()
649                .clever_error()
650                .finish(),
651            ExecutedTransaction::path_builder()
652                .effects()
653                .unchanged_loaded_runtime_objects()
654                .finish(),
655            ExecutedTransaction::path_builder()
656                .effects()
657                .changed_objects()
658                .finish(),
659            ExecutedTransaction::path_builder().events().bcs().finish(),
660            ExecutedTransaction::path_builder().events().events().json(),
661            ExecutedTransaction::path_builder()
662                .balance_changes()
663                .finish(),
664            ExecutedTransaction::path_builder().checkpoint(),
665            ExecutedTransaction::path_builder().timestamp(),
666        ])
667    }
668
669    pub fn get_new_package_obj(&self) -> Option<sui_types::base_types::ObjectRef> {
670        use sui_rpc::proto::sui::rpc::v2::changed_object::OutputObjectState;
671
672        self.changed_objects
673            .iter()
674            .find(|o| matches!(o.output_state(), OutputObjectState::PackageWrite))
675            .and_then(|o| {
676                let id = o.object_id().parse().ok()?;
677                let version = o.output_version().into();
678                let digest = o.output_digest().parse().ok()?;
679                Some((id, version, digest))
680            })
681    }
682
683    pub fn get_new_package_upgrade_cap(&self) -> Option<sui_types::base_types::ObjectRef> {
684        use sui_rpc::proto::sui::rpc::v2::changed_object::OutputObjectState;
685        use sui_rpc::proto::sui::rpc::v2::owner::OwnerKind;
686
687        const UPGRADE_CAP: &str = "0x0000000000000000000000000000000000000000000000000000000000000002::package::UpgradeCap";
688
689        self.changed_objects
690            .iter()
691            .find(|o| {
692                matches!(o.output_state(), OutputObjectState::ObjectWrite)
693                    && matches!(
694                        o.output_owner().kind(),
695                        OwnerKind::Address | OwnerKind::ConsensusAddress
696                    )
697                    && o.object_type() == UPGRADE_CAP
698            })
699            .and_then(|o| {
700                let id = o.object_id().parse().ok()?;
701                let version = o.output_version().into();
702                let digest = o.output_digest().parse().ok()?;
703                Some((id, version, digest))
704            })
705    }
706
707    pub fn timestamp_ms(&self) -> Option<u64> {
708        self.timestamp
709            .and_then(|timestamp| sui_rpc::proto::proto_to_timestamp_ms(timestamp).ok())
710    }
711}
712
713#[derive(Clone, Debug, serde::Serialize)]
714pub struct SimulateTransactionResponse {
715    pub transaction: ExecutedTransaction,
716    pub command_outputs: Vec<proto::CommandResult>,
717    pub suggested_gas_price: Option<u64>,
718}
719
720/// Attempts to parse `CertifiedCheckpointSummary` from a proto::Checkpoint
721#[allow(clippy::result_large_err)]
722fn certified_checkpoint_summary_try_from_proto(
723    checkpoint: &proto::Checkpoint,
724) -> Result<CertifiedCheckpointSummary, TryFromProtoError> {
725    let summary = checkpoint
726        .summary
727        .as_ref()
728        .and_then(|summary| summary.bcs.as_ref())
729        .ok_or_else(|| TryFromProtoError::missing("summary.bcs"))?
730        .deserialize()
731        .map_err(|e| TryFromProtoError::invalid("summary.bcs", e))?;
732
733    let signature = sui_types::crypto::AuthorityStrongQuorumSignInfo::from(
734        sui_sdk_types::ValidatorAggregatedSignature::try_from(
735            checkpoint
736                .signature
737                .as_ref()
738                .ok_or_else(|| TryFromProtoError::missing("signature"))?,
739        )
740        .map_err(|e| TryFromProtoError::invalid("signature", e))?,
741    );
742
743    Ok(CertifiedCheckpointSummary::new_from_data_and_sig(
744        summary, signature,
745    ))
746}
747
748/// Attempts to parse `Object` from the bcs fields in `GetObjectResponse`
749#[allow(clippy::result_large_err)]
750fn object_try_from_proto(object: &proto::Object) -> Result<Object, TryFromProtoError> {
751    object
752        .bcs
753        .as_ref()
754        .ok_or_else(|| TryFromProtoError::missing("bcs"))?
755        .deserialize()
756        .map_err(|e| TryFromProtoError::invalid("bcs", e))
757}
758
759/// Attempts to parse `ExecutedTransaction` from the fields in `proto::ExecuteTransactionResponse`
760#[allow(clippy::result_large_err)]
761fn execute_transaction_response_try_from_proto(
762    response: &proto::ExecuteTransactionResponse,
763) -> Result<ExecutedTransaction, TryFromProtoError> {
764    let executed_transaction = response
765        .transaction
766        .as_ref()
767        .ok_or_else(|| TryFromProtoError::missing("transaction"))?;
768
769    executed_transaction_try_from_proto(executed_transaction)
770}
771
772#[allow(clippy::result_large_err)]
773fn executed_transaction_try_from_proto(
774    executed_transaction: &proto::ExecutedTransaction,
775) -> Result<ExecutedTransaction, TryFromProtoError> {
776    let transaction = executed_transaction
777        .transaction()
778        .bcs()
779        .deserialize()
780        .map_err(|e| TryFromProtoError::invalid("transaction.bcs", e))?;
781
782    let effects = executed_transaction
783        .effects()
784        .bcs()
785        .deserialize()
786        .map_err(|e| TryFromProtoError::invalid("effects.bcs", e))?;
787    let signatures = executed_transaction
788        .signatures()
789        .iter()
790        .map(|sig| {
791            GenericSignature::from_bytes(sig.bcs().value())
792                .map_err(|e| TryFromProtoError::invalid("signatures.bcs", e))
793        })
794        .collect::<Result<_, _>>()?;
795    let clever_error = executed_transaction
796        .effects()
797        .status()
798        .error()
799        .abort()
800        .clever_error_opt()
801        .cloned();
802    let events = executed_transaction
803        .events
804        .as_ref()
805        .and_then(|events| events.bcs.as_ref())
806        .map(|bcs| bcs.deserialize())
807        .transpose()
808        .map_err(|e| TryFromProtoError::invalid("events.bcs", e))?;
809    let event_json = executed_transaction
810        .events_opt()
811        .map(|events| {
812            events
813                .events()
814                .iter()
815                .map(|event| event.json_opt().map(proto_value_to_json_value))
816                .collect::<Vec<_>>()
817        })
818        .unwrap_or_default();
819
820    let balance_changes = executed_transaction
821        .balance_changes
822        .iter()
823        .map(TryInto::try_into)
824        .collect::<Result<_, _>>()?;
825
826    ExecutedTransaction {
827        transaction,
828        signatures,
829        effects,
830        clever_error,
831        events,
832        event_json,
833        balance_changes,
834        checkpoint: executed_transaction.checkpoint,
835        changed_objects: executed_transaction.effects().changed_objects().to_owned(),
836        unchanged_loaded_runtime_objects: executed_transaction
837            .effects()
838            .unchanged_loaded_runtime_objects()
839            .to_owned(),
840        timestamp: executed_transaction.timestamp,
841    }
842    .pipe(Ok)
843}
844
845fn proto_value_to_json_value(proto: &prost_types::Value) -> serde_json::Value {
846    match proto.kind.as_ref() {
847        Some(ProtoValueKind::NullValue(_)) | None => serde_json::Value::Null,
848        Some(ProtoValueKind::NumberValue(n)) => serde_json::Value::from(*n),
849        Some(ProtoValueKind::StringValue(s)) => serde_json::Value::from(s.clone()),
850        Some(ProtoValueKind::BoolValue(b)) => serde_json::Value::from(*b),
851        Some(ProtoValueKind::StructValue(map)) => serde_json::Value::Object(
852            map.fields
853                .iter()
854                .map(|(k, v)| (k.clone(), proto_value_to_json_value(v)))
855                .collect(),
856        ),
857        Some(ProtoValueKind::ListValue(list_value)) => serde_json::Value::Array(
858            list_value
859                .values
860                .iter()
861                .map(proto_value_to_json_value)
862                .collect(),
863        ),
864    }
865}
866
867fn status_from_error_with_metadata<T: Into<BoxError>>(err: T, metadata: MetadataMap) -> Status {
868    let mut status = Status::from_error(err.into());
869    *status.metadata_mut() = metadata;
870    status
871}
872
873#[async_trait::async_trait]
874impl sui_transaction_builder::DataReader for Client {
875    async fn get_owned_objects(
876        &self,
877        address: SuiAddress,
878        object_type: move_core_types::language_storage::StructTag,
879    ) -> Result<Vec<sui_types::base_types::ObjectInfo>, anyhow::Error> {
880        self.list_owned_objects(address, Some(object_type))
881            .map_ok(|o| sui_types::base_types::ObjectInfo::from_object(&o))
882            .try_collect()
883            .await
884            .map_err(Into::into)
885    }
886
887    async fn get_object(&self, object_id: ObjectID) -> Result<Object, anyhow::Error> {
888        let mut client = self.clone();
889        Self::get_object(&mut client, object_id)
890            .await
891            .map_err(Into::into)
892    }
893
894    async fn get_reference_gas_price(&self) -> Result<u64, anyhow::Error> {
895        self.get_reference_gas_price().await.map_err(Into::into)
896    }
897}