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