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