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