1use 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 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 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 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#[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#[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#[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}