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 (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 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 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 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#[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#[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#[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}