1use std::error::Error as StdError;
5use std::fmt::{self, Display, Formatter, Write};
6use std::str::FromStr;
7
8use enum_dispatch::enum_dispatch;
9use serde::{Deserialize, Serialize};
10use serde_with::base64::Base64;
11use serde_with::{DeserializeAs, DisplayFromStr, IfIsHumanReadable, SerializeAs, serde_as};
12use sui_sdk_types::{
13 Address,
14 ConsensusDeterminedVersionAssignments,
15 Digest,
16 EpochId,
17 GasCostSummary,
18 MoveLocation,
19 TypeTag,
20 UserSignature,
21 Version,
22};
23use tabled::builder::Builder as TableBuilder;
24use tabled::settings::style::HorizontalLine;
25use tabled::settings::{Panel as TablePanel, Style as TableStyle};
26
27use super::balance_changes::BalanceChange;
28use super::object_changes::ObjectChange;
29use super::{Page, SuiEvent, SuiObjectRef};
30use crate::msgs::Owner;
31use crate::serde::BigInt;
32
33pub type SuiEpochId = BigInt<u64>;
35
36#[derive(Debug, Clone, Deserialize, Serialize, Default)]
37#[serde(
38 rename_all = "camelCase",
39 rename = "TransactionBlockResponseQuery",
40 default
41)]
42pub struct SuiTransactionBlockResponseQuery {
43 pub filter: Option<TransactionFilter>,
45 pub options: Option<SuiTransactionBlockResponseOptions>,
47}
48
49impl SuiTransactionBlockResponseQuery {
50 pub fn new(
51 filter: Option<TransactionFilter>,
52 options: Option<SuiTransactionBlockResponseOptions>,
53 ) -> Self {
54 Self { filter, options }
55 }
56
57 pub fn new_with_filter(filter: TransactionFilter) -> Self {
58 Self {
59 filter: Some(filter),
60 options: None,
61 }
62 }
63}
64
65pub type TransactionBlocksPage = Page<SuiTransactionBlockResponse, Digest>;
66
67#[derive(Debug, Clone, Deserialize, Serialize, Eq, PartialEq, Default)]
68#[serde(
69 rename_all = "camelCase",
70 rename = "TransactionBlockResponseOptions",
71 default
72)]
73pub struct SuiTransactionBlockResponseOptions {
74 pub show_input: bool,
76 pub show_raw_input: bool,
78 pub show_effects: bool,
80 pub show_events: bool,
82 pub show_object_changes: bool,
84 pub show_balance_changes: bool,
86 pub show_raw_effects: bool,
88}
89
90impl SuiTransactionBlockResponseOptions {
91 pub fn new() -> Self {
92 Self::default()
93 }
94
95 pub fn full_content() -> Self {
96 Self {
97 show_effects: true,
98 show_input: true,
99 show_raw_input: true,
100 show_events: true,
101 show_object_changes: true,
102 show_balance_changes: true,
103 show_raw_effects: false,
106 }
107 }
108
109 pub fn with_input(mut self) -> Self {
110 self.show_input = true;
111 self
112 }
113
114 pub fn with_raw_input(mut self) -> Self {
115 self.show_raw_input = true;
116 self
117 }
118
119 pub fn with_effects(mut self) -> Self {
120 self.show_effects = true;
121 self
122 }
123
124 pub fn with_events(mut self) -> Self {
125 self.show_events = true;
126 self
127 }
128
129 pub fn with_balance_changes(mut self) -> Self {
130 self.show_balance_changes = true;
131 self
132 }
133
134 pub fn with_object_changes(mut self) -> Self {
135 self.show_object_changes = true;
136 self
137 }
138
139 pub fn with_raw_effects(mut self) -> Self {
140 self.show_raw_effects = true;
141 self
142 }
143
144 pub fn default_execution_request_type(&self) -> ExecuteTransactionRequestType {
147 if self.require_effects() {
149 ExecuteTransactionRequestType::WaitForLocalExecution
150 } else {
151 ExecuteTransactionRequestType::WaitForEffectsCert
152 }
153 }
154
155 pub fn require_input(&self) -> bool {
156 self.show_input || self.show_raw_input || self.show_object_changes
157 }
158
159 pub fn require_effects(&self) -> bool {
160 self.show_effects
161 || self.show_events
162 || self.show_balance_changes
163 || self.show_object_changes
164 || self.show_raw_effects
165 }
166
167 pub fn only_digest(&self) -> bool {
168 self == &Self::default()
169 }
170}
171
172#[derive(Serialize, Deserialize, Clone, Debug)]
173pub enum ExecuteTransactionRequestType {
174 WaitForEffectsCert,
177 WaitForLocalExecution,
187}
188
189#[serde_as]
190#[derive(Serialize, Deserialize, Debug, Clone, Default)]
191#[serde(rename_all = "camelCase", rename = "TransactionBlockResponse")]
192pub struct SuiTransactionBlockResponse {
193 pub digest: Digest,
194 #[serde(skip_serializing_if = "Option::is_none")]
196 pub transaction: Option<SuiTransactionBlock>,
197 #[serde_as(as = "Base64")]
200 #[serde(skip_serializing_if = "Vec::is_empty", default)]
201 pub raw_transaction: Vec<u8>,
202 #[serde(skip_serializing_if = "Option::is_none")]
203 pub effects: Option<SuiTransactionBlockEffects>,
204 #[serde(skip_serializing_if = "Option::is_none")]
205 pub events: Option<SuiTransactionBlockEvents>,
206 #[serde(skip_serializing_if = "Option::is_none")]
207 pub object_changes: Option<Vec<ObjectChange>>,
208 #[serde(skip_serializing_if = "Option::is_none")]
209 pub balance_changes: Option<Vec<BalanceChange>>,
210 #[serde(default, skip_serializing_if = "Option::is_none")]
211 #[serde_as(as = "Option<BigInt<u64>>")]
212 pub timestamp_ms: Option<u64>,
213 #[serde(default, skip_serializing_if = "Option::is_none")]
214 pub confirmed_local_execution: Option<bool>,
215 #[serde_as(as = "Option<BigInt<u64>>")]
218 #[serde(skip_serializing_if = "Option::is_none")]
219 pub checkpoint: Option<Version>,
220 #[serde(skip_serializing_if = "Vec::is_empty", default)]
221 pub errors: Vec<String>,
222 #[serde(skip_serializing_if = "Vec::is_empty", default)]
223 pub raw_effects: Vec<u8>,
224}
225
226impl SuiTransactionBlockResponse {
227 pub fn new(digest: Digest) -> Self {
228 Self {
229 digest,
230 ..Default::default()
231 }
232 }
233
234 pub fn status_ok(&self) -> Option<bool> {
235 self.effects.as_ref().map(|e| e.status().is_ok())
236 }
237
238 pub fn sui_effects(&self) -> Result<Option<sui_sdk_types::TransactionEffects>, ToEffectsError> {
243 self.raw_effects
244 .len()
245 .ge(&0)
246 .then_some(sui_sdk_types::bcs::FromBcs::from_bcs(&self.raw_effects))
247 .transpose()
248 .map_err(From::from)
249 .map_err(ToEffectsError::Generic)
250 }
251
252 pub fn get_transaction(
253 &self,
254 ) -> Result<&SuiTransactionBlock, SuiTransactionBlockResponseError> {
255 self.transaction
256 .as_ref()
257 .ok_or(SuiTransactionBlockResponseError::MissingTransaction)
258 }
259
260 pub fn get_effects(
261 &self,
262 ) -> Result<&SuiTransactionBlockEffectsV1, SuiTransactionBlockResponseError> {
263 let SuiTransactionBlockEffects::V1(effects) = self
264 .effects
265 .as_ref()
266 .ok_or(SuiTransactionBlockResponseError::MissingEffects)?;
267 Ok(effects)
268 }
269
270 pub fn get_events(
271 &self,
272 ) -> Result<&SuiTransactionBlockEvents, SuiTransactionBlockResponseError> {
273 self.events
274 .as_ref()
275 .ok_or(SuiTransactionBlockResponseError::MissingEvents)
276 }
277
278 pub fn get_object_changes(
279 &self,
280 ) -> Result<&Vec<ObjectChange>, SuiTransactionBlockResponseError> {
281 self.object_changes
282 .as_ref()
283 .ok_or(SuiTransactionBlockResponseError::MissingObjectChanges)
284 }
285
286 pub fn get_balance_changes(
287 &self,
288 ) -> Result<&Vec<BalanceChange>, SuiTransactionBlockResponseError> {
289 self.balance_changes
290 .as_ref()
291 .ok_or(SuiTransactionBlockResponseError::MissingBalanceChanges)
292 }
293
294 pub fn try_check_execution_status(&self) -> Result<(), SuiTransactionBlockResponseError> {
295 if let Some(SuiTransactionBlockEffects::V1(effects)) = &self.effects {
296 if let SuiExecutionStatus::Failure { error } = &effects.status {
297 return Err(SuiTransactionBlockResponseError::ExecutionFailure(
298 error.clone(),
299 ));
300 }
301 }
302 Ok(())
303 }
304
305 pub fn check_execution_status(&self) -> Result<(), SuiTransactionBlockResponseError> {
306 let Some(SuiTransactionBlockEffects::V1(effects)) = &self.effects else {
307 return Err(SuiTransactionBlockResponseError::MissingEffects);
308 };
309 if let SuiExecutionStatus::Failure { error } = &effects.status {
310 return Err(SuiTransactionBlockResponseError::ExecutionFailure(
311 error.clone(),
312 ));
313 }
314 Ok(())
315 }
316
317 pub fn published_package_id(&self) -> Result<Address, SuiTransactionBlockResponseError> {
318 for change in self.get_object_changes()? {
319 if let ObjectChange::Published { package_id, .. } = change {
320 return Ok(*package_id);
321 }
322 }
323 Err(SuiTransactionBlockResponseError::NoPublishedPackage)
324 }
325
326 pub fn into_object_changes(
327 self,
328 ) -> Result<Vec<ObjectChange>, SuiTransactionBlockResponseError> {
329 let Self { object_changes, .. } = self;
330 object_changes.ok_or(SuiTransactionBlockResponseError::MissingObjectChanges)
331 }
332}
333
334#[derive(thiserror::Error, Debug)]
335#[non_exhaustive]
336pub enum ToEffectsError {
337 #[error("Generic: {0:#}")]
338 Generic(Box<dyn StdError + Send + Sync + 'static>),
339}
340
341#[derive(thiserror::Error, Clone, Debug, PartialEq, Eq)]
342pub enum SuiTransactionBlockResponseError {
343 #[error("No transaction in response")]
344 MissingTransaction,
345 #[error("No effects in response")]
346 MissingEffects,
347 #[error("No events in response")]
348 MissingEvents,
349 #[error("No object changes in response")]
350 MissingObjectChanges,
351 #[error("No balance changes in response")]
352 MissingBalanceChanges,
353 #[error("Failed to execute transaction block: {0}")]
354 ExecutionFailure(String),
355 #[error("No 'Published' object change")]
356 NoPublishedPackage,
357}
358
359impl PartialEq for SuiTransactionBlockResponse {
361 fn eq(&self, other: &Self) -> bool {
362 self.transaction == other.transaction
363 && self.effects == other.effects
364 && self.timestamp_ms == other.timestamp_ms
365 && self.confirmed_local_execution == other.confirmed_local_execution
366 && self.checkpoint == other.checkpoint
367 }
368}
369
370impl Display for SuiTransactionBlockResponse {
371 fn fmt(&self, writer: &mut Formatter<'_>) -> fmt::Result {
372 writeln!(writer, "Transaction Digest: {}", &self.digest)?;
373
374 if let Some(t) = &self.transaction {
375 writeln!(writer, "{}", t)?;
376 }
377
378 if let Some(e) = &self.effects {
379 writeln!(writer, "{}", e)?;
380 }
381
382 if let Some(e) = &self.events {
383 writeln!(writer, "{}", e)?;
384 }
385
386 if let Some(object_changes) = &self.object_changes {
387 let mut builder = TableBuilder::default();
388 let (
389 mut created,
390 mut deleted,
391 mut mutated,
392 mut published,
393 mut transferred,
394 mut wrapped,
395 ) = (vec![], vec![], vec![], vec![], vec![], vec![]);
396
397 for obj in object_changes {
398 match obj {
399 ObjectChange::Created { .. } => created.push(obj),
400 ObjectChange::Deleted { .. } => deleted.push(obj),
401 ObjectChange::Mutated { .. } => mutated.push(obj),
402 ObjectChange::Published { .. } => published.push(obj),
403 ObjectChange::Transferred { .. } => transferred.push(obj),
404 ObjectChange::Wrapped { .. } => wrapped.push(obj),
405 };
406 }
407
408 write_obj_changes(created, "Created", &mut builder)?;
409 write_obj_changes(deleted, "Deleted", &mut builder)?;
410 write_obj_changes(mutated, "Mutated", &mut builder)?;
411 write_obj_changes(published, "Published", &mut builder)?;
412 write_obj_changes(transferred, "Transferred", &mut builder)?;
413 write_obj_changes(wrapped, "Wrapped", &mut builder)?;
414
415 let mut table = builder.build();
416 table.with(TablePanel::header("Object Changes"));
417 table.with(TableStyle::rounded().horizontals([HorizontalLine::new(
418 1,
419 TableStyle::modern().get_horizontal(),
420 )]));
421 writeln!(writer, "{}", table)?;
422 }
423
424 if let Some(balance_changes) = &self.balance_changes {
425 let mut builder = TableBuilder::default();
426 for balance in balance_changes {
427 builder.push_record(vec![format!("{}", balance)]);
428 }
429 let mut table = builder.build();
430 table.with(TablePanel::header("Balance Changes"));
431 table.with(TableStyle::rounded().horizontals([HorizontalLine::new(
432 1,
433 TableStyle::modern().get_horizontal(),
434 )]));
435 writeln!(writer, "{}", table)?;
436 }
437 Ok(())
438 }
439}
440
441fn write_obj_changes<T: Display>(
442 values: Vec<T>,
443 output_string: &str,
444 builder: &mut TableBuilder,
445) -> std::fmt::Result {
446 if !values.is_empty() {
447 builder.push_record(vec![format!("{} Objects: ", output_string)]);
448 for obj in values {
449 builder.push_record(vec![format!("{}", obj)]);
450 }
451 }
452 Ok(())
453}
454
455pub fn get_new_package_obj_from_response(
456 response: &SuiTransactionBlockResponse,
457) -> Option<(Address, Version, Digest)> {
458 response.object_changes.as_ref().and_then(|changes| {
459 changes
460 .iter()
461 .find(|change| matches!(change, ObjectChange::Published { .. }))
462 .map(|change| change.object_ref())
463 })
464}
465
466pub fn get_new_package_upgrade_cap_from_response(
467 response: &SuiTransactionBlockResponse,
468) -> Option<(Address, Version, Digest)> {
469 response.object_changes.as_ref().and_then(|changes| {
470 changes
471 .iter()
472 .find(|change| {
473 matches!(change, ObjectChange::Created {
474 owner: Owner::AddressOwner(_),
475 object_type,
476 ..
477 } if *object_type.address() == Address::TWO && object_type.module().as_str() == "package" && object_type.name().as_str() == "UpgradeCap")
478 })
479 .map(|change| change.object_ref())
480 })
481}
482
483#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
484#[serde(rename = "TransactionBlockKind", tag = "kind")]
485#[non_exhaustive]
486pub enum SuiTransactionBlockKind {
487 ChangeEpoch(SuiChangeEpoch),
489 Genesis(SuiGenesisTransaction),
491 ConsensusCommitPrologue(SuiConsensusCommitPrologue),
494 ProgrammableTransaction(SuiProgrammableTransactionBlock),
497 AuthenticatorStateUpdate(SuiAuthenticatorStateUpdate),
499 RandomnessStateUpdate(SuiRandomnessStateUpdate),
501 EndOfEpochTransaction(SuiEndOfEpochTransaction),
503 ConsensusCommitPrologueV2(SuiConsensusCommitPrologueV2),
504 ConsensusCommitPrologueV3(SuiConsensusCommitPrologueV3),
505 }
507
508impl Display for SuiTransactionBlockKind {
509 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
510 let mut writer = String::new();
511 match &self {
512 Self::ChangeEpoch(e) => {
513 writeln!(writer, "Transaction Kind: Epoch Change")?;
514 writeln!(writer, "New epoch ID: {}", e.epoch)?;
515 writeln!(writer, "Storage gas reward: {}", e.storage_charge)?;
516 writeln!(writer, "Computation gas reward: {}", e.computation_charge)?;
517 writeln!(writer, "Storage rebate: {}", e.storage_rebate)?;
518 writeln!(writer, "Timestamp: {}", e.epoch_start_timestamp_ms)?;
519 }
520 Self::Genesis(_) => {
521 writeln!(writer, "Transaction Kind: Genesis Transaction")?;
522 }
523 Self::ConsensusCommitPrologue(p) => {
524 writeln!(writer, "Transaction Kind: Consensus Commit Prologue")?;
525 writeln!(
526 writer,
527 "Epoch: {}, Round: {}, Timestamp: {}",
528 p.epoch, p.round, p.commit_timestamp_ms
529 )?;
530 }
531 Self::ConsensusCommitPrologueV2(p) => {
532 writeln!(writer, "Transaction Kind: Consensus Commit Prologue V2")?;
533 writeln!(
534 writer,
535 "Epoch: {}, Round: {}, Timestamp: {}, Digest: {}",
536 p.epoch, p.round, p.commit_timestamp_ms, p.consensus_commit_digest
537 )?;
538 }
539 Self::ConsensusCommitPrologueV3(p) => {
540 writeln!(writer, "Transaction Kind: Consensus Commit Prologue V3")?;
541 writeln!(
542 writer,
543 "Epoch: {}, Round: {}, SubDagIndex: {:?}, Timestamp: {}, Digest: {}",
544 p.epoch,
545 p.round,
546 p.sub_dag_index,
547 p.commit_timestamp_ms,
548 p.consensus_commit_digest
549 )?;
550 }
551 Self::ProgrammableTransaction(p) => {
552 write!(writer, "Transaction Kind: Programmable")?;
553 write!(writer, "{}", super::displays::Pretty(p))?;
554 }
555 Self::AuthenticatorStateUpdate(_) => {
556 writeln!(writer, "Transaction Kind: Authenticator State Update")?;
557 }
558 Self::RandomnessStateUpdate(_) => {
559 writeln!(writer, "Transaction Kind: Randomness State Update")?;
560 }
561 Self::EndOfEpochTransaction(_) => {
562 writeln!(writer, "Transaction Kind: End of Epoch Transaction")?;
563 }
564 }
565 write!(f, "{}", writer)
566 }
567}
568
569impl SuiTransactionBlockKind {
570 pub fn transaction_count(&self) -> usize {
571 match self {
572 Self::ProgrammableTransaction(p) => p.commands.len(),
573 _ => 1,
574 }
575 }
576
577 pub fn name(&self) -> &'static str {
578 match self {
579 Self::ChangeEpoch(_) => "ChangeEpoch",
580 Self::Genesis(_) => "Genesis",
581 Self::ConsensusCommitPrologue(_) => "ConsensusCommitPrologue",
582 Self::ConsensusCommitPrologueV2(_) => "ConsensusCommitPrologueV2",
583 Self::ConsensusCommitPrologueV3(_) => "ConsensusCommitPrologueV3",
584 Self::ProgrammableTransaction(_) => "ProgrammableTransaction",
585 Self::AuthenticatorStateUpdate(_) => "AuthenticatorStateUpdate",
586 Self::RandomnessStateUpdate(_) => "RandomnessStateUpdate",
587 Self::EndOfEpochTransaction(_) => "EndOfEpochTransaction",
588 }
589 }
590}
591
592#[serde_as]
593#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
594pub struct SuiChangeEpoch {
595 #[serde_as(as = "BigInt<u64>")]
596 pub epoch: EpochId,
597 #[serde_as(as = "BigInt<u64>")]
598 pub storage_charge: u64,
599 #[serde_as(as = "BigInt<u64>")]
600 pub computation_charge: u64,
601 #[serde_as(as = "BigInt<u64>")]
602 pub storage_rebate: u64,
603 #[serde_as(as = "BigInt<u64>")]
604 pub epoch_start_timestamp_ms: u64,
605}
606
607#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
608#[enum_dispatch(SuiTransactionBlockEffectsAPI)]
609#[serde(
610 rename = "TransactionBlockEffects",
611 rename_all = "camelCase",
612 tag = "messageVersion"
613)]
614pub enum SuiTransactionBlockEffects {
615 V1(SuiTransactionBlockEffectsV1),
616}
617
618#[enum_dispatch]
619pub trait SuiTransactionBlockEffectsAPI {
620 fn status(&self) -> &SuiExecutionStatus;
621 fn into_status(self) -> SuiExecutionStatus;
622 fn shared_objects(&self) -> &[SuiObjectRef];
623 fn created(&self) -> &[OwnedObjectRef];
624 fn mutated(&self) -> &[OwnedObjectRef];
625 fn unwrapped(&self) -> &[OwnedObjectRef];
626 fn deleted(&self) -> &[SuiObjectRef];
627 fn unwrapped_then_deleted(&self) -> &[SuiObjectRef];
628 fn wrapped(&self) -> &[SuiObjectRef];
629 fn gas_object(&self) -> &OwnedObjectRef;
630 fn events_digest(&self) -> Option<&Digest>;
631 fn dependencies(&self) -> &[Digest];
632 fn executed_epoch(&self) -> EpochId;
633 fn transaction_digest(&self) -> &Digest;
634 fn gas_cost_summary(&self) -> &GasCostSummary;
635
636 fn mutated_excluding_gas(&self) -> Vec<OwnedObjectRef>;
638 fn modified_at_versions(&self) -> Vec<(Address, Version)>;
639 fn all_changed_objects(&self) -> Vec<(&OwnedObjectRef, WriteKind)>;
640 fn all_deleted_objects(&self) -> Vec<(&SuiObjectRef, DeleteKind)>;
641}
642
643#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
645pub enum WriteKind {
646 Mutate,
648 Create,
650 Unwrap,
652}
653
654#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
656pub enum DeleteKind {
657 Normal,
659 UnwrapThenDelete,
662 Wrap,
664}
665
666#[serde_as]
667#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize)]
668#[serde(
669 rename = "TransactionBlockEffectsModifiedAtVersions",
670 rename_all = "camelCase"
671)]
672pub struct SuiTransactionBlockEffectsModifiedAtVersions {
673 object_id: Address,
674 #[serde_as(as = "BigInt<u64>")]
675 sequence_number: Version,
676}
677
678#[serde_as]
680#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize)]
681#[serde(rename = "TransactionBlockEffectsV1", rename_all = "camelCase")]
682pub struct SuiTransactionBlockEffectsV1 {
683 pub status: SuiExecutionStatus,
685 #[serde_as(as = "BigInt<u64>")]
687 pub executed_epoch: EpochId,
688 #[serde_as(as = "serde_with::FromInto<crate::serde::GasCostSummaryJson>")]
689 pub gas_used: GasCostSummary,
690 #[serde(default, skip_serializing_if = "Vec::is_empty")]
693 pub modified_at_versions: Vec<SuiTransactionBlockEffectsModifiedAtVersions>,
694 #[serde(default, skip_serializing_if = "Vec::is_empty")]
696 pub shared_objects: Vec<SuiObjectRef>,
697 pub transaction_digest: Digest,
699 #[serde(default, skip_serializing_if = "Vec::is_empty")]
701 pub created: Vec<OwnedObjectRef>,
702 #[serde(default, skip_serializing_if = "Vec::is_empty")]
704 pub mutated: Vec<OwnedObjectRef>,
705 #[serde(default, skip_serializing_if = "Vec::is_empty")]
709 pub unwrapped: Vec<OwnedObjectRef>,
710 #[serde(default, skip_serializing_if = "Vec::is_empty")]
712 pub deleted: Vec<SuiObjectRef>,
713 #[serde(default, skip_serializing_if = "Vec::is_empty")]
715 pub unwrapped_then_deleted: Vec<SuiObjectRef>,
716 #[serde(default, skip_serializing_if = "Vec::is_empty")]
718 pub wrapped: Vec<SuiObjectRef>,
719 pub gas_object: OwnedObjectRef,
722 #[serde(skip_serializing_if = "Option::is_none")]
725 pub events_digest: Option<Digest>,
726 #[serde(default, skip_serializing_if = "Vec::is_empty")]
728 pub dependencies: Vec<Digest>,
729}
730
731impl SuiTransactionBlockEffectsAPI for SuiTransactionBlockEffectsV1 {
732 fn status(&self) -> &SuiExecutionStatus {
733 &self.status
734 }
735 fn into_status(self) -> SuiExecutionStatus {
736 self.status
737 }
738 fn shared_objects(&self) -> &[SuiObjectRef] {
739 &self.shared_objects
740 }
741 fn created(&self) -> &[OwnedObjectRef] {
742 &self.created
743 }
744 fn mutated(&self) -> &[OwnedObjectRef] {
745 &self.mutated
746 }
747 fn unwrapped(&self) -> &[OwnedObjectRef] {
748 &self.unwrapped
749 }
750 fn deleted(&self) -> &[SuiObjectRef] {
751 &self.deleted
752 }
753 fn unwrapped_then_deleted(&self) -> &[SuiObjectRef] {
754 &self.unwrapped_then_deleted
755 }
756 fn wrapped(&self) -> &[SuiObjectRef] {
757 &self.wrapped
758 }
759 fn gas_object(&self) -> &OwnedObjectRef {
760 &self.gas_object
761 }
762 fn events_digest(&self) -> Option<&Digest> {
763 self.events_digest.as_ref()
764 }
765 fn dependencies(&self) -> &[Digest] {
766 &self.dependencies
767 }
768
769 fn executed_epoch(&self) -> EpochId {
770 self.executed_epoch
771 }
772
773 fn transaction_digest(&self) -> &Digest {
774 &self.transaction_digest
775 }
776
777 fn gas_cost_summary(&self) -> &GasCostSummary {
778 &self.gas_used
779 }
780
781 fn mutated_excluding_gas(&self) -> Vec<OwnedObjectRef> {
782 self.mutated
783 .iter()
784 .filter(|o| *o != &self.gas_object)
785 .cloned()
786 .collect()
787 }
788
789 fn modified_at_versions(&self) -> Vec<(Address, Version)> {
790 self.modified_at_versions
791 .iter()
792 .map(|v| (v.object_id, v.sequence_number))
793 .collect::<Vec<_>>()
794 }
795
796 fn all_changed_objects(&self) -> Vec<(&OwnedObjectRef, WriteKind)> {
797 self.mutated
798 .iter()
799 .map(|owner_ref| (owner_ref, WriteKind::Mutate))
800 .chain(
801 self.created
802 .iter()
803 .map(|owner_ref| (owner_ref, WriteKind::Create)),
804 )
805 .chain(
806 self.unwrapped
807 .iter()
808 .map(|owner_ref| (owner_ref, WriteKind::Unwrap)),
809 )
810 .collect()
811 }
812
813 fn all_deleted_objects(&self) -> Vec<(&SuiObjectRef, DeleteKind)> {
814 self.deleted
815 .iter()
816 .map(|r| (r, DeleteKind::Normal))
817 .chain(
818 self.unwrapped_then_deleted
819 .iter()
820 .map(|r| (r, DeleteKind::UnwrapThenDelete)),
821 )
822 .chain(self.wrapped.iter().map(|r| (r, DeleteKind::Wrap)))
823 .collect()
824 }
825}
826
827fn owned_objref_string(obj: &OwnedObjectRef) -> String {
828 format!(
829 " ┌──\n │ ID: {} \n │ Owner: {:?} \n │ Version: {} \n │ Digest: {}\n └──",
830 obj.reference.object_id, obj.owner, obj.reference.version, obj.reference.digest
831 )
832}
833
834fn objref_string(obj: &SuiObjectRef) -> String {
835 format!(
836 " ┌──\n │ ID: {} \n │ Version: {} \n │ Digest: {}\n └──",
837 obj.object_id, obj.version, obj.digest
838 )
839}
840
841impl Display for SuiTransactionBlockEffects {
842 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
843 let mut builder = TableBuilder::default();
844
845 builder.push_record(vec![format!("Digest: {}", self.transaction_digest())]);
846 builder.push_record(vec![format!("Status: {:?}", self.status())]);
847 builder.push_record(vec![format!("Executed Epoch: {}", self.executed_epoch())]);
848
849 if !self.created().is_empty() {
850 builder.push_record(vec![format!("\nCreated Objects: ")]);
851
852 for oref in self.created() {
853 builder.push_record(vec![owned_objref_string(oref)]);
854 }
855 }
856
857 if !self.mutated().is_empty() {
858 builder.push_record(vec![format!("Mutated Objects: ")]);
859 for oref in self.mutated() {
860 builder.push_record(vec![owned_objref_string(oref)]);
861 }
862 }
863
864 if !self.shared_objects().is_empty() {
865 builder.push_record(vec![format!("Shared Objects: ")]);
866 for oref in self.shared_objects() {
867 builder.push_record(vec![objref_string(oref)]);
868 }
869 }
870
871 if !self.deleted().is_empty() {
872 builder.push_record(vec![format!("Deleted Objects: ")]);
873
874 for oref in self.deleted() {
875 builder.push_record(vec![objref_string(oref)]);
876 }
877 }
878
879 if !self.wrapped().is_empty() {
880 builder.push_record(vec![format!("Wrapped Objects: ")]);
881
882 for oref in self.wrapped() {
883 builder.push_record(vec![objref_string(oref)]);
884 }
885 }
886
887 if !self.unwrapped().is_empty() {
888 builder.push_record(vec![format!("Unwrapped Objects: ")]);
889 for oref in self.unwrapped() {
890 builder.push_record(vec![owned_objref_string(oref)]);
891 }
892 }
893
894 builder.push_record(vec![format!(
895 "Gas Object: \n{}",
896 owned_objref_string(self.gas_object())
897 )]);
898
899 let gas_cost_summary = self.gas_cost_summary();
900 builder.push_record(vec![format!(
901 "Gas Cost Summary:\n \
902 Storage Cost: {} MIST\n \
903 Computation Cost: {} MIST\n \
904 Storage Rebate: {} MIST\n \
905 Non-refundable Storage Fee: {} MIST",
906 gas_cost_summary.storage_cost,
907 gas_cost_summary.computation_cost,
908 gas_cost_summary.storage_rebate,
909 gas_cost_summary.non_refundable_storage_fee,
910 )]);
911
912 let dependencies = self.dependencies();
913 if !dependencies.is_empty() {
914 builder.push_record(vec![format!("\nTransaction Dependencies:")]);
915 for dependency in dependencies {
916 builder.push_record(vec![format!(" {}", dependency)]);
917 }
918 }
919
920 let mut table = builder.build();
921 table.with(TablePanel::header("Transaction Effects"));
922 table.with(TableStyle::rounded().horizontals([HorizontalLine::new(
923 1,
924 TableStyle::modern().get_horizontal(),
925 )]));
926 write!(f, "{}", table)
927 }
928}
929
930#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize)]
931#[serde(rename_all = "camelCase")]
932#[non_exhaustive]
933pub struct DryRunTransactionBlockResponse {
934 pub effects: SuiTransactionBlockEffects,
935 pub events: SuiTransactionBlockEvents,
936 pub object_changes: Vec<ObjectChange>,
937 pub balance_changes: Vec<BalanceChange>,
938 pub input: SuiTransactionBlockData,
939 #[serde(default)]
940 pub execution_error_source: Option<String>,
941}
942
943#[derive(Eq, PartialEq, Clone, Debug, Default, Serialize, Deserialize)]
944#[serde(rename = "TransactionBlockEvents", transparent)]
945pub struct SuiTransactionBlockEvents {
946 pub data: Vec<SuiEvent>,
947}
948
949impl Display for SuiTransactionBlockEvents {
950 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
951 if self.data.is_empty() {
952 writeln!(f, "â•─────────────────────────────╮")?;
953 writeln!(f, "│ No transaction block events │")?;
954 writeln!(f, "╰─────────────────────────────╯")
955 } else {
956 let mut builder = TableBuilder::default();
957
958 for event in &self.data {
959 builder.push_record(vec![format!("{}", event)]);
960 }
961
962 let mut table = builder.build();
963 table.with(TablePanel::header("Transaction Block Events"));
964 table.with(TableStyle::rounded().horizontals([HorizontalLine::new(
965 1,
966 TableStyle::modern().get_horizontal(),
967 )]));
968 write!(f, "{}", table)
969 }
970 }
971}
972
973#[derive(Debug, Default, Clone, Serialize, Deserialize)]
975#[serde(rename = "DevInspectArgs", rename_all = "camelCase")]
976pub struct DevInspectArgs {
977 pub gas_sponsor: Option<Address>,
979 pub gas_budget: Option<BigInt<u64>>,
981 pub gas_objects: Option<Vec<(Address, Version, Digest)>>,
983 pub skip_checks: Option<bool>,
985 pub show_raw_txn_data_and_effects: Option<bool>,
987}
988
989#[derive(Debug, Clone, Serialize, Deserialize)]
991#[serde(rename = "DevInspectResults", rename_all = "camelCase")]
992pub struct DevInspectResults {
993 pub effects: SuiTransactionBlockEffects,
997 pub events: SuiTransactionBlockEvents,
999 #[serde(skip_serializing_if = "Option::is_none")]
1001 pub results: Option<Vec<SuiExecutionResult>>,
1002 #[serde(skip_serializing_if = "Option::is_none")]
1004 pub error: Option<String>,
1005 #[serde(skip_serializing_if = "Vec::is_empty", default)]
1007 pub raw_txn_data: Vec<u8>,
1008 #[serde(skip_serializing_if = "Vec::is_empty", default)]
1010 pub raw_effects: Vec<u8>,
1011}
1012
1013#[derive(Debug, Clone, Serialize, Deserialize)]
1014#[serde(rename = "SuiExecutionResult", rename_all = "camelCase")]
1015pub struct SuiExecutionResult {
1016 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1019 pub mutable_reference_outputs: Vec<(SuiArgument, Vec<u8>, SuiTypeTag)>,
1020 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1022 pub return_values: Vec<(Vec<u8>, SuiTypeTag)>,
1023}
1024
1025#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize)]
1026pub enum SuiTransactionBlockBuilderMode {
1027 Commit,
1029 DevInspect,
1032}
1033
1034#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize)]
1035#[serde(rename = "ExecutionStatus", rename_all = "camelCase", tag = "status")]
1036pub enum SuiExecutionStatus {
1037 Success,
1039 Failure { error: String },
1041}
1042
1043impl Display for SuiExecutionStatus {
1044 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1045 match self {
1046 Self::Success => write!(f, "success"),
1047 Self::Failure { error } => write!(f, "failure due to {error}"),
1048 }
1049 }
1050}
1051
1052impl SuiExecutionStatus {
1053 const MOVE_ABORT_PATTERN: &str = r#"MoveAbort\(MoveLocation \{ module: ModuleId \{ address: ([[:alnum:]]+), name: Identifier\("([[:word:]]+)"\) \}, function: (\d+), instruction: (\d+), function_name: Some\("([[:word:]]+)"\) \}, (\d+)\)"#;
1054
1055 pub fn is_ok(&self) -> bool {
1056 matches!(self, SuiExecutionStatus::Success)
1057 }
1058
1059 pub fn is_err(&self) -> bool {
1060 matches!(self, SuiExecutionStatus::Failure { .. })
1061 }
1062
1063 pub fn as_move_abort(&self) -> Option<(MoveLocation, u64)> {
1067 let Self::Failure { error } = self else {
1068 return None;
1069 };
1070 let re = regex::Regex::new(Self::MOVE_ABORT_PATTERN).expect("Tested below");
1071
1072 let matches = re.captures(error)?;
1073
1074 let address = "0x".to_owned() + matches.get(1)?.as_str();
1076 Some((
1077 MoveLocation {
1078 package: address.parse().ok()?,
1079 module: matches.get(2)?.as_str().parse().ok()?,
1080 function: matches.get(3)?.as_str().parse().ok()?,
1081 instruction: matches.get(4)?.as_str().parse().ok()?,
1082 function_name: Some(matches.get(5)?.as_str().parse().ok()?),
1083 },
1084 matches.get(6)?.as_str().parse().ok()?,
1085 ))
1086 }
1087}
1088
1089#[serde_as]
1090#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
1091#[serde(rename = "GasData", rename_all = "camelCase")]
1092pub struct SuiGasData {
1093 pub payment: Vec<SuiObjectRef>,
1094 pub owner: Address,
1095 #[serde_as(as = "BigInt<u64>")]
1096 pub price: u64,
1097 #[serde_as(as = "BigInt<u64>")]
1098 pub budget: u64,
1099}
1100
1101impl Display for SuiGasData {
1102 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1103 writeln!(f, "Gas Owner: {}", self.owner)?;
1104 writeln!(f, "Gas Budget: {} MIST", self.budget)?;
1105 writeln!(f, "Gas Price: {} MIST", self.price)?;
1106 writeln!(f, "Gas Payment:")?;
1107 for payment in &self.payment {
1108 write!(f, "{} ", objref_string(payment))?;
1109 }
1110 writeln!(f)
1111 }
1112}
1113
1114#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
1115#[enum_dispatch(SuiTransactionBlockDataAPI)]
1116#[serde(
1117 rename = "TransactionBlockData",
1118 rename_all = "camelCase",
1119 tag = "messageVersion"
1120)]
1121pub enum SuiTransactionBlockData {
1122 V1(SuiTransactionBlockDataV1),
1123}
1124
1125#[enum_dispatch]
1126pub trait SuiTransactionBlockDataAPI {
1127 fn transaction(&self) -> &SuiTransactionBlockKind;
1128 fn sender(&self) -> &Address;
1129 fn gas_data(&self) -> &SuiGasData;
1130}
1131
1132#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
1133#[serde(rename = "TransactionBlockDataV1", rename_all = "camelCase")]
1134pub struct SuiTransactionBlockDataV1 {
1135 pub transaction: SuiTransactionBlockKind,
1136 pub sender: Address,
1137 pub gas_data: SuiGasData,
1138}
1139
1140impl SuiTransactionBlockDataAPI for SuiTransactionBlockDataV1 {
1141 fn transaction(&self) -> &SuiTransactionBlockKind {
1142 &self.transaction
1143 }
1144 fn sender(&self) -> &Address {
1145 &self.sender
1146 }
1147 fn gas_data(&self) -> &SuiGasData {
1148 &self.gas_data
1149 }
1150}
1151
1152impl SuiTransactionBlockData {
1153 pub fn move_calls(&self) -> Vec<&SuiProgrammableMoveCall> {
1154 match self {
1155 Self::V1(data) => match &data.transaction {
1156 SuiTransactionBlockKind::ProgrammableTransaction(pt) => pt
1157 .commands
1158 .iter()
1159 .filter_map(|command| match command {
1160 SuiCommand::MoveCall(c) => Some(&**c),
1161 _ => None,
1162 })
1163 .collect(),
1164 _ => vec![],
1165 },
1166 }
1167 }
1168}
1169
1170impl Display for SuiTransactionBlockData {
1171 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1172 match self {
1173 Self::V1(data) => {
1174 writeln!(f, "Sender: {}", data.sender)?;
1175 writeln!(f, "{}", self.gas_data())?;
1176 writeln!(f, "{}", data.transaction)
1177 }
1178 }
1179 }
1180}
1181
1182#[serde_as]
1183#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
1184#[serde(rename = "TransactionBlock", rename_all = "camelCase")]
1185pub struct SuiTransactionBlock {
1186 pub data: SuiTransactionBlockData,
1187 #[serde_as(as = "Vec<IfIsHumanReadable<Base64UserSignature>>")]
1188 pub tx_signatures: Vec<UserSignature>,
1189}
1190
1191struct Base64UserSignature;
1192
1193impl<'de> DeserializeAs<'de, UserSignature> for Base64UserSignature {
1194 fn deserialize_as<D>(deserializer: D) -> Result<UserSignature, D::Error>
1195 where
1196 D: serde::Deserializer<'de>,
1197 {
1198 let base64 = Box::<str>::deserialize(deserializer)?;
1199 UserSignature::from_base64(&base64).map_err(serde::de::Error::custom)
1200 }
1201}
1202
1203impl SerializeAs<UserSignature> for Base64UserSignature {
1204 fn serialize_as<S>(source: &UserSignature, serializer: S) -> Result<S::Ok, S::Error>
1205 where
1206 S: serde::Serializer,
1207 {
1208 let base64 = source.to_base64();
1209 base64.serialize(serializer)
1210 }
1211}
1212
1213impl Display for SuiTransactionBlock {
1214 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1215 let mut builder = TableBuilder::default();
1216
1217 builder.push_record(vec![format!("{}", self.data)]);
1218 builder.push_record(vec![format!("Signatures:")]);
1219 for tx_sig in &self.tx_signatures {
1220 builder.push_record(vec![format!(" {}\n", tx_sig.to_base64())]);
1221 }
1222
1223 let mut table = builder.build();
1224 table.with(TablePanel::header("Transaction Data"));
1225 table.with(TableStyle::rounded().horizontals([HorizontalLine::new(
1226 1,
1227 TableStyle::modern().get_horizontal(),
1228 )]));
1229 write!(f, "{}", table)
1230 }
1231}
1232
1233#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1234pub struct SuiGenesisTransaction {
1235 pub objects: Vec<Address>,
1236}
1237
1238#[serde_as]
1239#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1240pub struct SuiConsensusCommitPrologue {
1241 #[serde_as(as = "BigInt<u64>")]
1242 pub epoch: u64,
1243 #[serde_as(as = "BigInt<u64>")]
1244 pub round: u64,
1245 #[serde_as(as = "BigInt<u64>")]
1246 pub commit_timestamp_ms: u64,
1247}
1248
1249#[serde_as]
1250#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1251pub struct SuiConsensusCommitPrologueV2 {
1252 #[serde_as(as = "BigInt<u64>")]
1253 pub epoch: u64,
1254 #[serde_as(as = "BigInt<u64>")]
1255 pub round: u64,
1256 #[serde_as(as = "BigInt<u64>")]
1257 pub commit_timestamp_ms: u64,
1258 pub consensus_commit_digest: Digest,
1259}
1260
1261#[serde_as]
1262#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1263pub struct SuiConsensusCommitPrologueV3 {
1264 #[serde_as(as = "BigInt<u64>")]
1265 pub epoch: u64,
1266 #[serde_as(as = "BigInt<u64>")]
1267 pub round: u64,
1268 #[serde_as(as = "Option<BigInt<u64>>")]
1269 pub sub_dag_index: Option<u64>,
1270 #[serde_as(as = "BigInt<u64>")]
1271 pub commit_timestamp_ms: u64,
1272 pub consensus_commit_digest: Digest,
1273 pub consensus_determined_version_assignments: ConsensusDeterminedVersionAssignments,
1274}
1275
1276#[serde_as]
1277#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1278pub struct SuiAuthenticatorStateUpdate {
1279 #[serde_as(as = "BigInt<u64>")]
1280 pub epoch: u64,
1281 #[serde_as(as = "BigInt<u64>")]
1282 pub round: u64,
1283
1284 pub new_active_jwks: Vec<SuiActiveJwk>,
1285}
1286
1287#[serde_as]
1288#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1289pub struct SuiRandomnessStateUpdate {
1290 #[serde_as(as = "BigInt<u64>")]
1291 pub epoch: u64,
1292
1293 #[serde_as(as = "BigInt<u64>")]
1294 pub randomness_round: u64,
1295 pub random_bytes: Vec<u8>,
1296}
1297
1298#[serde_as]
1299#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1300pub struct SuiEndOfEpochTransaction {
1301 pub transactions: Vec<SuiEndOfEpochTransactionKind>,
1302}
1303
1304#[serde_as]
1305#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1306#[non_exhaustive]
1307pub enum SuiEndOfEpochTransactionKind {
1308 ChangeEpoch(SuiChangeEpoch),
1309 AuthenticatorStateCreate,
1310 AuthenticatorStateExpire(SuiAuthenticatorStateExpire),
1311 RandomnessStateCreate,
1312 CoinDenyListStateCreate,
1313 BridgeStateCreate(Digest),
1314 BridgeCommitteeUpdate(Version),
1315}
1316
1317#[serde_as]
1318#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1319pub struct SuiAuthenticatorStateExpire {
1320 #[serde_as(as = "BigInt<u64>")]
1321 pub min_epoch: u64,
1322}
1323
1324#[serde_as]
1325#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1326pub struct SuiActiveJwk {
1327 pub jwk_id: SuiJwkId,
1328 pub jwk: SuiJWK,
1329
1330 #[serde_as(as = "BigInt<u64>")]
1331 pub epoch: u64,
1332}
1333
1334#[serde_as]
1335#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1336pub struct SuiJwkId {
1337 pub iss: String,
1338 pub kid: String,
1339}
1340
1341#[serde_as]
1342#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1343pub struct SuiJWK {
1344 pub kty: String,
1345 pub e: String,
1346 pub n: String,
1347 pub alg: String,
1348}
1349
1350#[serde_as]
1351#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
1352#[serde(rename = "InputObjectKind")]
1353pub enum SuiInputObjectKind {
1354 MovePackage(Address),
1356 ImmOrOwnedMoveObject(SuiObjectRef),
1358 SharedMoveObject {
1360 id: Address,
1361 #[serde_as(as = "BigInt<u64>")]
1362 initial_shared_version: Version,
1363 #[serde(default)]
1364 mutable: bool,
1365 },
1366}
1367
1368#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1371pub struct SuiProgrammableTransactionBlock {
1372 pub inputs: Vec<SuiCallArg>,
1374 #[serde(rename = "transactions")]
1375 pub commands: Vec<SuiCommand>,
1378}
1379
1380impl Display for SuiProgrammableTransactionBlock {
1381 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1382 let Self { inputs, commands } = self;
1383 writeln!(f, "Inputs: {inputs:?}")?;
1384 writeln!(f, "Commands: [")?;
1385 for c in commands {
1386 writeln!(f, " {c},")?;
1387 }
1388 writeln!(f, "]")
1389 }
1390}
1391
1392#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1394#[serde(rename = "SuiTransaction")]
1395pub enum SuiCommand {
1396 MoveCall(Box<SuiProgrammableMoveCall>),
1398 TransferObjects(Vec<SuiArgument>, SuiArgument),
1403 SplitCoins(SuiArgument, Vec<SuiArgument>),
1406 MergeCoins(SuiArgument, Vec<SuiArgument>),
1409 Publish(Vec<Address>),
1412 Upgrade(Vec<Address>, Address, SuiArgument),
1414 MakeMoveVec(Option<String>, Vec<SuiArgument>),
1418}
1419
1420impl Display for SuiCommand {
1421 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1422 match self {
1423 Self::MoveCall(p) => {
1424 write!(f, "MoveCall({p})")
1425 }
1426 Self::MakeMoveVec(ty_opt, elems) => {
1427 write!(f, "MakeMoveVec(")?;
1428 if let Some(ty) = ty_opt {
1429 write!(f, "Some{ty}")?;
1430 } else {
1431 write!(f, "None")?;
1432 }
1433 write!(f, ",[")?;
1434 write_sep(f, elems, ",")?;
1435 write!(f, "])")
1436 }
1437 Self::TransferObjects(objs, addr) => {
1438 write!(f, "TransferObjects([")?;
1439 write_sep(f, objs, ",")?;
1440 write!(f, "],{addr})")
1441 }
1442 Self::SplitCoins(coin, amounts) => {
1443 write!(f, "SplitCoins({coin},")?;
1444 write_sep(f, amounts, ",")?;
1445 write!(f, ")")
1446 }
1447 Self::MergeCoins(target, coins) => {
1448 write!(f, "MergeCoins({target},")?;
1449 write_sep(f, coins, ",")?;
1450 write!(f, ")")
1451 }
1452 Self::Publish(deps) => {
1453 write!(f, "Publish(<modules>,")?;
1454 write_sep(f, deps, ",")?;
1455 write!(f, ")")
1456 }
1457 Self::Upgrade(deps, current_package_id, ticket) => {
1458 write!(f, "Upgrade(<modules>, {ticket},")?;
1459 write_sep(f, deps, ",")?;
1460 write!(f, ", {current_package_id}")?;
1461 write!(f, ")")
1462 }
1463 }
1464 }
1465}
1466
1467#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)]
1469pub enum SuiArgument {
1470 GasCoin,
1473 Input(u16),
1476 Result(u16),
1478 NestedResult(u16, u16),
1481}
1482
1483impl Display for SuiArgument {
1484 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1485 match self {
1486 Self::GasCoin => write!(f, "GasCoin"),
1487 Self::Input(i) => write!(f, "Input({i})"),
1488 Self::Result(i) => write!(f, "Result({i})"),
1489 Self::NestedResult(i, j) => write!(f, "NestedResult({i},{j})"),
1490 }
1491 }
1492}
1493
1494#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1497pub struct SuiProgrammableMoveCall {
1498 pub package: Address,
1500 pub module: String,
1502 pub function: String,
1504 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1505 pub type_arguments: Vec<String>,
1507 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1508 pub arguments: Vec<SuiArgument>,
1510}
1511
1512fn write_sep<T: Display>(
1513 f: &mut Formatter<'_>,
1514 items: impl IntoIterator<Item = T>,
1515 sep: &str,
1516) -> std::fmt::Result {
1517 let mut xs = items.into_iter().peekable();
1518 while let Some(x) = xs.next() {
1519 write!(f, "{x}")?;
1520 if xs.peek().is_some() {
1521 write!(f, "{sep}")?;
1522 }
1523 }
1524 Ok(())
1525}
1526
1527impl Display for SuiProgrammableMoveCall {
1528 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1529 let Self {
1530 package,
1531 module,
1532 function,
1533 type_arguments,
1534 arguments,
1535 } = self;
1536 write!(f, "{package}::{module}::{function}")?;
1537 if !type_arguments.is_empty() {
1538 write!(f, "<")?;
1539 write_sep(f, type_arguments, ",")?;
1540 write!(f, ">")?;
1541 }
1542 write!(f, "(")?;
1543 write_sep(f, arguments, ",")?;
1544 write!(f, ")")
1545 }
1546}
1547
1548#[derive(Debug, Serialize, Deserialize, Clone)]
1549#[serde(rename = "TypeTag", rename_all = "camelCase")]
1550pub struct SuiTypeTag(String);
1551
1552impl SuiTypeTag {
1553 pub fn new(tag: String) -> Self {
1554 Self(tag)
1555 }
1556}
1557
1558impl TryInto<TypeTag> for SuiTypeTag {
1559 type Error = <TypeTag as FromStr>::Err;
1560 fn try_into(self) -> Result<TypeTag, Self::Error> {
1561 self.0.parse()
1562 }
1563}
1564
1565impl From<TypeTag> for SuiTypeTag {
1566 fn from(tag: TypeTag) -> Self {
1567 Self(format!("{}", tag))
1568 }
1569}
1570
1571#[derive(Serialize, Deserialize)]
1572#[serde(rename_all = "camelCase")]
1573pub enum RPCTransactionRequestParams {
1574 TransferObjectRequestParams(TransferObjectParams),
1575 MoveCallRequestParams(MoveCallParams),
1576}
1577
1578#[derive(Serialize, Deserialize)]
1579#[serde(rename_all = "camelCase")]
1580pub struct TransferObjectParams {
1581 pub recipient: Address,
1582 pub object_id: Address,
1583}
1584
1585#[derive(Serialize, Deserialize)]
1586#[serde(rename_all = "camelCase")]
1587pub struct MoveCallParams {
1588 pub package_object_id: Address,
1589 pub module: String,
1590 pub function: String,
1591 #[serde(default)]
1592 pub type_arguments: Vec<SuiTypeTag>,
1593 pub arguments: Vec<serde_json::Value>,
1594}
1595
1596#[serde_as]
1597#[derive(Serialize, Deserialize)]
1598#[serde(rename_all = "camelCase")]
1599pub struct TransactionBlockBytes {
1600 pub tx_bytes: String,
1602 pub gas: Vec<SuiObjectRef>,
1604 pub input_objects: Vec<SuiInputObjectKind>,
1606}
1607
1608#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize)]
1609#[serde(rename = "OwnedObjectRef")]
1610pub struct OwnedObjectRef {
1611 pub owner: Owner,
1612 pub reference: SuiObjectRef,
1613}
1614
1615impl OwnedObjectRef {
1616 pub fn object_id(&self) -> Address {
1617 self.reference.object_id
1618 }
1619 pub fn version(&self) -> Version {
1620 self.reference.version
1621 }
1622}
1623
1624#[derive(Eq, PartialEq, Debug, Clone, Serialize, Deserialize)]
1625#[serde(tag = "type", rename_all = "camelCase")]
1626pub enum SuiCallArg {
1627 Object(SuiObjectArg),
1629 Pure(SuiPureValue),
1631}
1632
1633impl SuiCallArg {
1634 pub fn pure(&self) -> Option<&serde_json::Value> {
1635 match self {
1636 SuiCallArg::Pure(v) => Some(&v.value),
1637 _ => None,
1638 }
1639 }
1640
1641 pub fn object(&self) -> Option<&Address> {
1642 match self {
1643 SuiCallArg::Object(SuiObjectArg::SharedObject { object_id, .. })
1644 | SuiCallArg::Object(SuiObjectArg::ImmOrOwnedObject { object_id, .. })
1645 | SuiCallArg::Object(SuiObjectArg::Receiving { object_id, .. }) => Some(object_id),
1646 _ => None,
1647 }
1648 }
1649}
1650
1651#[serde_as]
1652#[derive(Eq, PartialEq, Debug, Clone, Serialize, Deserialize)]
1653#[serde(rename_all = "camelCase")]
1654pub struct SuiPureValue {
1655 #[serde_as(as = "Option<DisplayFromStr>")]
1657 value_type: Option<TypeTag>,
1658 value: serde_json::Value,
1659}
1660
1661impl SuiPureValue {
1662 pub fn value(&self) -> serde_json::Value {
1663 self.value.clone()
1664 }
1665
1666 pub fn value_type(&self) -> Option<TypeTag> {
1667 self.value_type.clone()
1668 }
1669}
1670
1671#[serde_as]
1672#[derive(Eq, PartialEq, Debug, Clone, Serialize, Deserialize)]
1673#[serde(tag = "objectType", rename_all = "camelCase")]
1674pub enum SuiObjectArg {
1675 #[serde(rename_all = "camelCase")]
1677 ImmOrOwnedObject {
1678 object_id: Address,
1679 #[serde_as(as = "BigInt<u64>")]
1680 version: Version,
1681 digest: Digest,
1682 },
1683 #[serde(rename_all = "camelCase")]
1686 SharedObject {
1687 object_id: Address,
1688 #[serde_as(as = "BigInt<u64>")]
1689 initial_shared_version: Version,
1690 mutable: bool,
1691 },
1692 #[serde(rename_all = "camelCase")]
1694 Receiving {
1695 object_id: Address,
1696 #[serde_as(as = "BigInt<u64>")]
1697 version: Version,
1698 digest: Digest,
1699 },
1700}
1701
1702#[serde_as]
1703#[derive(Clone, Debug, Serialize, Deserialize)]
1704#[non_exhaustive]
1705pub enum TransactionFilter {
1706 Checkpoint(#[serde_as(as = "IfIsHumanReadable<BigInt<u64>, _>")] Version),
1708 MoveFunction {
1710 package: Address,
1711 module: Option<String>,
1712 function: Option<String>,
1713 },
1714 InputObject(Address),
1716 ChangedObject(Address),
1718 AffectedObject(Address),
1720 FromAddress(Address),
1722 ToAddress(Address),
1724 FromAndToAddress { from: Address, to: Address },
1726 FromOrToAddress { addr: Address },
1728 TransactionKind(String),
1730 TransactionKindIn(Vec<String>),
1732}
1733
1734#[cfg(test)]
1735mod tests {
1736 use color_eyre::Result;
1737 use itertools::Itertools as _;
1738 use sui_sdk_types::Identifier;
1739
1740 use super::*;
1741
1742 const MOVE_ABORT_ERRORS: [&str; 3] = [
1743 r#"MoveAbort(MoveLocation { module: ModuleId { address: fd6f306bb2f8dce24dd3d4a9bdc51a46e7c932b15007d73ac0cfb38c15de0fea, name: Identifier("market") }, function: 1, instruction: 60, function_name: Some("try_update_funding") }, 1001)"#,
1744 r#"MoveAbort(MoveLocation { module: ModuleId { address: 241537381737a40df6838bc395fb64f04ff604513c18a2ac3308ac810c805fa6, name: Identifier("oracle") }, function: 23, instruction: 42, function_name: Some("update_price_feed_inner") }, 4)"#,
1745 r#"MoveAbort(MoveLocation { module: ModuleId { address: 72a8715095cdc8442b4316f78802d7aefa2e6f0c3c6fac256ce81554034b0d4b, name: Identifier("clearing_house") }, function: 53, instruction: 32, function_name: Some("settled_liquidated_position") }, 2001) in command 3"#,
1746 ];
1747
1748 #[test]
1749 fn move_abort_regex_is_valid() -> Result<()> {
1750 regex::Regex::new(SuiExecutionStatus::MOVE_ABORT_PATTERN)?;
1751 Ok(())
1752 }
1753
1754 #[test]
1755 fn move_abort_extracts() -> Result<()> {
1756 let expected = [
1757 (
1758 MoveLocation {
1759 package: "0xfd6f306bb2f8dce24dd3d4a9bdc51a46e7c932b15007d73ac0cfb38c15de0fea"
1760 .parse()?,
1761 module: Identifier::from_static("market"),
1762 function: 1,
1763 instruction: 60,
1764 function_name: Some("try_update_funding".parse()?),
1765 },
1766 1001,
1767 ),
1768 (
1769 MoveLocation {
1770 package: "0x241537381737a40df6838bc395fb64f04ff604513c18a2ac3308ac810c805fa6"
1771 .parse()?,
1772 module: Identifier::from_static("oracle"),
1773 function: 23,
1774 instruction: 42,
1775 function_name: Some("update_price_feed_inner".parse()?),
1776 },
1777 4,
1778 ),
1779 (
1780 MoveLocation {
1781 package: "0x72a8715095cdc8442b4316f78802d7aefa2e6f0c3c6fac256ce81554034b0d4b"
1782 .parse()?,
1783 module: Identifier::from_static("clearing_house"),
1784 function: 53,
1785 instruction: 32,
1786 function_name: Some("settled_liquidated_position".parse()?),
1787 },
1788 2001,
1789 ),
1790 ];
1791
1792 let errors = MOVE_ABORT_ERRORS
1793 .into_iter()
1794 .map(|msg| SuiExecutionStatus::Failure { error: msg.into() });
1795
1796 for (error, expect) in errors.into_iter().zip_eq(expected) {
1797 assert_eq!(error.as_move_abort(), Some(expect));
1798 }
1799
1800 Ok(())
1801 }
1802
1803 #[test]
1804 fn transaction_block_transaction_deser() {
1805 let value = serde_json::json!({
1806 "data": {
1807 "messageVersion": "v1",
1808 "transaction": {
1809 "kind": "ProgrammableTransaction",
1810 "inputs": [
1811 {
1812 "type": "pure",
1813 "valueType": "u64",
1814 "value": "5"
1815 },
1816 {
1817 "type": "pure",
1818 "valueType": "u64",
1819 "value": "13"
1820 },
1821 {
1822 "type": "pure",
1823 "valueType": "u64",
1824 "value": "20"
1825 },
1826 {
1827 "type": "object",
1828 "objectType": "immOrOwnedObject",
1829 "objectId": "0xa3926c709e6ed570217aec468c9b81c35c4e7178a18f610f4427f4a075f63680",
1830 "version": "3",
1831 "digest": "7YeqF7kSbFrdzE2zyG4BdTctiHQrjapUkzqT3kqGAg6D"
1832 },
1833 {
1834 "type": "pure",
1835 "valueType": "u256",
1836 "value": "100000000000000000"
1837 },
1838 {
1839 "type": "pure",
1840 "valueType": "u256",
1841 "value": "50000000000000000"
1842 },
1843 {
1844 "type": "pure",
1845 "valueType": "u64",
1846 "value": "3600000"
1847 },
1848 {
1849 "type": "pure",
1850 "valueType": "u64",
1851 "value": "86400000"
1852 },
1853 {
1854 "type": "pure",
1855 "valueType": "u64",
1856 "value": "5000"
1857 },
1858 {
1859 "type": "pure",
1860 "valueType": "u256",
1861 "value": "1000000000000000"
1862 },
1863 {
1864 "type": "pure",
1865 "valueType": "u256",
1866 "value": "100000000000000"
1867 },
1868 {
1869 "type": "pure",
1870 "valueType": "u64",
1871 "value": "1000000"
1872 },
1873 {
1874 "type": "pure",
1875 "valueType": "u64",
1876 "value": "1000"
1877 },
1878 {
1879 "type": "object",
1880 "objectType": "immOrOwnedObject",
1881 "objectId": "0xf8cea4da5f0b9cf6cea67217fb99de1ac7dcc905863c5d2b1f8d4ab4530b726a",
1882 "version": "3",
1883 "digest": "6cACC7x9FFY1SdnzT6aH1z9UPMmvm4NGUz3skXGBn1Yd"
1884 },
1885 {
1886 "type": "object",
1887 "objectType": "sharedObject",
1888 "objectId": "0x981bfebd35bf22ab220853d6756568fd0556d127b856d7d08a12822da0cf1a4b",
1889 "initialSharedVersion": "3",
1890 "mutable": true
1891 },
1892 {
1893 "type": "object",
1894 "objectType": "sharedObject",
1895 "objectId": "0xd9b347da14a2600c9392d6718096d712e2b5497ce87c871854bfa92023099530",
1896 "initialSharedVersion": "509",
1897 "mutable": false
1898 },
1899 {
1900 "type": "object",
1901 "objectType": "sharedObject",
1902 "objectId": "0x7bbfd7177ef8042d2752deb2bfd5016114ad91e8c0abae6f78f35b4fd4db95d1",
1903 "initialSharedVersion": "510",
1904 "mutable": false
1905 },
1906 {
1907 "type": "object",
1908 "objectType": "sharedObject",
1909 "objectId": "0x0000000000000000000000000000000000000000000000000000000000000006",
1910 "initialSharedVersion": "1",
1911 "mutable": false
1912 }
1913 ],
1914 "transactions": [
1915 {
1916 "MoveCall": {
1917 "package": "0xb9f430cddd9cbe60f0beea60fce6da590688abbf2948dccff7cdda5a874be9ad",
1918 "module": "interface",
1919 "function": "create_orderbook",
1920 "arguments": [
1921 {
1922 "Input": 13
1923 },
1924 {
1925 "Input": 0
1926 },
1927 {
1928 "Input": 1
1929 },
1930 {
1931 "Input": 2
1932 },
1933 {
1934 "Input": 0
1935 },
1936 {
1937 "Input": 1
1938 },
1939 {
1940 "Input": 2
1941 }
1942 ]
1943 }
1944 },
1945 {
1946 "MoveCall": {
1947 "package": "0xb9f430cddd9cbe60f0beea60fce6da590688abbf2948dccff7cdda5a874be9ad",
1948 "module": "interface",
1949 "function": "create_clearing_house",
1950 "type_arguments": [
1951 "0x6f048d2b0929f0f2c16dd7ccf643b59b7b029d180766780ba83ce9f847306715::usdc::USDC"
1952 ],
1953 "arguments": [
1954 {
1955 "Input": 13
1956 },
1957 {
1958 "Result": 0
1959 },
1960 {
1961 "Input": 3
1962 },
1963 {
1964 "Input": 17
1965 },
1966 {
1967 "Input": 15
1968 },
1969 {
1970 "Input": 16
1971 },
1972 {
1973 "Input": 4
1974 },
1975 {
1976 "Input": 5
1977 },
1978 {
1979 "Input": 6
1980 },
1981 {
1982 "Input": 7
1983 },
1984 {
1985 "Input": 8
1986 },
1987 {
1988 "Input": 6
1989 },
1990 {
1991 "Input": 8
1992 },
1993 {
1994 "Input": 6
1995 },
1996 {
1997 "Input": 9
1998 },
1999 {
2000 "Input": 9
2001 },
2002 {
2003 "Input": 9
2004 },
2005 {
2006 "Input": 10
2007 },
2008 {
2009 "Input": 9
2010 },
2011 {
2012 "Input": 11
2013 },
2014 {
2015 "Input": 12
2016 }
2017 ]
2018 }
2019 },
2020 {
2021 "MoveCall": {
2022 "package": "0xb9f430cddd9cbe60f0beea60fce6da590688abbf2948dccff7cdda5a874be9ad",
2023 "module": "interface",
2024 "function": "register_market",
2025 "type_arguments": [
2026 "0x6f048d2b0929f0f2c16dd7ccf643b59b7b029d180766780ba83ce9f847306715::usdc::USDC"
2027 ],
2028 "arguments": [
2029 {
2030 "Input": 13
2031 },
2032 {
2033 "Input": 14
2034 },
2035 {
2036 "Result": 1
2037 }
2038 ]
2039 }
2040 },
2041 {
2042 "MoveCall": {
2043 "package": "0xb9f430cddd9cbe60f0beea60fce6da590688abbf2948dccff7cdda5a874be9ad",
2044 "module": "interface",
2045 "function": "share_clearing_house",
2046 "type_arguments": [
2047 "0x6f048d2b0929f0f2c16dd7ccf643b59b7b029d180766780ba83ce9f847306715::usdc::USDC"
2048 ],
2049 "arguments": [
2050 {
2051 "Result": 1
2052 }
2053 ]
2054 }
2055 }
2056 ]
2057 },
2058 "sender": "0x98e9cafb116af9d69f77ce0d644c60e384f850f8af050b268377d8293d7fe7c6",
2059 "gasData": {
2060 "payment": [
2061 {
2062 "objectId": "0x39e44ce0f8ee07f02d38c2f5fc7d9805bee241d0acbb2153e1e3fa005abf9736",
2063 "version": 553,
2064 "digest": "A1NSYfktRWLNCVBC6jHmRs5SyhkJYoo4CtNg7QZq3vZC"
2065 }
2066 ],
2067 "owner": "0x98e9cafb116af9d69f77ce0d644c60e384f850f8af050b268377d8293d7fe7c6",
2068 "price": "1000",
2069 "budget": "39172808"
2070 }
2071 },
2072 "txSignatures": [
2073 "ABfUuGXoWtGL54zCZh2Ef3NsNAHQqgibuIFUieVUox8EsTpNgH3WiKq/UgHwnlB3xW7D+AeC5hoBcVO6KbGPBAmvVqH0xXcgXDKTMc2JG4DUIii4K/ah/Is/TjelRccIlg=="
2074 ]
2075 });
2076 let block: SuiTransactionBlock = serde_json::from_value(value.clone()).unwrap();
2077 let restored = serde_json::to_value(&block).unwrap();
2078 assert_eq!(value, restored);
2079 }
2080}