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