sui_jsonrpc/msgs/
sui_transaction.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use 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
37/// similar to EpochId of sui-types but BigInt
38pub 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    /// If None, no filter will be applied
48    pub filter: Option<TransactionFilter>,
49    /// config which fields to include in the response, by default only digest is included
50    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    /// Whether to show transaction input data. Default to be False
79    pub show_input: bool,
80    /// Whether to show bcs-encoded transaction input data
81    pub show_raw_input: bool,
82    /// Whether to show transaction effects. Default to be False
83    pub show_effects: bool,
84    /// Whether to show transaction events. Default to be False
85    pub show_events: bool,
86    /// Whether to show object_changes. Default to be False
87    pub show_object_changes: bool,
88    /// Whether to show balance_changes. Default to be False
89    pub show_balance_changes: bool,
90    /// Whether to show raw transaction effects. Default to be False
91    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            // This field is added for graphql execution. We keep it false here
108            // so current users of `full_content` will not get raw effects unexpectedly.
109            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    /// default to return `WaitForEffectsCert` unless some options require
149    /// local execution
150    pub fn default_execution_request_type(&self) -> ExecuteTransactionRequestType {
151        // if people want effects or events, they typically want to wait for local execution
152        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    /// Waits for `TransactionEffectsCert` and then return to client. This mode is a proxy for
179    /// transaction finality.
180    WaitForEffectsCert,
181    /// JSON-RPC now ignores this. It will always behave as if
182    /// [`WaitForEffectsCert`](ExecuteTransactionRequestType::WaitForEffectsCert) was passed.
183    ///
184    /// Originally: waits for `TransactionEffectsCert` and make sure the node executed the
185    /// transaction locally before returning the client. The local execution makes sure this node is
186    /// aware of this transaction when client fires subsequent queries. However if the node fails to
187    /// execute the transaction locally in a timely manner, a bool type in the response is set to
188    /// false to indicated the case. request_type is default to be `WaitForEffectsCert` unless
189    /// options.show_events or options.show_effects is true
190    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    /// Transaction input data
199    #[serde(skip_serializing_if = "Option::is_none")]
200    pub transaction: Option<SuiTransactionBlock>,
201    /// BCS encoded [SenderSignedData](https://mystenlabs.github.io/sui/sui_types/transaction/struct.SenderSignedData.html)
202    /// that includes input object references returns empty array if `show_raw_transaction` is false
203    #[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    /// The checkpoint number when this transaction was included and hence finalized.
220    /// This is only returned in the read api, not in the transaction execution api.
221    #[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
342/// We are specifically ignoring events for now until events become more stable.
343impl 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    /// A system transaction that will update epoch information on-chain.
476    ChangeEpoch(SuiChangeEpoch),
477    /// A system transaction used for initializing the initial state of the chain.
478    Genesis(SuiGenesisTransaction),
479    /// A system transaction marking the start of a series of transactions scheduled as part of a
480    /// checkpoint
481    ConsensusCommitPrologue(SuiConsensusCommitPrologue),
482    /// A series of transactions where the results of one transaction can be used in future
483    /// transactions
484    ProgrammableTransaction(SuiProgrammableTransactionBlock),
485    /// A transaction which updates global authenticator state
486    AuthenticatorStateUpdate(SuiAuthenticatorStateUpdate),
487    /// A transaction which updates global randomness state
488    RandomnessStateUpdate(SuiRandomnessStateUpdate),
489    /// The transaction which occurs only at the end of the epoch
490    EndOfEpochTransaction(SuiEndOfEpochTransaction),
491    ConsensusCommitPrologueV2(SuiConsensusCommitPrologueV2),
492    ConsensusCommitPrologueV3(SuiConsensusCommitPrologueV3),
493    // .. more transaction types go here
494}
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    /// Return an iterator of mutated objects, but excluding the gas object.
625    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/// Originally from `sui_types::storage`.
632#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
633pub enum WriteKind {
634    /// The object was in storage already but has been modified
635    Mutate,
636    /// The object was created in this transaction
637    Create,
638    /// The object was previously wrapped in another object, but has been restored to storage
639    Unwrap,
640}
641
642/// Originally from `sui_types::storage`.
643#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
644pub enum DeleteKind {
645    /// An object is provided in the call input, and gets deleted.
646    Normal,
647    /// An object is not provided in the call input, but gets unwrapped
648    /// from another object, and then gets deleted.
649    UnwrapThenDelete,
650    /// An object is provided in the call input, and gets wrapped into another object.
651    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/// The response from processing a transaction or a certified transaction
667#[serde_as]
668#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize)]
669#[serde(rename = "TransactionBlockEffectsV1", rename_all = "camelCase")]
670pub struct SuiTransactionBlockEffectsV1 {
671    /// The status of the execution
672    pub status: SuiExecutionStatus,
673    /// The epoch when this transaction was executed.
674    #[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    /// The version that every modified (mutated or deleted) object had before it was modified by
679    /// this transaction.
680    #[serde(default, skip_serializing_if = "Vec::is_empty")]
681    pub modified_at_versions: Vec<SuiTransactionBlockEffectsModifiedAtVersions>,
682    /// The object references of the shared objects used in this transaction. Empty if no shared objects were used.
683    #[serde(default, skip_serializing_if = "Vec::is_empty")]
684    pub shared_objects: Vec<SuiObjectRef>,
685    /// The transaction digest
686    pub transaction_digest: TransactionDigest,
687    /// ObjectRef and owner of new objects created.
688    #[serde(default, skip_serializing_if = "Vec::is_empty")]
689    pub created: Vec<OwnedObjectRef>,
690    /// ObjectRef and owner of mutated objects, including gas object.
691    #[serde(default, skip_serializing_if = "Vec::is_empty")]
692    pub mutated: Vec<OwnedObjectRef>,
693    /// ObjectRef and owner of objects that are unwrapped in this transaction.
694    /// Unwrapped objects are objects that were wrapped into other objects in the past,
695    /// and just got extracted out.
696    #[serde(default, skip_serializing_if = "Vec::is_empty")]
697    pub unwrapped: Vec<OwnedObjectRef>,
698    /// Object Refs of objects now deleted (the old refs).
699    #[serde(default, skip_serializing_if = "Vec::is_empty")]
700    pub deleted: Vec<SuiObjectRef>,
701    /// Object refs of objects previously wrapped in other objects but now deleted.
702    #[serde(default, skip_serializing_if = "Vec::is_empty")]
703    pub unwrapped_then_deleted: Vec<SuiObjectRef>,
704    /// Object refs of objects now wrapped in other objects.
705    #[serde(default, skip_serializing_if = "Vec::is_empty")]
706    pub wrapped: Vec<SuiObjectRef>,
707    /// The updated gas object reference. Have a dedicated field for convenient access.
708    /// It's also included in mutated.
709    pub gas_object: OwnedObjectRef,
710    /// The digest of the events emitted during execution,
711    /// can be None if the transaction does not emit any event.
712    #[serde(skip_serializing_if = "Option::is_none")]
713    pub events_digest: Option<TransactionEventsDigest>,
714    /// The set of transaction digests this transaction depends on.
715    #[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/// Additional rguments supplied to dev inspect beyond what is allowed in today's API.
962#[derive(Debug, Default, Clone, Serialize, Deserialize)]
963#[serde(rename = "DevInspectArgs", rename_all = "camelCase")]
964pub struct DevInspectArgs {
965    /// The sponsor of the gas for the transaction, might be different from the sender.
966    pub gas_sponsor: Option<SuiAddress>,
967    /// The gas budget for the transaction.
968    pub gas_budget: Option<BigInt<u64>>,
969    /// The gas objects used to pay for the transaction.
970    pub gas_objects: Option<Vec<ObjectRef>>,
971    /// Whether to skip transaction checks for the transaction.
972    pub skip_checks: Option<bool>,
973    /// Whether to return the raw transaction data and effects.
974    pub show_raw_txn_data_and_effects: Option<bool>,
975}
976
977/// The response from processing a dev inspect transaction
978#[derive(Debug, Clone, Serialize, Deserialize)]
979#[serde(rename = "DevInspectResults", rename_all = "camelCase")]
980pub struct DevInspectResults {
981    /// Summary of effects that likely would be generated if the transaction is actually run.
982    /// Note however, that not all dev-inspect transactions are actually usable as transactions so
983    /// it might not be possible actually generate these effects from a normal transaction.
984    pub effects: SuiTransactionBlockEffects,
985    /// Events that likely would be generated if the transaction is actually run.
986    pub events: SuiTransactionBlockEvents,
987    /// Execution results (including return values) from executing the transactions
988    #[serde(skip_serializing_if = "Option::is_none")]
989    pub results: Option<Vec<SuiExecutionResult>>,
990    /// Execution error from executing the transactions
991    #[serde(skip_serializing_if = "Option::is_none")]
992    pub error: Option<String>,
993    /// The raw transaction data that was dev inspected.
994    #[serde(skip_serializing_if = "Vec::is_empty", default)]
995    pub raw_txn_data: Vec<u8>,
996    /// The raw effects of the transaction that was dev inspected.
997    #[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    /// The value of any arguments that were mutably borrowed.
1005    /// Non-mut borrowed values are not included
1006    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1007    pub mutable_reference_outputs: Vec<(/* argument */ SuiArgument, Vec<u8>, SuiTypeTag)>,
1008    /// The return values from the transaction
1009    #[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    /// Regular Sui Transactions that are committed on chain
1016    Commit,
1017    /// Simulated transaction that allows calling any Move function with
1018    /// arbitrary values.
1019    DevInspect,
1020}
1021
1022#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize)]
1023#[serde(rename = "ExecutionStatus", rename_all = "camelCase", tag = "status")]
1024pub enum SuiExecutionStatus {
1025    // Gas used in the success case.
1026    Success,
1027    // Gas used in the failed case, and the error.
1028    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    /// If the error is a [`MoveAbort`], try extracting it.
1052    ///
1053    /// [`MoveAbort`]: af_sui_types::ExecutionError::MoveAbort
1054    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        // NOTE: prepend with "0x" so that parsing to an address works
1063        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    // A Move package, must be immutable.
1343    MovePackage(ObjectId),
1344    // A Move object, either immutable, or owned mutable.
1345    ImmOrOwnedMoveObject(SuiObjectRef),
1346    // A Move object that's shared and mutable.
1347    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/// A series of commands where the results of one command can be used in future
1357/// commands
1358#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1359pub struct SuiProgrammableTransactionBlock {
1360    /// Input objects or primitive values
1361    pub inputs: Vec<SuiCallArg>,
1362    #[serde(rename = "transactions")]
1363    /// The transactions to be executed sequentially. A failure in any transaction will
1364    /// result in the failure of the entire programmable transaction block.
1365    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/// A single transaction in a programmable transaction block.
1381#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1382#[serde(rename = "SuiTransaction")]
1383pub enum SuiCommand {
1384    /// A call to either an entry or a public Move function
1385    MoveCall(Box<SuiProgrammableMoveCall>),
1386    /// `(Vec<forall T:key+store. T>, address)`
1387    /// It sends n-objects to the specified address. These objects must have store
1388    /// (public transfer) and either the previous owner must be an address or the object must
1389    /// be newly created.
1390    TransferObjects(Vec<SuiArgument>, SuiArgument),
1391    /// `(&mut Coin<T>, Vec<u64>)` -> `Vec<Coin<T>>`
1392    /// It splits off some amounts into a new coins with those amounts
1393    SplitCoins(SuiArgument, Vec<SuiArgument>),
1394    /// `(&mut Coin<T>, Vec<Coin<T>>)`
1395    /// It merges n-coins into the first coin
1396    MergeCoins(SuiArgument, Vec<SuiArgument>),
1397    /// Publishes a Move package. It takes the package bytes and a list of the package's transitive
1398    /// dependencies to link against on-chain.
1399    Publish(Vec<ObjectId>),
1400    /// Upgrades a Move package
1401    Upgrade(Vec<ObjectId>, ObjectId, SuiArgument),
1402    /// `forall T: Vec<T> -> vector<T>`
1403    /// Given n-values of the same type, it constructs a vector. For non objects or an empty vector,
1404    /// the type tag must be specified.
1405    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/// An argument to a transaction in a programmable transaction block
1456#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)]
1457pub enum SuiArgument {
1458    /// The gas coin. The gas coin can only be used by-ref, except for with
1459    /// `TransferObjects`, which can use it by-value.
1460    GasCoin,
1461    /// One of the input objects or primitive values (from
1462    /// `ProgrammableTransactionBlock` inputs)
1463    Input(u16),
1464    /// The result of another transaction (from `ProgrammableTransactionBlock` transactions)
1465    Result(u16),
1466    /// Like a `Result` but it accesses a nested result. Currently, the only usage
1467    /// of this is to access a value from a Move call with multiple return values.
1468    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/// The transaction for calling a Move function, either an entry function or a public
1483/// function (which cannot return references).
1484#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1485pub struct SuiProgrammableMoveCall {
1486    /// The package containing the module and function.
1487    pub package: ObjectId,
1488    /// The specific module in the package containing the function.
1489    pub module: String,
1490    /// The function to be called.
1491    pub function: String,
1492    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1493    /// The type arguments to the function.
1494    pub type_arguments: Vec<String>,
1495    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1496    /// The arguments to the function.
1497    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    /// BCS serialized transaction data bytes without its type tag, as base-64 encoded string.
1589    pub tx_bytes: String,
1590    /// the gas objects to be used
1591    pub gas: Vec<SuiObjectRef>,
1592    /// objects to be used in this transaction
1593    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    // Needs to become an Object Ref or Object ID, depending on object type
1616    Object(SuiObjectArg),
1617    // pure value, bcs encoded
1618    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<AsSuiTypeTag>")]
1644    #[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    // A Move object, either immutable, or owned mutable.
1664    #[serde(rename_all = "camelCase")]
1665    ImmOrOwnedObject {
1666        object_id: ObjectId,
1667        #[serde_as(as = "BigInt<u64>")]
1668        version: Version,
1669        digest: ObjectDigest,
1670    },
1671    // A Move object that's shared.
1672    // SharedObject::mutable controls whether caller asks for a mutable reference to shared object.
1673    #[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    // A reference to a Move object that's going to be received in the transaction.
1681    #[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    /// CURRENTLY NOT SUPPORTED. Query by checkpoint.
1695    Checkpoint(#[serde_as(as = "IfIsHumanReadable<BigInt<u64>, _>")] Version),
1696    /// Query by move function.
1697    MoveFunction {
1698        package: ObjectId,
1699        module: Option<String>,
1700        function: Option<String>,
1701    },
1702    /// Query by input object.
1703    InputObject(ObjectId),
1704    /// Query by changed object, including created, mutated and unwrapped objects.
1705    ChangedObject(ObjectId),
1706    /// Query for transactions that touch this object.
1707    AffectedObject(ObjectId),
1708    /// Query by sender address.
1709    FromAddress(SuiAddress),
1710    /// Query by recipient address.
1711    ToAddress(SuiAddress),
1712    /// Query by sender and recipient address.
1713    FromAndToAddress { from: SuiAddress, to: SuiAddress },
1714    /// CURRENTLY NOT SUPPORTED. Query txs that have a given address as sender or recipient.
1715    FromOrToAddress { addr: SuiAddress },
1716    /// Query by transaction kind
1717    TransactionKind(String),
1718    /// Query transactions of any given kind in the input.
1719    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}