Skip to main content

sui_json_rpc_types/
sui_transaction.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use std::fmt::{self, Display, Formatter, Write};
5
6use enum_dispatch::enum_dispatch;
7use schemars::JsonSchema;
8use serde::{Deserialize, Serialize};
9use serde_with::serde_as;
10use sui_package_resolver::{PackageStore, Resolver};
11use tabled::{
12    builder::Builder as TableBuilder,
13    settings::{Panel as TablePanel, Style as TableStyle, style::HorizontalLine},
14};
15
16use fastcrypto::encoding::Base64;
17use move_binary_format::CompiledModule;
18use move_bytecode_utils::module_cache::GetModule;
19use move_core_types::annotated_value::MoveTypeLayout;
20use move_core_types::identifier::{IdentStr, Identifier};
21use move_core_types::language_storage::{ModuleId, StructTag, TypeTag};
22use mysten_common::ZipDebugEqIteratorExt;
23use mysten_metrics::monitored_scope;
24use nonempty::NonEmpty;
25use sui_json::{SuiJsonValue, primitive_type};
26use sui_types::SUI_FRAMEWORK_ADDRESS;
27use sui_types::accumulator_event::AccumulatorEvent;
28use sui_types::base_types::{
29    EpochId, ObjectID, ObjectRef, SequenceNumber, SuiAddress, TransactionDigest,
30};
31use sui_types::crypto::SuiSignature;
32use sui_types::digests::Digest;
33use sui_types::digests::{
34    AdditionalConsensusStateDigest, CheckpointDigest, ConsensusCommitDigest, ObjectDigest,
35    TransactionEventsDigest,
36};
37use sui_types::effects::{
38    AccumulatorOperation, AccumulatorValue, TransactionEffects, TransactionEffectsAPI,
39    TransactionEvents,
40};
41use sui_types::error::{ExecutionError, SuiError, SuiResult};
42use sui_types::execution_status::{ExecutionFailure, ExecutionStatus};
43use sui_types::gas::GasCostSummary;
44use sui_types::layout_resolver::{LayoutResolver, get_layout_from_struct_tag};
45use sui_types::messages_checkpoint::CheckpointSequenceNumber;
46use sui_types::messages_consensus::ConsensusDeterminedVersionAssignments;
47use sui_types::object::Owner;
48use sui_types::parse_sui_type_tag;
49use sui_types::signature::GenericSignature;
50use sui_types::storage::{DeleteKind, WriteKind};
51use sui_types::sui_serde::Readable;
52use sui_types::sui_serde::{
53    BigInt, SequenceNumber as AsSequenceNumber, SuiTypeTag as AsSuiTypeTag,
54};
55use sui_types::transaction::{
56    Argument, CallArg, ChangeEpoch, Command, EndOfEpochTransactionKind, GenesisObject,
57    InputObjectKind, ObjectArg, ProgrammableMoveCall, ProgrammableTransaction, Reservation,
58    SenderSignedData, TransactionData, TransactionDataAPI, TransactionKind, WithdrawFrom,
59    WithdrawalTypeArg,
60};
61use sui_types::transaction_driver_types::ExecuteTransactionRequestType;
62use sui_types::{authenticator_state::ActiveJwk, transaction::SharedObjectMutability};
63
64use crate::balance_changes::BalanceChange;
65use crate::object_changes::ObjectChange;
66use crate::sui_transaction::GenericSignature::Signature;
67use crate::{Filter, Page, SuiEvent, SuiMoveAbort, SuiObjectRef};
68
69// similar to EpochId of sui-types but BigInt
70pub type SuiEpochId = BigInt<u64>;
71
72#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, Default)]
73#[serde(
74    rename_all = "camelCase",
75    rename = "TransactionBlockResponseQuery",
76    default
77)]
78pub struct SuiTransactionBlockResponseQuery {
79    /// If None, no filter will be applied
80    pub filter: Option<TransactionFilter>,
81    /// config which fields to include in the response, by default only digest is included
82    pub options: Option<SuiTransactionBlockResponseOptions>,
83}
84
85impl SuiTransactionBlockResponseQuery {
86    pub fn new(
87        filter: Option<TransactionFilter>,
88        options: Option<SuiTransactionBlockResponseOptions>,
89    ) -> Self {
90        Self { filter, options }
91    }
92
93    pub fn new_with_filter(filter: TransactionFilter) -> Self {
94        Self {
95            filter: Some(filter),
96            options: None,
97        }
98    }
99}
100
101pub type TransactionBlocksPage = Page<SuiTransactionBlockResponse, TransactionDigest>;
102
103#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, Eq, PartialEq, Default)]
104#[serde(
105    rename_all = "camelCase",
106    rename = "TransactionBlockResponseOptions",
107    default
108)]
109pub struct SuiTransactionBlockResponseOptions {
110    /// Whether to show transaction input data. Default to be False
111    pub show_input: bool,
112    /// Whether to show bcs-encoded transaction input data
113    pub show_raw_input: bool,
114    /// Whether to show transaction effects. Default to be False
115    pub show_effects: bool,
116    /// Whether to show transaction events. Default to be False
117    pub show_events: bool,
118    /// Whether to show object_changes. Default to be False
119    pub show_object_changes: bool,
120    /// Whether to show balance_changes. Default to be False
121    pub show_balance_changes: bool,
122    /// Whether to show raw transaction effects. Default to be False
123    pub show_raw_effects: bool,
124}
125
126impl SuiTransactionBlockResponseOptions {
127    pub fn new() -> Self {
128        Self::default()
129    }
130
131    pub fn full_content() -> Self {
132        Self {
133            show_effects: true,
134            show_input: true,
135            show_raw_input: true,
136            show_events: true,
137            show_object_changes: true,
138            show_balance_changes: true,
139            // This field is added for graphql execution. We keep it false here
140            // so current users of `full_content` will not get raw effects unexpectedly.
141            show_raw_effects: false,
142        }
143    }
144
145    pub fn with_input(mut self) -> Self {
146        self.show_input = true;
147        self
148    }
149
150    pub fn with_raw_input(mut self) -> Self {
151        self.show_raw_input = true;
152        self
153    }
154
155    pub fn with_effects(mut self) -> Self {
156        self.show_effects = true;
157        self
158    }
159
160    pub fn with_events(mut self) -> Self {
161        self.show_events = true;
162        self
163    }
164
165    pub fn with_balance_changes(mut self) -> Self {
166        self.show_balance_changes = true;
167        self
168    }
169
170    pub fn with_object_changes(mut self) -> Self {
171        self.show_object_changes = true;
172        self
173    }
174
175    pub fn with_raw_effects(mut self) -> Self {
176        self.show_raw_effects = true;
177        self
178    }
179
180    /// default to return `WaitForEffectsCert` unless some options require
181    /// local execution
182    pub fn default_execution_request_type(&self) -> ExecuteTransactionRequestType {
183        // if people want effects or events, they typically want to wait for local execution
184        if self.require_effects() {
185            ExecuteTransactionRequestType::WaitForLocalExecution
186        } else {
187            ExecuteTransactionRequestType::WaitForEffectsCert
188        }
189    }
190
191    #[deprecated(
192        since = "1.33.0",
193        note = "Balance and object changes no longer require local execution"
194    )]
195    pub fn require_local_execution(&self) -> bool {
196        self.show_balance_changes || self.show_object_changes
197    }
198
199    pub fn require_input(&self) -> bool {
200        self.show_input || self.show_raw_input || self.show_object_changes
201    }
202
203    pub fn require_effects(&self) -> bool {
204        self.show_effects
205            || self.show_events
206            || self.show_balance_changes
207            || self.show_object_changes
208            || self.show_raw_effects
209    }
210
211    pub fn only_digest(&self) -> bool {
212        self == &Self::default()
213    }
214}
215
216#[serde_as]
217#[derive(Serialize, Deserialize, Debug, JsonSchema, Clone, Default)]
218#[serde(rename_all = "camelCase", rename = "TransactionBlockResponse")]
219pub struct SuiTransactionBlockResponse {
220    pub digest: TransactionDigest,
221    /// Transaction input data
222    #[serde(skip_serializing_if = "Option::is_none")]
223    pub transaction: Option<SuiTransactionBlock>,
224    /// BCS encoded [SenderSignedData] that includes input object references
225    /// returns empty array if `show_raw_transaction` is false
226    #[serde_as(as = "Base64")]
227    #[schemars(with = "Base64")]
228    #[serde(skip_serializing_if = "Vec::is_empty", default)]
229    pub raw_transaction: Vec<u8>,
230    #[serde(skip_serializing_if = "Option::is_none")]
231    pub effects: Option<SuiTransactionBlockEffects>,
232    #[serde(skip_serializing_if = "Option::is_none")]
233    pub events: Option<SuiTransactionBlockEvents>,
234    #[serde(skip_serializing_if = "Option::is_none")]
235    pub object_changes: Option<Vec<ObjectChange>>,
236    #[serde(skip_serializing_if = "Option::is_none")]
237    pub balance_changes: Option<Vec<BalanceChange>>,
238    #[serde(default, skip_serializing_if = "Option::is_none")]
239    #[schemars(with = "Option<BigInt<u64>>")]
240    #[serde_as(as = "Option<BigInt<u64>>")]
241    pub timestamp_ms: Option<u64>,
242    #[serde(default, skip_serializing_if = "Option::is_none")]
243    pub confirmed_local_execution: Option<bool>,
244    /// The checkpoint number when this transaction was included and hence finalized.
245    /// This is only returned in the read api, not in the transaction execution api.
246    #[schemars(with = "Option<BigInt<u64>>")]
247    #[serde_as(as = "Option<BigInt<u64>>")]
248    #[serde(skip_serializing_if = "Option::is_none")]
249    pub checkpoint: Option<CheckpointSequenceNumber>,
250    #[serde(skip_serializing_if = "Vec::is_empty", default)]
251    pub errors: Vec<String>,
252    #[serde(skip_serializing_if = "Vec::is_empty", default)]
253    pub raw_effects: Vec<u8>,
254}
255
256impl SuiTransactionBlockResponse {
257    pub fn new(digest: TransactionDigest) -> Self {
258        Self {
259            digest,
260            ..Default::default()
261        }
262    }
263
264    pub fn status_ok(&self) -> Option<bool> {
265        self.effects.as_ref().map(|e| e.status().is_ok())
266    }
267
268    pub fn get_new_package_obj(&self) -> Option<ObjectRef> {
269        self.object_changes.as_ref().and_then(|changes| {
270            changes
271                .iter()
272                .find(|change| matches!(change, ObjectChange::Published { .. }))
273                .map(|change| change.object_ref())
274        })
275    }
276
277    pub fn get_new_package_upgrade_cap(&self) -> Option<ObjectRef> {
278        self.object_changes.as_ref().and_then(|changes| {
279            changes
280                .iter()
281                .find(|change| {
282                    matches!(change, ObjectChange::Created {
283                        owner: Owner::AddressOwner(_),
284                        object_type: StructTag {
285                            address: SUI_FRAMEWORK_ADDRESS,
286                            module,
287                            name,
288                            ..
289                        },
290                        ..
291                    } if module.as_str() == "package" && name.as_str() == "UpgradeCap")
292                })
293                .map(|change| change.object_ref())
294        })
295    }
296}
297
298/// We are specifically ignoring events for now until events become more stable.
299impl PartialEq for SuiTransactionBlockResponse {
300    fn eq(&self, other: &Self) -> bool {
301        self.transaction == other.transaction
302            && self.effects == other.effects
303            && self.timestamp_ms == other.timestamp_ms
304            && self.confirmed_local_execution == other.confirmed_local_execution
305            && self.checkpoint == other.checkpoint
306    }
307}
308
309impl Display for SuiTransactionBlockResponse {
310    fn fmt(&self, writer: &mut Formatter<'_>) -> fmt::Result {
311        writeln!(writer, "Transaction Digest: {}", &self.digest)?;
312
313        if let Some(t) = &self.transaction {
314            writeln!(writer, "{}", t)?;
315        }
316
317        if let Some(e) = &self.effects {
318            writeln!(writer, "{}", e)?;
319        }
320
321        if let Some(e) = &self.events {
322            writeln!(writer, "{}", e)?;
323        }
324
325        if let Some(object_changes) = &self.object_changes {
326            let mut builder = TableBuilder::default();
327            let (
328                mut created,
329                mut deleted,
330                mut mutated,
331                mut published,
332                mut transferred,
333                mut wrapped,
334            ) = (vec![], vec![], vec![], vec![], vec![], vec![]);
335
336            for obj in object_changes {
337                match obj {
338                    ObjectChange::Created { .. } => created.push(obj),
339                    ObjectChange::Deleted { .. } => deleted.push(obj),
340                    ObjectChange::Mutated { .. } => mutated.push(obj),
341                    ObjectChange::Published { .. } => published.push(obj),
342                    ObjectChange::Transferred { .. } => transferred.push(obj),
343                    ObjectChange::Wrapped { .. } => wrapped.push(obj),
344                };
345            }
346
347            write_obj_changes(created, "Created", &mut builder)?;
348            write_obj_changes(deleted, "Deleted", &mut builder)?;
349            write_obj_changes(mutated, "Mutated", &mut builder)?;
350            write_obj_changes(published, "Published", &mut builder)?;
351            write_obj_changes(transferred, "Transferred", &mut builder)?;
352            write_obj_changes(wrapped, "Wrapped", &mut builder)?;
353
354            let mut table = builder.build();
355            table.with(TablePanel::header("Object Changes"));
356            table.with(TableStyle::rounded().horizontals([HorizontalLine::new(
357                1,
358                TableStyle::modern().get_horizontal(),
359            )]));
360            writeln!(writer, "{}", table)?;
361        }
362
363        if let Some(balance_changes) = &self.balance_changes {
364            // Only build a table if the vector of balance changes is non-empty.
365            // Empty balance changes occur, for example, for system transactions
366            // like `ConsensusCommitPrologueV3`
367            if !balance_changes.is_empty() {
368                let mut builder = TableBuilder::default();
369
370                for balance in balance_changes {
371                    builder.push_record(vec![format!("{}", balance)]);
372                }
373
374                let mut table = builder.build();
375                table.with(TablePanel::header("Balance Changes"));
376                table.with(TableStyle::rounded().horizontals([HorizontalLine::new(
377                    1,
378                    TableStyle::modern().get_horizontal(),
379                )]));
380                writeln!(writer, "{}", table)?;
381            } else {
382                writeln!(writer, "╭────────────────────╮")?;
383                writeln!(writer, "│ No balance changes │")?;
384                writeln!(writer, "╰────────────────────╯")?;
385            }
386        }
387        Ok(())
388    }
389}
390
391fn write_obj_changes<T: Display>(
392    values: Vec<T>,
393    output_string: &str,
394    builder: &mut TableBuilder,
395) -> std::fmt::Result {
396    if !values.is_empty() {
397        builder.push_record(vec![format!("{} Objects: ", output_string)]);
398        for obj in values {
399            builder.push_record(vec![format!("{}", obj)]);
400        }
401    }
402    Ok(())
403}
404
405#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
406#[serde(rename = "TransactionBlockKind", tag = "kind")]
407pub enum SuiTransactionBlockKind {
408    /// A system transaction that will update epoch information on-chain.
409    ChangeEpoch(SuiChangeEpoch),
410    /// A system transaction used for initializing the initial state of the chain.
411    Genesis(SuiGenesisTransaction),
412    /// A system transaction marking the start of a series of transactions scheduled as part of a
413    /// checkpoint
414    ConsensusCommitPrologue(SuiConsensusCommitPrologue),
415    /// A series of transactions where the results of one transaction can be used in future
416    /// transactions
417    ProgrammableTransaction(SuiProgrammableTransactionBlock),
418    /// A transaction which updates global authenticator state
419    AuthenticatorStateUpdate(SuiAuthenticatorStateUpdate),
420    /// A transaction which updates global randomness state
421    RandomnessStateUpdate(SuiRandomnessStateUpdate),
422    /// The transaction which occurs only at the end of the epoch
423    EndOfEpochTransaction(SuiEndOfEpochTransaction),
424    ConsensusCommitPrologueV2(SuiConsensusCommitPrologueV2),
425    ConsensusCommitPrologueV3(SuiConsensusCommitPrologueV3),
426    ConsensusCommitPrologueV4(SuiConsensusCommitPrologueV4),
427
428    ProgrammableSystemTransaction(SuiProgrammableTransactionBlock),
429    // .. more transaction types go here
430}
431
432impl Display for SuiTransactionBlockKind {
433    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
434        let mut writer = String::new();
435        match &self {
436            Self::ChangeEpoch(e) => {
437                writeln!(writer, "Transaction Kind: Epoch Change")?;
438                writeln!(writer, "New epoch ID: {}", e.epoch)?;
439                writeln!(writer, "Storage gas reward: {}", e.storage_charge)?;
440                writeln!(writer, "Computation gas reward: {}", e.computation_charge)?;
441                writeln!(writer, "Storage rebate: {}", e.storage_rebate)?;
442                writeln!(writer, "Timestamp: {}", e.epoch_start_timestamp_ms)?;
443            }
444            Self::Genesis(_) => {
445                writeln!(writer, "Transaction Kind: Genesis Transaction")?;
446            }
447            Self::ConsensusCommitPrologue(p) => {
448                writeln!(writer, "Transaction Kind: Consensus Commit Prologue")?;
449                writeln!(
450                    writer,
451                    "Epoch: {}, Round: {}, Timestamp: {}",
452                    p.epoch, p.round, p.commit_timestamp_ms
453                )?;
454            }
455            Self::ConsensusCommitPrologueV2(p) => {
456                writeln!(writer, "Transaction Kind: Consensus Commit Prologue V2")?;
457                writeln!(
458                    writer,
459                    "Epoch: {}, Round: {}, Timestamp: {}, ConsensusCommitDigest: {}",
460                    p.epoch, p.round, p.commit_timestamp_ms, p.consensus_commit_digest
461                )?;
462            }
463            Self::ConsensusCommitPrologueV3(p) => {
464                writeln!(writer, "Transaction Kind: Consensus Commit Prologue V3")?;
465                writeln!(
466                    writer,
467                    "Epoch: {}, Round: {}, SubDagIndex: {:?}, Timestamp: {}, ConsensusCommitDigest: {}",
468                    p.epoch,
469                    p.round,
470                    p.sub_dag_index,
471                    p.commit_timestamp_ms,
472                    p.consensus_commit_digest
473                )?;
474            }
475            Self::ConsensusCommitPrologueV4(p) => {
476                writeln!(writer, "Transaction Kind: Consensus Commit Prologue V4")?;
477                writeln!(
478                    writer,
479                    "Epoch: {}, Round: {}, SubDagIndex: {:?}, Timestamp: {}, ConsensusCommitDigest: {} AdditionalStateDigest: {}",
480                    p.epoch,
481                    p.round,
482                    p.sub_dag_index,
483                    p.commit_timestamp_ms,
484                    p.consensus_commit_digest,
485                    p.additional_state_digest
486                )?;
487            }
488            Self::ProgrammableTransaction(p) => {
489                write!(writer, "Transaction Kind: Programmable")?;
490                write!(writer, "{}", crate::displays::Pretty(p))?;
491            }
492            Self::ProgrammableSystemTransaction(p) => {
493                write!(writer, "Transaction Kind: Programmable System")?;
494                write!(writer, "{}", crate::displays::Pretty(p))?;
495            }
496            Self::AuthenticatorStateUpdate(_) => {
497                writeln!(writer, "Transaction Kind: Authenticator State Update")?;
498            }
499            Self::RandomnessStateUpdate(_) => {
500                writeln!(writer, "Transaction Kind: Randomness State Update")?;
501            }
502            Self::EndOfEpochTransaction(_) => {
503                writeln!(writer, "Transaction Kind: End of Epoch Transaction")?;
504            }
505        }
506        write!(f, "{}", writer)
507    }
508}
509
510impl SuiTransactionBlockKind {
511    fn try_from_inner(tx: TransactionKind) -> Result<Self, anyhow::Error> {
512        Ok(match tx {
513            TransactionKind::ChangeEpoch(e) => Self::ChangeEpoch(e.into()),
514            TransactionKind::Genesis(g) => Self::Genesis(SuiGenesisTransaction {
515                objects: g.objects.iter().map(GenesisObject::id).collect(),
516            }),
517            TransactionKind::ConsensusCommitPrologue(p) => {
518                Self::ConsensusCommitPrologue(SuiConsensusCommitPrologue {
519                    epoch: p.epoch,
520                    round: p.round,
521                    commit_timestamp_ms: p.commit_timestamp_ms,
522                })
523            }
524            TransactionKind::ConsensusCommitPrologueV2(p) => {
525                Self::ConsensusCommitPrologueV2(SuiConsensusCommitPrologueV2 {
526                    epoch: p.epoch,
527                    round: p.round,
528                    commit_timestamp_ms: p.commit_timestamp_ms,
529                    consensus_commit_digest: p.consensus_commit_digest,
530                })
531            }
532            TransactionKind::ConsensusCommitPrologueV3(p) => {
533                Self::ConsensusCommitPrologueV3(SuiConsensusCommitPrologueV3 {
534                    epoch: p.epoch,
535                    round: p.round,
536                    sub_dag_index: p.sub_dag_index,
537                    commit_timestamp_ms: p.commit_timestamp_ms,
538                    consensus_commit_digest: p.consensus_commit_digest,
539                    consensus_determined_version_assignments: p
540                        .consensus_determined_version_assignments,
541                })
542            }
543            TransactionKind::ConsensusCommitPrologueV4(p) => {
544                Self::ConsensusCommitPrologueV4(SuiConsensusCommitPrologueV4 {
545                    epoch: p.epoch,
546                    round: p.round,
547                    sub_dag_index: p.sub_dag_index,
548                    commit_timestamp_ms: p.commit_timestamp_ms,
549                    consensus_commit_digest: p.consensus_commit_digest,
550                    consensus_determined_version_assignments: p
551                        .consensus_determined_version_assignments,
552                    additional_state_digest: p.additional_state_digest,
553                })
554            }
555            TransactionKind::ProgrammableTransaction(_)
556            | TransactionKind::ProgrammableSystemTransaction(_) => {
557                // This case is handled separately by the callers
558                unreachable!()
559            }
560            TransactionKind::AuthenticatorStateUpdate(update) => {
561                Self::AuthenticatorStateUpdate(SuiAuthenticatorStateUpdate {
562                    epoch: update.epoch,
563                    round: update.round,
564                    new_active_jwks: update
565                        .new_active_jwks
566                        .into_iter()
567                        .map(SuiActiveJwk::from)
568                        .collect(),
569                })
570            }
571            TransactionKind::RandomnessStateUpdate(update) => {
572                Self::RandomnessStateUpdate(SuiRandomnessStateUpdate {
573                    epoch: update.epoch,
574                    randomness_round: update.randomness_round.0,
575                    random_bytes: update.random_bytes,
576                })
577            }
578            TransactionKind::EndOfEpochTransaction(end_of_epoch_tx) => {
579                Self::EndOfEpochTransaction(SuiEndOfEpochTransaction {
580                    transactions: end_of_epoch_tx
581                        .into_iter()
582                        .map(|tx| match tx {
583                            EndOfEpochTransactionKind::ChangeEpoch(e) => {
584                                SuiEndOfEpochTransactionKind::ChangeEpoch(e.into())
585                            }
586                            EndOfEpochTransactionKind::AuthenticatorStateCreate => {
587                                SuiEndOfEpochTransactionKind::AuthenticatorStateCreate
588                            }
589                            EndOfEpochTransactionKind::AuthenticatorStateExpire(expire) => {
590                                SuiEndOfEpochTransactionKind::AuthenticatorStateExpire(
591                                    SuiAuthenticatorStateExpire {
592                                        min_epoch: expire.min_epoch,
593                                    },
594                                )
595                            }
596                            EndOfEpochTransactionKind::RandomnessStateCreate => {
597                                SuiEndOfEpochTransactionKind::RandomnessStateCreate
598                            }
599                            EndOfEpochTransactionKind::DenyListStateCreate => {
600                                SuiEndOfEpochTransactionKind::CoinDenyListStateCreate
601                            }
602                            EndOfEpochTransactionKind::BridgeStateCreate(chain_id) => {
603                                SuiEndOfEpochTransactionKind::BridgeStateCreate(
604                                    (*chain_id.as_bytes()).into(),
605                                )
606                            }
607                            EndOfEpochTransactionKind::BridgeCommitteeInit(
608                                bridge_shared_version,
609                            ) => SuiEndOfEpochTransactionKind::BridgeCommitteeUpdate(
610                                bridge_shared_version,
611                            ),
612                            EndOfEpochTransactionKind::StoreExecutionTimeObservations(_) => {
613                                SuiEndOfEpochTransactionKind::StoreExecutionTimeObservations
614                            }
615                            EndOfEpochTransactionKind::AccumulatorRootCreate => {
616                                SuiEndOfEpochTransactionKind::AccumulatorRootCreate
617                            }
618                            EndOfEpochTransactionKind::CoinRegistryCreate => {
619                                SuiEndOfEpochTransactionKind::CoinRegistryCreate
620                            }
621                            EndOfEpochTransactionKind::DisplayRegistryCreate => {
622                                SuiEndOfEpochTransactionKind::DisplayRegistryCreate
623                            }
624                            EndOfEpochTransactionKind::AddressAliasStateCreate => {
625                                SuiEndOfEpochTransactionKind::AddressAliasStateCreate
626                            }
627                            EndOfEpochTransactionKind::WriteAccumulatorStorageCost(_) => {
628                                SuiEndOfEpochTransactionKind::WriteAccumulatorStorageCost
629                            }
630                            EndOfEpochTransactionKind::ForwardingAddressRegistryCreate => {
631                                SuiEndOfEpochTransactionKind::ForwardingAddressRegistryCreate
632                            }
633                        })
634                        .collect(),
635                })
636            }
637        })
638    }
639
640    fn try_from_with_module_cache(
641        tx: TransactionKind,
642        module_cache: &impl GetModule,
643    ) -> Result<Self, anyhow::Error> {
644        match tx {
645            TransactionKind::ProgrammableTransaction(p)
646            | TransactionKind::ProgrammableSystemTransaction(p) => {
647                Ok(Self::ProgrammableTransaction(
648                    SuiProgrammableTransactionBlock::try_from_with_module_cache(p, module_cache)?,
649                ))
650            }
651            tx => Self::try_from_inner(tx),
652        }
653    }
654
655    async fn try_from_with_package_resolver(
656        tx: TransactionKind,
657        package_resolver: &Resolver<impl PackageStore>,
658    ) -> Result<Self, anyhow::Error> {
659        match tx {
660            TransactionKind::ProgrammableSystemTransaction(p) => {
661                Ok(Self::ProgrammableSystemTransaction(
662                    SuiProgrammableTransactionBlock::try_from_with_package_resolver(
663                        p,
664                        package_resolver,
665                    )
666                    .await?,
667                ))
668            }
669            TransactionKind::ProgrammableTransaction(p) => Ok(Self::ProgrammableTransaction(
670                SuiProgrammableTransactionBlock::try_from_with_package_resolver(
671                    p,
672                    package_resolver,
673                )
674                .await?,
675            )),
676            tx => Self::try_from_inner(tx),
677        }
678    }
679
680    pub fn transaction_count(&self) -> usize {
681        match self {
682            Self::ProgrammableTransaction(p) | Self::ProgrammableSystemTransaction(p) => {
683                p.commands.len()
684            }
685            _ => 1,
686        }
687    }
688
689    pub fn name(&self) -> &'static str {
690        match self {
691            Self::ChangeEpoch(_) => "ChangeEpoch",
692            Self::Genesis(_) => "Genesis",
693            Self::ConsensusCommitPrologue(_) => "ConsensusCommitPrologue",
694            Self::ConsensusCommitPrologueV2(_) => "ConsensusCommitPrologueV2",
695            Self::ConsensusCommitPrologueV3(_) => "ConsensusCommitPrologueV3",
696            Self::ConsensusCommitPrologueV4(_) => "ConsensusCommitPrologueV4",
697            Self::ProgrammableTransaction(_) => "ProgrammableTransaction",
698            Self::ProgrammableSystemTransaction(_) => "ProgrammableSystemTransaction",
699            Self::AuthenticatorStateUpdate(_) => "AuthenticatorStateUpdate",
700            Self::RandomnessStateUpdate(_) => "RandomnessStateUpdate",
701            Self::EndOfEpochTransaction(_) => "EndOfEpochTransaction",
702        }
703    }
704}
705
706#[serde_as]
707#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
708pub struct SuiChangeEpoch {
709    #[schemars(with = "BigInt<u64>")]
710    #[serde_as(as = "BigInt<u64>")]
711    pub epoch: EpochId,
712    #[schemars(with = "BigInt<u64>")]
713    #[serde_as(as = "BigInt<u64>")]
714    pub storage_charge: u64,
715    #[schemars(with = "BigInt<u64>")]
716    #[serde_as(as = "BigInt<u64>")]
717    pub computation_charge: u64,
718    #[schemars(with = "BigInt<u64>")]
719    #[serde_as(as = "BigInt<u64>")]
720    pub storage_rebate: u64,
721    #[schemars(with = "BigInt<u64>")]
722    #[serde_as(as = "BigInt<u64>")]
723    pub epoch_start_timestamp_ms: u64,
724}
725
726impl From<ChangeEpoch> for SuiChangeEpoch {
727    fn from(e: ChangeEpoch) -> Self {
728        Self {
729            epoch: e.epoch,
730            storage_charge: e.storage_charge,
731            computation_charge: e.computation_charge,
732            storage_rebate: e.storage_rebate,
733            epoch_start_timestamp_ms: e.epoch_start_timestamp_ms,
734        }
735    }
736}
737
738#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, PartialEq, Eq)]
739#[enum_dispatch(SuiTransactionBlockEffectsAPI)]
740#[serde(
741    rename = "TransactionBlockEffects",
742    rename_all = "camelCase",
743    tag = "messageVersion"
744)]
745pub enum SuiTransactionBlockEffects {
746    V1(SuiTransactionBlockEffectsV1),
747}
748
749#[enum_dispatch]
750pub trait SuiTransactionBlockEffectsAPI {
751    fn status(&self) -> &SuiExecutionStatus;
752    fn into_status(self) -> SuiExecutionStatus;
753    fn shared_objects(&self) -> &[SuiObjectRef];
754    fn created(&self) -> &[OwnedObjectRef];
755    fn mutated(&self) -> &[OwnedObjectRef];
756    fn unwrapped(&self) -> &[OwnedObjectRef];
757    fn deleted(&self) -> &[SuiObjectRef];
758    fn unwrapped_then_deleted(&self) -> &[SuiObjectRef];
759    fn wrapped(&self) -> &[SuiObjectRef];
760    fn gas_object(&self) -> &OwnedObjectRef;
761    fn events_digest(&self) -> Option<&TransactionEventsDigest>;
762    fn dependencies(&self) -> &[TransactionDigest];
763    fn executed_epoch(&self) -> EpochId;
764    fn transaction_digest(&self) -> &TransactionDigest;
765    fn gas_cost_summary(&self) -> &GasCostSummary;
766
767    /// Return an iterator of mutated objects, but excluding the gas object.
768    fn mutated_excluding_gas(&self) -> Vec<OwnedObjectRef>;
769    fn modified_at_versions(&self) -> Vec<(ObjectID, SequenceNumber)>;
770    fn all_changed_objects(&self) -> Vec<(&OwnedObjectRef, WriteKind)>;
771    fn all_deleted_objects(&self) -> Vec<(&SuiObjectRef, DeleteKind)>;
772
773    fn accumulator_events(&self) -> Vec<SuiAccumulatorEvent>;
774}
775
776#[serde_as]
777#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize, JsonSchema)]
778#[serde(
779    rename = "TransactionBlockEffectsModifiedAtVersions",
780    rename_all = "camelCase"
781)]
782pub struct SuiTransactionBlockEffectsModifiedAtVersions {
783    object_id: ObjectID,
784    #[schemars(with = "AsSequenceNumber")]
785    #[serde_as(as = "AsSequenceNumber")]
786    sequence_number: SequenceNumber,
787}
788
789#[serde_as]
790#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize, JsonSchema)]
791#[serde(rename = "AccumulatorEvent", rename_all = "camelCase")]
792pub struct SuiAccumulatorEvent {
793    pub accumulator_obj: ObjectID,
794    pub address: SuiAddress,
795    pub ty: SuiTypeTag,
796    pub operation: SuiAccumulatorOperation,
797    pub value: SuiAccumulatorValue,
798}
799
800impl From<AccumulatorEvent> for SuiAccumulatorEvent {
801    fn from(event: AccumulatorEvent) -> Self {
802        let AccumulatorEvent {
803            accumulator_obj,
804            write,
805        } = event;
806        Self {
807            accumulator_obj: accumulator_obj.inner().to_owned(),
808            address: write.address.address,
809            ty: write.address.ty.into(),
810            operation: write.operation.into(),
811            value: write.value.into(),
812        }
813    }
814}
815
816#[serde_as]
817#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize, JsonSchema)]
818#[serde(rename = "AccumulatorOperation", rename_all = "camelCase")]
819pub enum SuiAccumulatorOperation {
820    Merge,
821    Split,
822}
823
824impl From<AccumulatorOperation> for SuiAccumulatorOperation {
825    fn from(operation: AccumulatorOperation) -> Self {
826        match operation {
827            AccumulatorOperation::Merge => Self::Merge,
828            AccumulatorOperation::Split => Self::Split,
829        }
830    }
831}
832
833#[serde_as]
834#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize, JsonSchema)]
835#[serde(rename = "AccumulatorValue", rename_all = "camelCase")]
836pub enum SuiAccumulatorValue {
837    Integer(u64),
838    IntegerTuple(u64, u64),
839    #[schemars(with = "Vec<(u64, Digest)>")]
840    EventDigest(NonEmpty<(u64 /* event index in the transaction */, Digest)>),
841}
842
843impl From<AccumulatorValue> for SuiAccumulatorValue {
844    fn from(value: AccumulatorValue) -> Self {
845        match value {
846            AccumulatorValue::Integer(value) => Self::Integer(value),
847            AccumulatorValue::IntegerTuple(value1, value2) => Self::IntegerTuple(value1, value2),
848            AccumulatorValue::EventDigest(digests) => Self::EventDigest(digests),
849        }
850    }
851}
852
853/// The response from processing a transaction or a certified transaction
854#[serde_as]
855#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize, JsonSchema)]
856#[serde(rename = "TransactionBlockEffectsV1", rename_all = "camelCase")]
857pub struct SuiTransactionBlockEffectsV1 {
858    /// The status of the execution
859    pub status: SuiExecutionStatus,
860    /// The epoch when this transaction was executed.
861    #[schemars(with = "BigInt<u64>")]
862    #[serde_as(as = "BigInt<u64>")]
863    pub executed_epoch: EpochId,
864    pub gas_used: GasCostSummary,
865    /// The version that every modified (mutated or deleted) object had before it was modified by
866    /// this transaction.
867    #[serde(default, skip_serializing_if = "Vec::is_empty")]
868    pub modified_at_versions: Vec<SuiTransactionBlockEffectsModifiedAtVersions>,
869    /// The object references of the shared objects used in this transaction. Empty if no shared objects were used.
870    #[serde(default, skip_serializing_if = "Vec::is_empty")]
871    pub shared_objects: Vec<SuiObjectRef>,
872    /// The transaction digest
873    pub transaction_digest: TransactionDigest,
874    /// ObjectRef and owner of new objects created.
875    #[serde(default, skip_serializing_if = "Vec::is_empty")]
876    pub created: Vec<OwnedObjectRef>,
877    /// ObjectRef and owner of mutated objects, including gas object.
878    #[serde(default, skip_serializing_if = "Vec::is_empty")]
879    pub mutated: Vec<OwnedObjectRef>,
880    /// ObjectRef and owner of objects that are unwrapped in this transaction.
881    /// Unwrapped objects are objects that were wrapped into other objects in the past,
882    /// and just got extracted out.
883    #[serde(default, skip_serializing_if = "Vec::is_empty")]
884    pub unwrapped: Vec<OwnedObjectRef>,
885    /// Object Refs of objects now deleted (the old refs).
886    #[serde(default, skip_serializing_if = "Vec::is_empty")]
887    pub deleted: Vec<SuiObjectRef>,
888    /// Object refs of objects previously wrapped in other objects but now deleted.
889    #[serde(default, skip_serializing_if = "Vec::is_empty")]
890    pub unwrapped_then_deleted: Vec<SuiObjectRef>,
891    /// Object refs of objects now wrapped in other objects.
892    #[serde(default, skip_serializing_if = "Vec::is_empty")]
893    pub wrapped: Vec<SuiObjectRef>,
894    #[serde(default, skip_serializing_if = "Vec::is_empty")]
895    pub accumulator_events: Vec<SuiAccumulatorEvent>,
896    /// The updated gas object reference. Have a dedicated field for convenient access.
897    /// It's also included in mutated.
898    pub gas_object: OwnedObjectRef,
899    /// The digest of the events emitted during execution,
900    /// can be None if the transaction does not emit any event.
901    #[serde(skip_serializing_if = "Option::is_none")]
902    pub events_digest: Option<TransactionEventsDigest>,
903    /// The set of transaction digests this transaction depends on.
904    #[serde(default, skip_serializing_if = "Vec::is_empty")]
905    pub dependencies: Vec<TransactionDigest>,
906    /// The abort error populated if the transaction failed with an abort code.
907    #[serde(default, skip_serializing_if = "Option::is_none")]
908    pub abort_error: Option<SuiMoveAbort>,
909}
910
911// TODO move additional error info here
912
913impl SuiTransactionBlockEffectsAPI for SuiTransactionBlockEffectsV1 {
914    fn status(&self) -> &SuiExecutionStatus {
915        &self.status
916    }
917    fn into_status(self) -> SuiExecutionStatus {
918        self.status
919    }
920    fn shared_objects(&self) -> &[SuiObjectRef] {
921        &self.shared_objects
922    }
923    fn created(&self) -> &[OwnedObjectRef] {
924        &self.created
925    }
926    fn mutated(&self) -> &[OwnedObjectRef] {
927        &self.mutated
928    }
929    fn unwrapped(&self) -> &[OwnedObjectRef] {
930        &self.unwrapped
931    }
932    fn deleted(&self) -> &[SuiObjectRef] {
933        &self.deleted
934    }
935    fn unwrapped_then_deleted(&self) -> &[SuiObjectRef] {
936        &self.unwrapped_then_deleted
937    }
938    fn wrapped(&self) -> &[SuiObjectRef] {
939        &self.wrapped
940    }
941    fn gas_object(&self) -> &OwnedObjectRef {
942        &self.gas_object
943    }
944    fn events_digest(&self) -> Option<&TransactionEventsDigest> {
945        self.events_digest.as_ref()
946    }
947    fn dependencies(&self) -> &[TransactionDigest] {
948        &self.dependencies
949    }
950
951    fn executed_epoch(&self) -> EpochId {
952        self.executed_epoch
953    }
954
955    fn transaction_digest(&self) -> &TransactionDigest {
956        &self.transaction_digest
957    }
958
959    fn gas_cost_summary(&self) -> &GasCostSummary {
960        &self.gas_used
961    }
962
963    fn mutated_excluding_gas(&self) -> Vec<OwnedObjectRef> {
964        self.mutated
965            .iter()
966            .filter(|o| *o != &self.gas_object)
967            .cloned()
968            .collect()
969    }
970
971    fn modified_at_versions(&self) -> Vec<(ObjectID, SequenceNumber)> {
972        self.modified_at_versions
973            .iter()
974            .map(|v| (v.object_id, v.sequence_number))
975            .collect::<Vec<_>>()
976    }
977
978    fn all_changed_objects(&self) -> Vec<(&OwnedObjectRef, WriteKind)> {
979        self.mutated
980            .iter()
981            .map(|owner_ref| (owner_ref, WriteKind::Mutate))
982            .chain(
983                self.created
984                    .iter()
985                    .map(|owner_ref| (owner_ref, WriteKind::Create)),
986            )
987            .chain(
988                self.unwrapped
989                    .iter()
990                    .map(|owner_ref| (owner_ref, WriteKind::Unwrap)),
991            )
992            .collect()
993    }
994
995    fn all_deleted_objects(&self) -> Vec<(&SuiObjectRef, DeleteKind)> {
996        self.deleted
997            .iter()
998            .map(|r| (r, DeleteKind::Normal))
999            .chain(
1000                self.unwrapped_then_deleted
1001                    .iter()
1002                    .map(|r| (r, DeleteKind::UnwrapThenDelete)),
1003            )
1004            .chain(self.wrapped.iter().map(|r| (r, DeleteKind::Wrap)))
1005            .collect()
1006    }
1007
1008    fn accumulator_events(&self) -> Vec<SuiAccumulatorEvent> {
1009        self.accumulator_events.clone()
1010    }
1011}
1012
1013impl SuiTransactionBlockEffects {
1014    pub fn new_for_testing(
1015        transaction_digest: TransactionDigest,
1016        status: SuiExecutionStatus,
1017    ) -> Self {
1018        Self::V1(SuiTransactionBlockEffectsV1 {
1019            transaction_digest,
1020            status,
1021            gas_object: OwnedObjectRef {
1022                owner: Owner::AddressOwner(SuiAddress::random_for_testing_only()),
1023                reference: sui_types::base_types::random_object_ref().into(),
1024            },
1025            executed_epoch: 0,
1026            modified_at_versions: vec![],
1027            gas_used: GasCostSummary::default(),
1028            shared_objects: vec![],
1029            created: vec![],
1030            mutated: vec![],
1031            unwrapped: vec![],
1032            deleted: vec![],
1033            unwrapped_then_deleted: vec![],
1034            wrapped: vec![],
1035            events_digest: None,
1036            dependencies: vec![],
1037            abort_error: None,
1038            accumulator_events: vec![],
1039        })
1040    }
1041}
1042
1043impl TryFrom<TransactionEffects> for SuiTransactionBlockEffects {
1044    type Error = SuiError;
1045
1046    fn try_from(effect: TransactionEffects) -> Result<Self, Self::Error> {
1047        Ok(SuiTransactionBlockEffects::V1(
1048            SuiTransactionBlockEffectsV1 {
1049                status: effect.status().clone().into(),
1050                executed_epoch: effect.executed_epoch(),
1051                modified_at_versions: effect
1052                    .modified_at_versions()
1053                    .into_iter()
1054                    .map(|(object_id, sequence_number)| {
1055                        SuiTransactionBlockEffectsModifiedAtVersions {
1056                            object_id,
1057                            sequence_number,
1058                        }
1059                    })
1060                    .collect(),
1061                gas_used: effect.gas_cost_summary().clone(),
1062                shared_objects: to_sui_object_ref(
1063                    effect
1064                        .input_consensus_objects()
1065                        .into_iter()
1066                        .map(|kind| {
1067                            #[allow(deprecated)]
1068                            kind.object_ref()
1069                        })
1070                        .collect(),
1071                ),
1072                transaction_digest: *effect.transaction_digest(),
1073                created: to_owned_ref(effect.created()),
1074                mutated: to_owned_ref(effect.mutated().to_vec()),
1075                unwrapped: to_owned_ref(effect.unwrapped().to_vec()),
1076                deleted: to_sui_object_ref(effect.deleted().to_vec()),
1077                unwrapped_then_deleted: to_sui_object_ref(effect.unwrapped_then_deleted().to_vec()),
1078                wrapped: to_sui_object_ref(effect.wrapped().to_vec()),
1079                gas_object: effect.gas_object().map_or_else(
1080                    || OwnedObjectRef {
1081                        owner: Owner::AddressOwner(SuiAddress::default()),
1082                        reference: SuiObjectRef {
1083                            object_id: ObjectID::ZERO,
1084                            version: SequenceNumber::default(),
1085                            digest: ObjectDigest::MIN,
1086                        },
1087                    },
1088                    |(obj_ref, owner)| OwnedObjectRef {
1089                        owner,
1090                        reference: obj_ref.into(),
1091                    },
1092                ),
1093                events_digest: effect.events_digest().copied(),
1094                dependencies: effect.dependencies().to_vec(),
1095                abort_error: effect
1096                    .move_abort()
1097                    .map(|(abort, code)| SuiMoveAbort::new(abort, code)),
1098                accumulator_events: effect
1099                    .accumulator_events()
1100                    .into_iter()
1101                    .map(SuiAccumulatorEvent::from)
1102                    .collect(),
1103            },
1104        ))
1105    }
1106}
1107
1108fn owned_objref_string(obj: &OwnedObjectRef) -> String {
1109    format!(
1110        " ┌──\n │ ID: {} \n │ Owner: {} \n │ Version: {} \n │ Digest: {}\n └──",
1111        obj.reference.object_id,
1112        obj.owner,
1113        u64::from(obj.reference.version),
1114        obj.reference.digest
1115    )
1116}
1117
1118fn objref_string(obj: &SuiObjectRef) -> String {
1119    format!(
1120        " ┌──\n │ ID: {} \n │ Version: {} \n │ Digest: {}\n └──",
1121        obj.object_id,
1122        u64::from(obj.version),
1123        obj.digest
1124    )
1125}
1126
1127impl Display for SuiTransactionBlockEffects {
1128    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1129        let mut builder = TableBuilder::default();
1130
1131        builder.push_record(vec![format!("Digest: {}", self.transaction_digest())]);
1132        builder.push_record(vec![format!("Status: {:?}", self.status())]);
1133        builder.push_record(vec![format!("Executed Epoch: {}", self.executed_epoch())]);
1134
1135        if !self.created().is_empty() {
1136            builder.push_record(vec![format!("\nCreated Objects: ")]);
1137
1138            for oref in self.created() {
1139                builder.push_record(vec![owned_objref_string(oref)]);
1140            }
1141        }
1142
1143        if !self.mutated().is_empty() {
1144            builder.push_record(vec![format!("Mutated Objects: ")]);
1145            for oref in self.mutated() {
1146                builder.push_record(vec![owned_objref_string(oref)]);
1147            }
1148        }
1149
1150        if !self.shared_objects().is_empty() {
1151            builder.push_record(vec![format!("Shared Objects: ")]);
1152            for oref in self.shared_objects() {
1153                builder.push_record(vec![objref_string(oref)]);
1154            }
1155        }
1156
1157        if !self.deleted().is_empty() {
1158            builder.push_record(vec![format!("Deleted Objects: ")]);
1159
1160            for oref in self.deleted() {
1161                builder.push_record(vec![objref_string(oref)]);
1162            }
1163        }
1164
1165        if !self.wrapped().is_empty() {
1166            builder.push_record(vec![format!("Wrapped Objects: ")]);
1167
1168            for oref in self.wrapped() {
1169                builder.push_record(vec![objref_string(oref)]);
1170            }
1171        }
1172
1173        if !self.unwrapped().is_empty() {
1174            builder.push_record(vec![format!("Unwrapped Objects: ")]);
1175            for oref in self.unwrapped() {
1176                builder.push_record(vec![owned_objref_string(oref)]);
1177            }
1178        }
1179
1180        builder.push_record(vec![format!(
1181            "Gas Object: \n{}",
1182            owned_objref_string(self.gas_object())
1183        )]);
1184
1185        let gas_cost_summary = self.gas_cost_summary();
1186        builder.push_record(vec![format!(
1187            "Gas Cost Summary:\n   \
1188             Storage Cost: {} MIST\n   \
1189             Computation Cost: {} MIST\n   \
1190             Storage Rebate: {} MIST\n   \
1191             Non-refundable Storage Fee: {} MIST",
1192            gas_cost_summary.storage_cost,
1193            gas_cost_summary.computation_cost,
1194            gas_cost_summary.storage_rebate,
1195            gas_cost_summary.non_refundable_storage_fee,
1196        )]);
1197
1198        let dependencies = self.dependencies();
1199        if !dependencies.is_empty() {
1200            builder.push_record(vec![format!("\nTransaction Dependencies:")]);
1201            for dependency in dependencies {
1202                builder.push_record(vec![format!("   {}", dependency)]);
1203            }
1204        }
1205
1206        let mut table = builder.build();
1207        table.with(TablePanel::header("Transaction Effects"));
1208        table.with(TableStyle::rounded().horizontals([HorizontalLine::new(
1209            1,
1210            TableStyle::modern().get_horizontal(),
1211        )]));
1212        write!(f, "{}", table)
1213    }
1214}
1215
1216#[serde_as]
1217#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize, JsonSchema)]
1218#[serde(rename_all = "camelCase")]
1219pub struct DryRunTransactionBlockResponse {
1220    pub effects: SuiTransactionBlockEffects,
1221    pub events: SuiTransactionBlockEvents,
1222    pub object_changes: Vec<ObjectChange>,
1223    pub balance_changes: Vec<BalanceChange>,
1224    pub input: SuiTransactionBlockData,
1225    pub execution_error_source: Option<String>,
1226    // If an input object is congested, suggest a gas price to use.
1227    #[serde(default, skip_serializing_if = "Option::is_none")]
1228    #[schemars(with = "Option<BigInt<u64>>")]
1229    #[serde_as(as = "Option<BigInt<u64>>")]
1230    pub suggested_gas_price: Option<u64>,
1231}
1232
1233#[derive(Eq, PartialEq, Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
1234#[serde(rename = "TransactionBlockEvents", transparent)]
1235pub struct SuiTransactionBlockEvents {
1236    pub data: Vec<SuiEvent>,
1237}
1238
1239impl SuiTransactionBlockEvents {
1240    pub fn try_from(
1241        events: TransactionEvents,
1242        tx_digest: TransactionDigest,
1243        timestamp_ms: Option<u64>,
1244        resolver: &mut dyn LayoutResolver,
1245    ) -> SuiResult<Self> {
1246        Ok(Self {
1247            data: events
1248                .data
1249                .into_iter()
1250                .enumerate()
1251                .map(|(seq, event)| {
1252                    let layout = resolver.get_annotated_layout(&event.type_)?;
1253                    SuiEvent::try_from(event, tx_digest, seq as u64, timestamp_ms, layout)
1254                })
1255                .collect::<Result<_, _>>()?,
1256        })
1257    }
1258
1259    // TODO: this is only called from the indexer. Remove this once indexer moves to its own resolver.
1260    pub fn try_from_using_module_resolver(
1261        events: TransactionEvents,
1262        tx_digest: TransactionDigest,
1263        timestamp_ms: Option<u64>,
1264        resolver: &impl GetModule,
1265    ) -> SuiResult<Self> {
1266        Ok(Self {
1267            data: events
1268                .data
1269                .into_iter()
1270                .enumerate()
1271                .map(|(seq, event)| {
1272                    let layout = get_layout_from_struct_tag(event.type_.clone(), resolver)?;
1273                    SuiEvent::try_from(event, tx_digest, seq as u64, timestamp_ms, layout)
1274                })
1275                .collect::<Result<_, _>>()?,
1276        })
1277    }
1278}
1279
1280impl Display for SuiTransactionBlockEvents {
1281    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1282        if self.data.is_empty() {
1283            writeln!(f, "╭─────────────────────────────╮")?;
1284            writeln!(f, "│ No transaction block events │")?;
1285            writeln!(f, "╰─────────────────────────────╯")
1286        } else {
1287            let mut builder = TableBuilder::default();
1288
1289            for event in &self.data {
1290                builder.push_record(vec![format!("{}", event)]);
1291            }
1292
1293            let mut table = builder.build();
1294            table.with(TablePanel::header("Transaction Block Events"));
1295            table.with(TableStyle::rounded().horizontals([HorizontalLine::new(
1296                1,
1297                TableStyle::modern().get_horizontal(),
1298            )]));
1299            write!(f, "{}", table)
1300        }
1301    }
1302}
1303
1304// TODO: this file might not be the best place for this struct.
1305/// Additional rguments supplied to dev inspect beyond what is allowed in today's API.
1306#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema)]
1307#[serde(rename = "DevInspectArgs", rename_all = "camelCase")]
1308pub struct DevInspectArgs {
1309    /// The sponsor of the gas for the transaction, might be different from the sender.
1310    pub gas_sponsor: Option<SuiAddress>,
1311    /// The gas budget for the transaction.
1312    pub gas_budget: Option<BigInt<u64>>,
1313    /// The gas objects used to pay for the transaction.
1314    pub gas_objects: Option<Vec<ObjectRef>>,
1315    /// Whether to skip transaction checks for the transaction.
1316    pub skip_checks: Option<bool>,
1317    /// Whether to return the raw transaction data and effects.
1318    pub show_raw_txn_data_and_effects: Option<bool>,
1319}
1320
1321/// The response from processing a dev inspect transaction
1322#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1323#[serde(rename = "DevInspectResults", rename_all = "camelCase")]
1324pub struct DevInspectResults {
1325    /// Summary of effects that likely would be generated if the transaction is actually run.
1326    /// Note however, that not all dev-inspect transactions are actually usable as transactions so
1327    /// it might not be possible actually generate these effects from a normal transaction.
1328    pub effects: SuiTransactionBlockEffects,
1329    /// Events that likely would be generated if the transaction is actually run.
1330    pub events: SuiTransactionBlockEvents,
1331    /// Execution results (including return values) from executing the transactions
1332    #[serde(skip_serializing_if = "Option::is_none")]
1333    pub results: Option<Vec<SuiExecutionResult>>,
1334    /// Execution error from executing the transactions
1335    #[serde(skip_serializing_if = "Option::is_none")]
1336    pub error: Option<String>,
1337    /// The raw transaction data that was dev inspected.
1338    #[serde(skip_serializing_if = "Vec::is_empty", default)]
1339    pub raw_txn_data: Vec<u8>,
1340    /// The raw effects of the transaction that was dev inspected.
1341    #[serde(skip_serializing_if = "Vec::is_empty", default)]
1342    pub raw_effects: Vec<u8>,
1343}
1344
1345#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1346#[serde(rename = "SuiExecutionResult", rename_all = "camelCase")]
1347pub struct SuiExecutionResult {
1348    /// The value of any arguments that were mutably borrowed.
1349    /// Non-mut borrowed values are not included
1350    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1351    pub mutable_reference_outputs: Vec<(/* argument */ SuiArgument, Vec<u8>, SuiTypeTag)>,
1352    /// The return values from the transaction
1353    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1354    pub return_values: Vec<(Vec<u8>, SuiTypeTag)>,
1355}
1356
1357type ExecutionResult = (
1358    /*  mutable_reference_outputs */ Vec<(Argument, Vec<u8>, TypeTag)>,
1359    /*  return_values */ Vec<(Vec<u8>, TypeTag)>,
1360);
1361
1362impl DevInspectResults {
1363    pub fn new(
1364        effects: TransactionEffects,
1365        events: TransactionEvents,
1366        return_values: Result<Vec<ExecutionResult>, ExecutionError>,
1367        raw_txn_data: Vec<u8>,
1368        raw_effects: Vec<u8>,
1369        resolver: &mut dyn LayoutResolver,
1370    ) -> SuiResult<Self> {
1371        let tx_digest = *effects.transaction_digest();
1372        let mut error = None;
1373        let mut results = None;
1374        match return_values {
1375            Err(e) => error = Some(e.to_string()),
1376            Ok(srvs) => {
1377                results = Some(
1378                    srvs.into_iter()
1379                        .map(|srv| {
1380                            let (mutable_reference_outputs, return_values) = srv;
1381                            let mutable_reference_outputs = mutable_reference_outputs
1382                                .into_iter()
1383                                .map(|(a, bytes, tag)| (a.into(), bytes, SuiTypeTag::from(tag)))
1384                                .collect();
1385                            let return_values = return_values
1386                                .into_iter()
1387                                .map(|(bytes, tag)| (bytes, SuiTypeTag::from(tag)))
1388                                .collect();
1389                            SuiExecutionResult {
1390                                mutable_reference_outputs,
1391                                return_values,
1392                            }
1393                        })
1394                        .collect(),
1395                )
1396            }
1397        };
1398        Ok(Self {
1399            effects: effects.try_into()?,
1400            events: SuiTransactionBlockEvents::try_from(events, tx_digest, None, resolver)?,
1401            results,
1402            error,
1403            raw_txn_data,
1404            raw_effects,
1405        })
1406    }
1407}
1408
1409#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize, JsonSchema)]
1410pub enum SuiTransactionBlockBuilderMode {
1411    /// Regular Sui Transactions that are committed on chain
1412    Commit,
1413    /// Simulated transaction that allows calling any Move function with
1414    /// arbitrary values.
1415    DevInspect,
1416}
1417
1418#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize, JsonSchema)]
1419#[serde(rename = "ExecutionStatus", rename_all = "camelCase", tag = "status")]
1420pub enum SuiExecutionStatus {
1421    // Gas used in the success case.
1422    Success,
1423    // Gas used in the failed case, and the error.
1424    Failure { error: String },
1425}
1426
1427impl Display for SuiExecutionStatus {
1428    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1429        match self {
1430            Self::Success => write!(f, "success"),
1431            Self::Failure { error } => write!(f, "failure due to {error}"),
1432        }
1433    }
1434}
1435
1436impl SuiExecutionStatus {
1437    pub fn is_ok(&self) -> bool {
1438        matches!(self, SuiExecutionStatus::Success)
1439    }
1440    pub fn is_err(&self) -> bool {
1441        matches!(self, SuiExecutionStatus::Failure { .. })
1442    }
1443}
1444
1445impl From<ExecutionStatus> for SuiExecutionStatus {
1446    fn from(status: ExecutionStatus) -> Self {
1447        match status {
1448            ExecutionStatus::Success => Self::Success,
1449            ExecutionStatus::Failure(ExecutionFailure {
1450                error,
1451                command: None,
1452            }) => Self::Failure {
1453                error: format!("{error:?}"),
1454            },
1455            ExecutionStatus::Failure(ExecutionFailure {
1456                error,
1457                command: Some(idx),
1458            }) => Self::Failure {
1459                error: format!("{error:?} in command {idx}"),
1460            },
1461        }
1462    }
1463}
1464
1465fn to_sui_object_ref(refs: Vec<ObjectRef>) -> Vec<SuiObjectRef> {
1466    refs.into_iter().map(SuiObjectRef::from).collect()
1467}
1468
1469fn to_owned_ref(owned_refs: Vec<(ObjectRef, Owner)>) -> Vec<OwnedObjectRef> {
1470    owned_refs
1471        .into_iter()
1472        .map(|(oref, owner)| OwnedObjectRef {
1473            owner,
1474            reference: oref.into(),
1475        })
1476        .collect()
1477}
1478
1479#[serde_as]
1480#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, PartialEq, Eq)]
1481#[serde(rename = "GasData", rename_all = "camelCase")]
1482pub struct SuiGasData {
1483    pub payment: Vec<SuiObjectRef>,
1484    pub owner: SuiAddress,
1485    #[schemars(with = "BigInt<u64>")]
1486    #[serde_as(as = "BigInt<u64>")]
1487    pub price: u64,
1488    #[schemars(with = "BigInt<u64>")]
1489    #[serde_as(as = "BigInt<u64>")]
1490    pub budget: u64,
1491}
1492
1493impl Display for SuiGasData {
1494    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1495        writeln!(f, "Gas Owner: {}", self.owner)?;
1496        writeln!(f, "Gas Budget: {} MIST", self.budget)?;
1497        writeln!(f, "Gas Price: {} MIST", self.price)?;
1498        writeln!(f, "Gas Payment:")?;
1499        for payment in &self.payment {
1500            write!(f, "{} ", objref_string(payment))?;
1501        }
1502        writeln!(f)
1503    }
1504}
1505
1506#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, PartialEq, Eq)]
1507#[enum_dispatch(SuiTransactionBlockDataAPI)]
1508#[serde(
1509    rename = "TransactionBlockData",
1510    rename_all = "camelCase",
1511    tag = "messageVersion"
1512)]
1513pub enum SuiTransactionBlockData {
1514    V1(SuiTransactionBlockDataV1),
1515}
1516
1517#[enum_dispatch]
1518pub trait SuiTransactionBlockDataAPI {
1519    fn transaction(&self) -> &SuiTransactionBlockKind;
1520    fn sender(&self) -> &SuiAddress;
1521    fn gas_data(&self) -> &SuiGasData;
1522}
1523
1524#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, PartialEq, Eq)]
1525#[serde(rename = "TransactionBlockDataV1", rename_all = "camelCase")]
1526pub struct SuiTransactionBlockDataV1 {
1527    pub transaction: SuiTransactionBlockKind,
1528    pub sender: SuiAddress,
1529    pub gas_data: SuiGasData,
1530}
1531
1532impl SuiTransactionBlockDataAPI for SuiTransactionBlockDataV1 {
1533    fn transaction(&self) -> &SuiTransactionBlockKind {
1534        &self.transaction
1535    }
1536    fn sender(&self) -> &SuiAddress {
1537        &self.sender
1538    }
1539    fn gas_data(&self) -> &SuiGasData {
1540        &self.gas_data
1541    }
1542}
1543
1544impl SuiTransactionBlockData {
1545    pub fn move_calls(&self) -> Vec<&SuiProgrammableMoveCall> {
1546        match self {
1547            Self::V1(data) => match &data.transaction {
1548                SuiTransactionBlockKind::ProgrammableTransaction(pt) => pt
1549                    .commands
1550                    .iter()
1551                    .filter_map(|command| match command {
1552                        SuiCommand::MoveCall(c) => Some(&**c),
1553                        _ => None,
1554                    })
1555                    .collect(),
1556                _ => vec![],
1557            },
1558        }
1559    }
1560
1561    fn try_from_inner(
1562        data: TransactionData,
1563        transaction: SuiTransactionBlockKind,
1564    ) -> Result<Self, anyhow::Error> {
1565        let message_version = data.message_version();
1566        let sender = data.sender();
1567        let gas_data = SuiGasData {
1568            payment: data
1569                .gas()
1570                .iter()
1571                .map(|obj_ref| SuiObjectRef::from(*obj_ref))
1572                .collect(),
1573            owner: data.gas_owner(),
1574            price: data.gas_price(),
1575            budget: data.gas_budget(),
1576        };
1577
1578        match message_version {
1579            1 => Ok(SuiTransactionBlockData::V1(SuiTransactionBlockDataV1 {
1580                transaction,
1581                sender,
1582                gas_data,
1583            })),
1584            _ => Err(anyhow::anyhow!(
1585                "Support for TransactionData version {} not implemented",
1586                message_version
1587            )),
1588        }
1589    }
1590
1591    pub fn try_from_with_module_cache(
1592        data: TransactionData,
1593        module_cache: &impl GetModule,
1594    ) -> Result<Self, anyhow::Error> {
1595        let transaction = SuiTransactionBlockKind::try_from_with_module_cache(
1596            data.clone().into_kind(),
1597            module_cache,
1598        )?;
1599        Self::try_from_inner(data, transaction)
1600    }
1601
1602    pub async fn try_from_with_package_resolver(
1603        data: TransactionData,
1604        package_resolver: &Resolver<impl PackageStore>,
1605    ) -> Result<Self, anyhow::Error> {
1606        let transaction = SuiTransactionBlockKind::try_from_with_package_resolver(
1607            data.clone().into_kind(),
1608            package_resolver,
1609        )
1610        .await?;
1611        Self::try_from_inner(data, transaction)
1612    }
1613}
1614
1615impl Display for SuiTransactionBlockData {
1616    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1617        match self {
1618            Self::V1(data) => {
1619                writeln!(f, "Sender: {}", data.sender)?;
1620                writeln!(f, "{}", self.gas_data())?;
1621                writeln!(f, "{}", data.transaction)
1622            }
1623        }
1624    }
1625}
1626
1627#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, PartialEq, Eq)]
1628#[serde(rename = "TransactionBlock", rename_all = "camelCase")]
1629pub struct SuiTransactionBlock {
1630    pub data: SuiTransactionBlockData,
1631    pub tx_signatures: Vec<GenericSignature>,
1632}
1633
1634impl SuiTransactionBlock {
1635    pub fn try_from(
1636        data: SenderSignedData,
1637        module_cache: &impl GetModule,
1638    ) -> Result<Self, anyhow::Error> {
1639        Ok(Self {
1640            data: SuiTransactionBlockData::try_from_with_module_cache(
1641                data.intent_message().value.clone(),
1642                module_cache,
1643            )?,
1644            tx_signatures: data.tx_signatures().to_vec(),
1645        })
1646    }
1647
1648    // TODO: the SuiTransactionBlock `try_from` can be removed after cleaning up indexer v1, so are the related
1649    // `try_from` methods for nested structs like SuiTransactionBlockData etc.
1650    pub async fn try_from_with_package_resolver(
1651        data: SenderSignedData,
1652        package_resolver: &Resolver<impl PackageStore>,
1653    ) -> Result<Self, anyhow::Error> {
1654        Ok(Self {
1655            data: SuiTransactionBlockData::try_from_with_package_resolver(
1656                data.intent_message().value.clone(),
1657                package_resolver,
1658            )
1659            .await?,
1660            tx_signatures: data.tx_signatures().to_vec(),
1661        })
1662    }
1663}
1664
1665impl Display for SuiTransactionBlock {
1666    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1667        let mut builder = TableBuilder::default();
1668
1669        builder.push_record(vec![format!("{}", self.data)]);
1670        builder.push_record(vec![format!("Signatures:")]);
1671        for tx_sig in &self.tx_signatures {
1672            builder.push_record(vec![format!(
1673                "   {}\n",
1674                match tx_sig {
1675                    Signature(sig) => Base64::from_bytes(sig.signature_bytes()).encoded(),
1676                    _ => Base64::from_bytes(tx_sig.as_ref()).encoded(), // the signatures for multisig and zklogin are not suited to be parsed out. they should be interpreted as a whole
1677                }
1678            )]);
1679        }
1680
1681        let mut table = builder.build();
1682        table.with(TablePanel::header("Transaction Data"));
1683        table.with(TableStyle::rounded().horizontals([HorizontalLine::new(
1684            1,
1685            TableStyle::modern().get_horizontal(),
1686        )]));
1687        write!(f, "{}", table)
1688    }
1689}
1690
1691#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1692pub struct SuiGenesisTransaction {
1693    pub objects: Vec<ObjectID>,
1694}
1695
1696#[serde_as]
1697#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1698pub struct SuiConsensusCommitPrologue {
1699    #[schemars(with = "BigInt<u64>")]
1700    #[serde_as(as = "BigInt<u64>")]
1701    pub epoch: u64,
1702    #[schemars(with = "BigInt<u64>")]
1703    #[serde_as(as = "BigInt<u64>")]
1704    pub round: u64,
1705    #[schemars(with = "BigInt<u64>")]
1706    #[serde_as(as = "BigInt<u64>")]
1707    pub commit_timestamp_ms: u64,
1708}
1709
1710#[serde_as]
1711#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1712pub struct SuiConsensusCommitPrologueV2 {
1713    #[schemars(with = "BigInt<u64>")]
1714    #[serde_as(as = "BigInt<u64>")]
1715    pub epoch: u64,
1716    #[schemars(with = "BigInt<u64>")]
1717    #[serde_as(as = "BigInt<u64>")]
1718    pub round: u64,
1719    #[schemars(with = "BigInt<u64>")]
1720    #[serde_as(as = "BigInt<u64>")]
1721    pub commit_timestamp_ms: u64,
1722    pub consensus_commit_digest: ConsensusCommitDigest,
1723}
1724
1725#[serde_as]
1726#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1727pub struct SuiConsensusCommitPrologueV3 {
1728    #[schemars(with = "BigInt<u64>")]
1729    #[serde_as(as = "BigInt<u64>")]
1730    pub epoch: u64,
1731    #[schemars(with = "BigInt<u64>")]
1732    #[serde_as(as = "BigInt<u64>")]
1733    pub round: u64,
1734    #[schemars(with = "Option<BigInt<u64>>")]
1735    #[serde_as(as = "Option<BigInt<u64>>")]
1736    pub sub_dag_index: Option<u64>,
1737    #[schemars(with = "BigInt<u64>")]
1738    #[serde_as(as = "BigInt<u64>")]
1739    pub commit_timestamp_ms: u64,
1740    pub consensus_commit_digest: ConsensusCommitDigest,
1741    pub consensus_determined_version_assignments: ConsensusDeterminedVersionAssignments,
1742}
1743
1744#[serde_as]
1745#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1746pub struct SuiConsensusCommitPrologueV4 {
1747    #[schemars(with = "BigInt<u64>")]
1748    #[serde_as(as = "BigInt<u64>")]
1749    pub epoch: u64,
1750    #[schemars(with = "BigInt<u64>")]
1751    #[serde_as(as = "BigInt<u64>")]
1752    pub round: u64,
1753    #[schemars(with = "Option<BigInt<u64>>")]
1754    #[serde_as(as = "Option<BigInt<u64>>")]
1755    pub sub_dag_index: Option<u64>,
1756    #[schemars(with = "BigInt<u64>")]
1757    #[serde_as(as = "BigInt<u64>")]
1758    pub commit_timestamp_ms: u64,
1759    pub consensus_commit_digest: ConsensusCommitDigest,
1760    pub consensus_determined_version_assignments: ConsensusDeterminedVersionAssignments,
1761    pub additional_state_digest: AdditionalConsensusStateDigest,
1762}
1763
1764#[serde_as]
1765#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1766pub struct SuiAuthenticatorStateUpdate {
1767    #[schemars(with = "BigInt<u64>")]
1768    #[serde_as(as = "BigInt<u64>")]
1769    pub epoch: u64,
1770    #[schemars(with = "BigInt<u64>")]
1771    #[serde_as(as = "BigInt<u64>")]
1772    pub round: u64,
1773
1774    pub new_active_jwks: Vec<SuiActiveJwk>,
1775}
1776
1777#[serde_as]
1778#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1779pub struct SuiRandomnessStateUpdate {
1780    #[schemars(with = "BigInt<u64>")]
1781    #[serde_as(as = "BigInt<u64>")]
1782    pub epoch: u64,
1783
1784    #[schemars(with = "BigInt<u64>")]
1785    #[serde_as(as = "BigInt<u64>")]
1786    pub randomness_round: u64,
1787    pub random_bytes: Vec<u8>,
1788}
1789
1790#[serde_as]
1791#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1792pub struct SuiEndOfEpochTransaction {
1793    pub transactions: Vec<SuiEndOfEpochTransactionKind>,
1794}
1795
1796#[serde_as]
1797#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1798pub enum SuiEndOfEpochTransactionKind {
1799    ChangeEpoch(SuiChangeEpoch),
1800    AuthenticatorStateCreate,
1801    AuthenticatorStateExpire(SuiAuthenticatorStateExpire),
1802    RandomnessStateCreate,
1803    CoinDenyListStateCreate,
1804    BridgeStateCreate(CheckpointDigest),
1805    BridgeCommitteeUpdate(SequenceNumber),
1806    StoreExecutionTimeObservations,
1807    AccumulatorRootCreate,
1808    CoinRegistryCreate,
1809    DisplayRegistryCreate,
1810    AddressAliasStateCreate,
1811    WriteAccumulatorStorageCost,
1812    ForwardingAddressRegistryCreate,
1813}
1814
1815#[serde_as]
1816#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1817pub struct SuiAuthenticatorStateExpire {
1818    #[schemars(with = "BigInt<u64>")]
1819    #[serde_as(as = "BigInt<u64>")]
1820    pub min_epoch: u64,
1821}
1822
1823#[serde_as]
1824#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1825pub struct SuiActiveJwk {
1826    pub jwk_id: SuiJwkId,
1827    pub jwk: SuiJWK,
1828
1829    #[schemars(with = "BigInt<u64>")]
1830    #[serde_as(as = "BigInt<u64>")]
1831    pub epoch: u64,
1832}
1833
1834impl From<ActiveJwk> for SuiActiveJwk {
1835    fn from(active_jwk: ActiveJwk) -> Self {
1836        Self {
1837            jwk_id: SuiJwkId {
1838                iss: active_jwk.jwk_id.iss.clone(),
1839                kid: active_jwk.jwk_id.kid.clone(),
1840            },
1841            jwk: SuiJWK {
1842                kty: active_jwk.jwk.kty.clone(),
1843                e: active_jwk.jwk.e.clone(),
1844                n: active_jwk.jwk.n.clone(),
1845                alg: active_jwk.jwk.alg.clone(),
1846            },
1847            epoch: active_jwk.epoch,
1848        }
1849    }
1850}
1851
1852#[serde_as]
1853#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1854pub struct SuiJwkId {
1855    pub iss: String,
1856    pub kid: String,
1857}
1858
1859#[serde_as]
1860#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1861pub struct SuiJWK {
1862    pub kty: String,
1863    pub e: String,
1864    pub n: String,
1865    pub alg: String,
1866}
1867
1868#[serde_as]
1869#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, JsonSchema)]
1870#[serde(rename = "InputObjectKind")]
1871pub enum SuiInputObjectKind {
1872    // A Move package, must be immutable.
1873    MovePackage(ObjectID),
1874    // A Move object, either immutable, or owned mutable.
1875    ImmOrOwnedMoveObject(SuiObjectRef),
1876    // A Move object that's shared and mutable.
1877    SharedMoveObject {
1878        id: ObjectID,
1879        #[schemars(with = "AsSequenceNumber")]
1880        #[serde_as(as = "AsSequenceNumber")]
1881        initial_shared_version: SequenceNumber,
1882        #[serde(default = "default_shared_object_mutability")]
1883        mutable: bool,
1884    },
1885}
1886
1887/// A series of commands where the results of one command can be used in future
1888/// commands
1889#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1890pub struct SuiProgrammableTransactionBlock {
1891    /// Input objects or primitive values
1892    pub inputs: Vec<SuiCallArg>,
1893    #[serde(rename = "transactions")]
1894    /// The transactions to be executed sequentially. A failure in any transaction will
1895    /// result in the failure of the entire programmable transaction block.
1896    pub commands: Vec<SuiCommand>,
1897}
1898
1899impl Display for SuiProgrammableTransactionBlock {
1900    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1901        let Self { inputs, commands } = self;
1902        writeln!(f, "Inputs: {inputs:?}")?;
1903        writeln!(f, "Commands: [")?;
1904        for c in commands {
1905            writeln!(f, "  {c},")?;
1906        }
1907        writeln!(f, "]")
1908    }
1909}
1910
1911impl SuiProgrammableTransactionBlock {
1912    fn try_from_with_module_cache(
1913        value: ProgrammableTransaction,
1914        module_cache: &impl GetModule,
1915    ) -> Result<Self, anyhow::Error> {
1916        let ProgrammableTransaction { inputs, commands } = value;
1917        let input_types = Self::resolve_input_type(&inputs, &commands, module_cache);
1918        Ok(SuiProgrammableTransactionBlock {
1919            inputs: inputs
1920                .into_iter()
1921                .zip_debug_eq(input_types)
1922                .map(|(arg, layout)| SuiCallArg::try_from(arg, layout.as_ref()))
1923                .collect::<Result<_, _>>()?,
1924            commands: commands.into_iter().map(SuiCommand::from).collect(),
1925        })
1926    }
1927
1928    async fn try_from_with_package_resolver(
1929        value: ProgrammableTransaction,
1930        package_resolver: &Resolver<impl PackageStore>,
1931    ) -> Result<Self, anyhow::Error> {
1932        // If the resolver can't infer layouts (e.g. a MoveCall references a function the resolver
1933        // can't find), fall back to rendering every pure input as untyped bytes rather than
1934        // failing the whole conversion. Matches the legacy `sui-json-rpc` behavior and the
1935        // `sui-indexer-alt-graphql` behavior at `programmable/mod.rs`.
1936        let input_types = match package_resolver.pure_input_layouts(&value).await {
1937            Ok(layouts) => layouts,
1938            Err(_) => vec![None; value.inputs.len()],
1939        };
1940
1941        let ProgrammableTransaction { inputs, commands } = value;
1942        Ok(SuiProgrammableTransactionBlock {
1943            inputs: inputs
1944                .into_iter()
1945                .zip_debug_eq(input_types)
1946                .map(|(arg, layout)| SuiCallArg::try_from(arg, layout.as_ref()))
1947                .collect::<Result<_, _>>()?,
1948            commands: commands.into_iter().map(SuiCommand::from).collect(),
1949        })
1950    }
1951
1952    fn resolve_input_type(
1953        inputs: &[CallArg],
1954        commands: &[Command],
1955        module_cache: &impl GetModule,
1956    ) -> Vec<Option<MoveTypeLayout>> {
1957        let mut result_types = vec![None; inputs.len()];
1958        for command in commands.iter() {
1959            match command {
1960                Command::MoveCall(c) => {
1961                    let Ok(module) = Identifier::new(c.module.clone()) else {
1962                        return result_types;
1963                    };
1964
1965                    let Ok(function) = Identifier::new(c.function.clone()) else {
1966                        return result_types;
1967                    };
1968
1969                    let id = ModuleId::new(c.package.into(), module);
1970                    let Some(types) =
1971                        get_signature_types(id, function.as_ident_str(), module_cache)
1972                    else {
1973                        return result_types;
1974                    };
1975                    #[allow(clippy::disallowed_methods)]
1976                    // Intentional zip: types includes implicit TxContext params not in arguments
1977                    for (arg, type_) in c.arguments.iter().zip(types) {
1978                        if let (&Argument::Input(i), Some(type_)) = (arg, type_)
1979                            && let Some(x) = result_types.get_mut(i as usize)
1980                        {
1981                            x.replace(type_);
1982                        }
1983                    }
1984                }
1985                Command::SplitCoins(_, amounts) => {
1986                    for arg in amounts {
1987                        if let &Argument::Input(i) = arg
1988                            && let Some(x) = result_types.get_mut(i as usize)
1989                        {
1990                            x.replace(MoveTypeLayout::U64);
1991                        }
1992                    }
1993                }
1994                Command::TransferObjects(_, Argument::Input(i)) => {
1995                    if let Some(x) = result_types.get_mut((*i) as usize) {
1996                        x.replace(MoveTypeLayout::Address);
1997                    }
1998                }
1999                _ => {}
2000            }
2001        }
2002        result_types
2003    }
2004}
2005
2006fn get_signature_types(
2007    id: ModuleId,
2008    function: &IdentStr,
2009    module_cache: &impl GetModule,
2010) -> Option<Vec<Option<MoveTypeLayout>>> {
2011    use std::borrow::Borrow;
2012    if let Ok(Some(module)) = module_cache.get_module_by_id(&id) {
2013        let module: &CompiledModule = module.borrow();
2014        let func = module
2015            .function_handles
2016            .iter()
2017            .find(|f| module.identifier_at(f.name) == function)?;
2018        Some(
2019            module
2020                .signature_at(func.parameters)
2021                .0
2022                .iter()
2023                .map(|s| primitive_type(module, &[], s))
2024                .collect(),
2025        )
2026    } else {
2027        None
2028    }
2029}
2030
2031/// A single transaction in a programmable transaction block.
2032#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
2033#[serde(rename = "SuiTransaction")]
2034pub enum SuiCommand {
2035    /// A call to either an entry or a public Move function
2036    MoveCall(Box<SuiProgrammableMoveCall>),
2037    /// `(Vec<forall T:key+store. T>, address)`
2038    /// It sends n-objects to the specified address. These objects must have store
2039    /// (public transfer) and either the previous owner must be an address or the object must
2040    /// be newly created.
2041    TransferObjects(Vec<SuiArgument>, SuiArgument),
2042    /// `(&mut Coin<T>, Vec<u64>)` -> `Vec<Coin<T>>`
2043    /// It splits off some amounts into a new coins with those amounts
2044    SplitCoins(SuiArgument, Vec<SuiArgument>),
2045    /// `(&mut Coin<T>, Vec<Coin<T>>)`
2046    /// It merges n-coins into the first coin
2047    MergeCoins(SuiArgument, Vec<SuiArgument>),
2048    /// Publishes a Move package. It takes the package bytes and a list of the package's transitive
2049    /// dependencies to link against on-chain.
2050    Publish(Vec<ObjectID>),
2051    /// Upgrades a Move package
2052    Upgrade(Vec<ObjectID>, ObjectID, SuiArgument),
2053    /// `forall T: Vec<T> -> vector<T>`
2054    /// Given n-values of the same type, it constructs a vector. For non objects or an empty vector,
2055    /// the type tag must be specified.
2056    MakeMoveVec(Option<String>, Vec<SuiArgument>),
2057}
2058
2059impl Display for SuiCommand {
2060    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
2061        match self {
2062            Self::MoveCall(p) => {
2063                write!(f, "MoveCall({p})")
2064            }
2065            Self::MakeMoveVec(ty_opt, elems) => {
2066                write!(f, "MakeMoveVec(")?;
2067                if let Some(ty) = ty_opt {
2068                    write!(f, "Some{ty}")?;
2069                } else {
2070                    write!(f, "None")?;
2071                }
2072                write!(f, ",[")?;
2073                write_sep(f, elems, ",")?;
2074                write!(f, "])")
2075            }
2076            Self::TransferObjects(objs, addr) => {
2077                write!(f, "TransferObjects([")?;
2078                write_sep(f, objs, ",")?;
2079                write!(f, "],{addr})")
2080            }
2081            Self::SplitCoins(coin, amounts) => {
2082                write!(f, "SplitCoins({coin},")?;
2083                write_sep(f, amounts, ",")?;
2084                write!(f, ")")
2085            }
2086            Self::MergeCoins(target, coins) => {
2087                write!(f, "MergeCoins({target},")?;
2088                write_sep(f, coins, ",")?;
2089                write!(f, ")")
2090            }
2091            Self::Publish(deps) => {
2092                write!(f, "Publish(<modules>,")?;
2093                write_sep(f, deps, ",")?;
2094                write!(f, ")")
2095            }
2096            Self::Upgrade(deps, current_package_id, ticket) => {
2097                write!(f, "Upgrade(<modules>, {ticket},")?;
2098                write_sep(f, deps, ",")?;
2099                write!(f, ", {current_package_id}")?;
2100                write!(f, ")")
2101            }
2102        }
2103    }
2104}
2105
2106impl From<Command> for SuiCommand {
2107    fn from(value: Command) -> Self {
2108        match value {
2109            Command::MoveCall(m) => SuiCommand::MoveCall(Box::new((*m).into())),
2110            Command::TransferObjects(args, arg) => SuiCommand::TransferObjects(
2111                args.into_iter().map(SuiArgument::from).collect(),
2112                arg.into(),
2113            ),
2114            Command::SplitCoins(arg, args) => SuiCommand::SplitCoins(
2115                arg.into(),
2116                args.into_iter().map(SuiArgument::from).collect(),
2117            ),
2118            Command::MergeCoins(arg, args) => SuiCommand::MergeCoins(
2119                arg.into(),
2120                args.into_iter().map(SuiArgument::from).collect(),
2121            ),
2122            Command::Publish(_modules, dep_ids) => SuiCommand::Publish(dep_ids),
2123            Command::MakeMoveVec(tag_opt, args) => SuiCommand::MakeMoveVec(
2124                tag_opt.map(|tag| tag.to_string()),
2125                args.into_iter().map(SuiArgument::from).collect(),
2126            ),
2127            Command::Upgrade(_modules, dep_ids, current_package_id, ticket) => {
2128                SuiCommand::Upgrade(dep_ids, current_package_id, SuiArgument::from(ticket))
2129            }
2130        }
2131    }
2132}
2133
2134/// An argument to a transaction in a programmable transaction block
2135#[derive(Debug, Copy, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
2136pub enum SuiArgument {
2137    /// The gas coin. The gas coin can only be used by-ref, except for with
2138    /// `TransferObjects`, which can use it by-value.
2139    GasCoin,
2140    /// One of the input objects or primitive values (from
2141    /// `ProgrammableTransactionBlock` inputs)
2142    Input(u16),
2143    /// The result of another transaction (from `ProgrammableTransactionBlock` transactions)
2144    Result(u16),
2145    /// Like a `Result` but it accesses a nested result. Currently, the only usage
2146    /// of this is to access a value from a Move call with multiple return values.
2147    NestedResult(u16, u16),
2148}
2149
2150impl Display for SuiArgument {
2151    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
2152        match self {
2153            Self::GasCoin => write!(f, "GasCoin"),
2154            Self::Input(i) => write!(f, "Input({i})"),
2155            Self::Result(i) => write!(f, "Result({i})"),
2156            Self::NestedResult(i, j) => write!(f, "NestedResult({i},{j})"),
2157        }
2158    }
2159}
2160
2161impl From<Argument> for SuiArgument {
2162    fn from(value: Argument) -> Self {
2163        match value {
2164            Argument::GasCoin => Self::GasCoin,
2165            Argument::Input(i) => Self::Input(i),
2166            Argument::Result(i) => Self::Result(i),
2167            Argument::NestedResult(i, j) => Self::NestedResult(i, j),
2168        }
2169    }
2170}
2171
2172/// The transaction for calling a Move function, either an entry function or a public
2173/// function (which cannot return references).
2174#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
2175pub struct SuiProgrammableMoveCall {
2176    /// The package containing the module and function.
2177    pub package: ObjectID,
2178    /// The specific module in the package containing the function.
2179    pub module: String,
2180    /// The function to be called.
2181    pub function: String,
2182    #[serde(default, skip_serializing_if = "Vec::is_empty")]
2183    /// The type arguments to the function.
2184    pub type_arguments: Vec<String>,
2185    #[serde(default, skip_serializing_if = "Vec::is_empty")]
2186    /// The arguments to the function.
2187    pub arguments: Vec<SuiArgument>,
2188}
2189
2190fn write_sep<T: Display>(
2191    f: &mut Formatter<'_>,
2192    items: impl IntoIterator<Item = T>,
2193    sep: &str,
2194) -> std::fmt::Result {
2195    let mut xs = items.into_iter().peekable();
2196    while let Some(x) = xs.next() {
2197        write!(f, "{x}")?;
2198        if xs.peek().is_some() {
2199            write!(f, "{sep}")?;
2200        }
2201    }
2202    Ok(())
2203}
2204
2205impl Display for SuiProgrammableMoveCall {
2206    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
2207        let Self {
2208            package,
2209            module,
2210            function,
2211            type_arguments,
2212            arguments,
2213        } = self;
2214        write!(f, "{package}::{module}::{function}")?;
2215        if !type_arguments.is_empty() {
2216            write!(f, "<")?;
2217            write_sep(f, type_arguments, ",")?;
2218            write!(f, ">")?;
2219        }
2220        write!(f, "(")?;
2221        write_sep(f, arguments, ",")?;
2222        write!(f, ")")
2223    }
2224}
2225
2226impl From<ProgrammableMoveCall> for SuiProgrammableMoveCall {
2227    fn from(value: ProgrammableMoveCall) -> Self {
2228        let ProgrammableMoveCall {
2229            package,
2230            module,
2231            function,
2232            type_arguments,
2233            arguments,
2234        } = value;
2235        Self {
2236            package,
2237            module: module.to_string(),
2238            function: function.to_string(),
2239            type_arguments: type_arguments.into_iter().map(|t| t.to_string()).collect(),
2240            arguments: arguments.into_iter().map(SuiArgument::from).collect(),
2241        }
2242    }
2243}
2244
2245const fn default_shared_object_mutability() -> bool {
2246    true
2247}
2248
2249impl From<InputObjectKind> for SuiInputObjectKind {
2250    fn from(input: InputObjectKind) -> Self {
2251        match input {
2252            InputObjectKind::MovePackage(id) => Self::MovePackage(id),
2253            InputObjectKind::ImmOrOwnedMoveObject(oref) => Self::ImmOrOwnedMoveObject(oref.into()),
2254            InputObjectKind::SharedMoveObject {
2255                id,
2256                initial_shared_version,
2257                mutability,
2258            } => Self::SharedMoveObject {
2259                id,
2260                initial_shared_version,
2261                mutable: match mutability {
2262                    SharedObjectMutability::Mutable => true,
2263                    SharedObjectMutability::Immutable => false,
2264                    // TODO(address-balances): expose detailed mutability info
2265                    SharedObjectMutability::NonExclusiveWrite => false,
2266                },
2267            },
2268        }
2269    }
2270}
2271
2272#[derive(Debug, Serialize, Deserialize, JsonSchema, Clone, Eq, PartialEq)]
2273#[serde(rename = "TypeTag", rename_all = "camelCase")]
2274pub struct SuiTypeTag(String);
2275
2276impl SuiTypeTag {
2277    pub fn new(tag: String) -> Self {
2278        Self(tag)
2279    }
2280}
2281
2282impl TryInto<TypeTag> for SuiTypeTag {
2283    type Error = anyhow::Error;
2284    fn try_into(self) -> Result<TypeTag, Self::Error> {
2285        parse_sui_type_tag(&self.0)
2286    }
2287}
2288
2289impl From<TypeTag> for SuiTypeTag {
2290    fn from(tag: TypeTag) -> Self {
2291        Self(format!("{}", tag))
2292    }
2293}
2294
2295#[derive(Serialize, Deserialize, JsonSchema, Clone)]
2296#[serde(rename_all = "camelCase")]
2297pub enum RPCTransactionRequestParams {
2298    TransferObjectRequestParams(TransferObjectParams),
2299    MoveCallRequestParams(MoveCallParams),
2300}
2301
2302#[derive(Serialize, Deserialize, JsonSchema, Clone)]
2303#[serde(rename_all = "camelCase")]
2304pub struct TransferObjectParams {
2305    pub recipient: SuiAddress,
2306    pub object_id: ObjectID,
2307}
2308
2309#[derive(Serialize, Deserialize, JsonSchema, Clone)]
2310#[serde(rename_all = "camelCase")]
2311pub struct MoveCallParams {
2312    pub package_object_id: ObjectID,
2313    pub module: String,
2314    pub function: String,
2315    #[serde(default)]
2316    pub type_arguments: Vec<SuiTypeTag>,
2317    pub arguments: Vec<SuiJsonValue>,
2318}
2319
2320#[serde_as]
2321#[derive(Serialize, Deserialize, JsonSchema, Clone)]
2322#[serde(rename_all = "camelCase")]
2323pub struct TransactionBlockBytes {
2324    /// BCS serialized transaction data bytes without its type tag, as base-64 encoded string.
2325    pub tx_bytes: Base64,
2326    /// the gas objects to be used
2327    pub gas: Vec<SuiObjectRef>,
2328    /// objects to be used in this transaction
2329    pub input_objects: Vec<SuiInputObjectKind>,
2330}
2331
2332impl TransactionBlockBytes {
2333    pub fn from_data(data: TransactionData) -> Result<Self, anyhow::Error> {
2334        Ok(Self {
2335            tx_bytes: Base64::from_bytes(bcs::to_bytes(&data)?.as_slice()),
2336            gas: data
2337                .gas()
2338                .iter()
2339                .map(|obj_ref| SuiObjectRef::from(*obj_ref))
2340                .collect(),
2341            input_objects: data
2342                .input_objects()?
2343                .into_iter()
2344                .map(SuiInputObjectKind::from)
2345                .collect(),
2346        })
2347    }
2348
2349    pub fn to_data(self) -> Result<TransactionData, anyhow::Error> {
2350        bcs::from_bytes::<TransactionData>(&self.tx_bytes.to_vec().map_err(|e| anyhow::anyhow!(e))?)
2351            .map_err(|e| anyhow::anyhow!(e))
2352    }
2353}
2354
2355#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize, JsonSchema)]
2356#[serde(rename = "OwnedObjectRef")]
2357pub struct OwnedObjectRef {
2358    pub owner: Owner,
2359    pub reference: SuiObjectRef,
2360}
2361
2362impl OwnedObjectRef {
2363    pub fn object_id(&self) -> ObjectID {
2364        self.reference.object_id
2365    }
2366    pub fn version(&self) -> SequenceNumber {
2367        self.reference.version
2368    }
2369}
2370
2371#[derive(Eq, PartialEq, Debug, Clone, Serialize, Deserialize, JsonSchema)]
2372#[serde(tag = "type", rename_all = "camelCase")]
2373pub enum SuiCallArg {
2374    // Needs to become an Object Ref or Object ID, depending on object type
2375    Object(SuiObjectArg),
2376    // pure value, bcs encoded
2377    Pure(SuiPureValue),
2378    // Reservation to withdraw balance. This will be converted into a Withdrawal struct and passed into Move.
2379    // It is allowed to have multiple withdraw arguments even for the same balance type.
2380    FundsWithdrawal(SuiFundsWithdrawalArg),
2381}
2382
2383impl SuiCallArg {
2384    pub fn try_from(
2385        value: CallArg,
2386        layout: Option<&MoveTypeLayout>,
2387    ) -> Result<Self, anyhow::Error> {
2388        Ok(match value {
2389            CallArg::Pure(p) => SuiCallArg::Pure(SuiPureValue {
2390                value_type: layout.map(|l| l.into()),
2391                value: SuiJsonValue::from_bcs_bytes(layout, &p)?,
2392            }),
2393            CallArg::Object(ObjectArg::ImmOrOwnedObject((id, version, digest))) => {
2394                SuiCallArg::Object(SuiObjectArg::ImmOrOwnedObject {
2395                    object_id: id,
2396                    version,
2397                    digest,
2398                })
2399            }
2400            // TODO(address-balances): Expose the full mutability enum
2401            CallArg::Object(ObjectArg::SharedObject {
2402                id,
2403                initial_shared_version,
2404                mutability,
2405            }) => SuiCallArg::Object(SuiObjectArg::SharedObject {
2406                object_id: id,
2407                initial_shared_version,
2408                mutable: mutability.is_exclusive(),
2409            }),
2410            CallArg::Object(ObjectArg::Receiving((object_id, version, digest))) => {
2411                SuiCallArg::Object(SuiObjectArg::Receiving {
2412                    object_id,
2413                    version,
2414                    digest,
2415                })
2416            }
2417            CallArg::FundsWithdrawal(arg) => SuiCallArg::FundsWithdrawal(SuiFundsWithdrawalArg {
2418                reservation: match arg.reservation {
2419                    Reservation::MaxAmountU64(amount) => SuiReservation::MaxAmountU64(amount),
2420                },
2421                type_arg: match arg.type_arg {
2422                    WithdrawalTypeArg::Balance(type_input) => {
2423                        SuiWithdrawalTypeArg::Balance(type_input.into())
2424                    }
2425                },
2426                withdraw_from: match arg.withdraw_from {
2427                    WithdrawFrom::Sender => SuiWithdrawFrom::Sender,
2428                    WithdrawFrom::Sponsor => SuiWithdrawFrom::Sponsor,
2429                },
2430            }),
2431        })
2432    }
2433
2434    pub fn pure(&self) -> Option<&SuiJsonValue> {
2435        match self {
2436            SuiCallArg::Pure(v) => Some(&v.value),
2437            _ => None,
2438        }
2439    }
2440
2441    pub fn object(&self) -> Option<&ObjectID> {
2442        match self {
2443            SuiCallArg::Object(SuiObjectArg::SharedObject { object_id, .. })
2444            | SuiCallArg::Object(SuiObjectArg::ImmOrOwnedObject { object_id, .. })
2445            | SuiCallArg::Object(SuiObjectArg::Receiving { object_id, .. }) => Some(object_id),
2446            _ => None,
2447        }
2448    }
2449}
2450
2451#[serde_as]
2452#[derive(Eq, PartialEq, Debug, Clone, Serialize, Deserialize, JsonSchema)]
2453#[serde(rename_all = "camelCase")]
2454pub struct SuiPureValue {
2455    #[schemars(with = "Option<String>")]
2456    #[serde_as(as = "Option<AsSuiTypeTag>")]
2457    value_type: Option<TypeTag>,
2458    value: SuiJsonValue,
2459}
2460
2461impl SuiPureValue {
2462    pub fn value(&self) -> SuiJsonValue {
2463        self.value.clone()
2464    }
2465
2466    pub fn value_type(&self) -> Option<TypeTag> {
2467        self.value_type.clone()
2468    }
2469}
2470
2471#[serde_as]
2472#[derive(Eq, PartialEq, Debug, Clone, Serialize, Deserialize, JsonSchema)]
2473#[serde(tag = "objectType", rename_all = "camelCase")]
2474pub enum SuiObjectArg {
2475    // A Move object, either immutable, or owned mutable.
2476    #[serde(rename_all = "camelCase")]
2477    ImmOrOwnedObject {
2478        object_id: ObjectID,
2479        #[schemars(with = "AsSequenceNumber")]
2480        #[serde_as(as = "AsSequenceNumber")]
2481        version: SequenceNumber,
2482        digest: ObjectDigest,
2483    },
2484    // A Move object that's shared.
2485    // SharedObject::mutable controls whether caller asks for a mutable reference to shared object.
2486    #[serde(rename_all = "camelCase")]
2487    SharedObject {
2488        object_id: ObjectID,
2489        #[schemars(with = "AsSequenceNumber")]
2490        #[serde_as(as = "AsSequenceNumber")]
2491        initial_shared_version: SequenceNumber,
2492        mutable: bool,
2493    },
2494    // A reference to a Move object that's going to be received in the transaction.
2495    #[serde(rename_all = "camelCase")]
2496    Receiving {
2497        object_id: ObjectID,
2498        #[schemars(with = "AsSequenceNumber")]
2499        #[serde_as(as = "AsSequenceNumber")]
2500        version: SequenceNumber,
2501        digest: ObjectDigest,
2502    },
2503}
2504
2505#[serde_as]
2506#[derive(Eq, PartialEq, Debug, Clone, Serialize, Deserialize, JsonSchema)]
2507#[serde(rename_all = "camelCase")]
2508pub enum SuiReservation {
2509    MaxAmountU64(
2510        #[schemars(with = "BigInt<u64>")]
2511        #[serde_as(as = "BigInt<u64>")]
2512        u64,
2513    ),
2514}
2515
2516#[derive(Eq, PartialEq, Debug, Clone, Serialize, Deserialize, JsonSchema)]
2517#[serde(rename_all = "camelCase")]
2518pub enum SuiWithdrawalTypeArg {
2519    Balance(SuiTypeTag),
2520}
2521
2522#[derive(Eq, PartialEq, Debug, Clone, Serialize, Deserialize, JsonSchema)]
2523#[serde(rename_all = "camelCase")]
2524pub enum SuiWithdrawFrom {
2525    Sender,
2526    Sponsor,
2527}
2528
2529#[derive(Eq, PartialEq, Debug, Clone, Serialize, Deserialize, JsonSchema)]
2530#[serde(rename_all = "camelCase")]
2531pub struct SuiFundsWithdrawalArg {
2532    pub reservation: SuiReservation,
2533    pub type_arg: SuiWithdrawalTypeArg,
2534    pub withdraw_from: SuiWithdrawFrom,
2535}
2536
2537#[derive(Clone)]
2538pub struct EffectsWithInput {
2539    pub effects: SuiTransactionBlockEffects,
2540    pub input: TransactionData,
2541}
2542
2543impl From<EffectsWithInput> for SuiTransactionBlockEffects {
2544    fn from(e: EffectsWithInput) -> Self {
2545        e.effects
2546    }
2547}
2548
2549#[serde_as]
2550#[derive(Clone, Debug, JsonSchema, Serialize, Deserialize)]
2551pub enum TransactionFilter {
2552    /// CURRENTLY NOT SUPPORTED. Query by checkpoint.
2553    Checkpoint(
2554        #[schemars(with = "BigInt<u64>")]
2555        #[serde_as(as = "Readable<BigInt<u64>, _>")]
2556        CheckpointSequenceNumber,
2557    ),
2558    /// Query by move function.
2559    MoveFunction {
2560        package: ObjectID,
2561        module: Option<String>,
2562        function: Option<String>,
2563    },
2564    /// Query by input object.
2565    InputObject(ObjectID),
2566    /// Query by changed object, including created, mutated and unwrapped objects.
2567    ChangedObject(ObjectID),
2568    /// Query for transactions that touch this object.
2569    AffectedObject(ObjectID),
2570    /// Query by sender address.
2571    FromAddress(SuiAddress),
2572    /// Query by recipient address.
2573    ToAddress(SuiAddress),
2574    /// Query by sender and recipient address.
2575    FromAndToAddress { from: SuiAddress, to: SuiAddress },
2576    /// CURRENTLY NOT SUPPORTED. Query txs that have a given address as sender or recipient.
2577    FromOrToAddress { addr: SuiAddress },
2578    /// Query by transaction kind
2579    TransactionKind(String),
2580    /// Query transactions of any given kind in the input.
2581    TransactionKindIn(Vec<String>),
2582}
2583
2584impl Filter<EffectsWithInput> for TransactionFilter {
2585    fn matches(&self, item: &EffectsWithInput) -> bool {
2586        let _scope = monitored_scope("TransactionFilter::matches");
2587        match self {
2588            TransactionFilter::InputObject(o) => {
2589                let Ok(input_objects) = item.input.input_objects() else {
2590                    return false;
2591                };
2592                input_objects.iter().any(|object| object.object_id() == *o)
2593            }
2594            TransactionFilter::ChangedObject(o) => item
2595                .effects
2596                .mutated()
2597                .iter()
2598                .any(|oref: &OwnedObjectRef| &oref.reference.object_id == o),
2599            TransactionFilter::AffectedObject(o) => item
2600                .effects
2601                .created()
2602                .iter()
2603                .chain(item.effects.mutated().iter())
2604                .chain(item.effects.unwrapped().iter())
2605                .map(|oref: &OwnedObjectRef| &oref.reference)
2606                .chain(item.effects.shared_objects().iter())
2607                .chain(item.effects.deleted().iter())
2608                .chain(item.effects.unwrapped_then_deleted().iter())
2609                .chain(item.effects.wrapped().iter())
2610                .any(|oref| &oref.object_id == o),
2611            TransactionFilter::FromAddress(a) => &item.input.sender() == a,
2612            TransactionFilter::ToAddress(a) => {
2613                let mutated: &[OwnedObjectRef] = item.effects.mutated();
2614                mutated.iter().chain(item.effects.unwrapped().iter()).any(|oref: &OwnedObjectRef| {
2615                    matches!(oref.owner, Owner::AddressOwner(owner) if owner == *a)
2616                })
2617            }
2618            TransactionFilter::FromAndToAddress { from, to } => {
2619                Self::FromAddress(*from).matches(item) && Self::ToAddress(*to).matches(item)
2620            }
2621            TransactionFilter::MoveFunction {
2622                package,
2623                module,
2624                function,
2625            } => item
2626                .input
2627                .move_calls()
2628                .into_iter()
2629                .any(|(_cmd_idx, p, m, f)| {
2630                    p == package
2631                        && (module.is_none() || matches!(module,  Some(m2) if m2 == &m.to_string()))
2632                        && (function.is_none()
2633                            || matches!(function, Some(f2) if f2 == &f.to_string()))
2634                }),
2635            TransactionFilter::TransactionKind(kind) => item.input.kind().to_string() == *kind,
2636            TransactionFilter::TransactionKindIn(kinds) => {
2637                kinds.contains(&item.input.kind().to_string())
2638            }
2639            // these filters are not supported, rpc will reject these filters on subscription
2640            TransactionFilter::Checkpoint(_) => false,
2641            TransactionFilter::FromOrToAddress { addr: _ } => false,
2642        }
2643    }
2644}
2645
2646#[cfg(test)]
2647mod tests {
2648    use std::sync::Arc;
2649
2650    use async_trait::async_trait;
2651    use move_core_types::account_address::AccountAddress;
2652    use move_core_types::ident_str;
2653    use sui_package_resolver::Package;
2654    use sui_package_resolver::error::Error as PackageResolverError;
2655    use sui_types::programmable_transaction_builder::ProgrammableTransactionBuilder;
2656
2657    use super::*;
2658
2659    struct EmptyPackageStore;
2660
2661    #[async_trait]
2662    impl PackageStore for EmptyPackageStore {
2663        async fn fetch(&self, id: AccountAddress) -> sui_package_resolver::Result<Arc<Package>> {
2664            Err(PackageResolverError::PackageNotFound(id))
2665        }
2666    }
2667
2668    #[tokio::test]
2669    async fn programmable_transaction_falls_back_when_layout_resolution_fails() {
2670        let mut builder = ProgrammableTransactionBuilder::new();
2671        let recipient = builder.pure(SuiAddress::ZERO).unwrap();
2672        builder.programmable_move_call(
2673            ObjectID::ZERO,
2674            ident_str!("pay").to_owned(),
2675            ident_str!("pay_all_sui").to_owned(),
2676            vec![],
2677            vec![recipient],
2678        );
2679
2680        let resolver = Resolver::new(EmptyPackageStore);
2681        let transaction = SuiProgrammableTransactionBlock::try_from_with_package_resolver(
2682            builder.finish(),
2683            &resolver,
2684        )
2685        .await
2686        .unwrap();
2687
2688        assert_eq!(transaction.commands.len(), 1);
2689        assert_eq!(transaction.inputs.len(), 1);
2690
2691        let SuiCallArg::Pure(input) = &transaction.inputs[0] else {
2692            panic!("expected pure input");
2693        };
2694        assert_eq!(input.value_type(), None);
2695
2696        // SuiAddress::ZERO BCS-encodes to 32 zero bytes. With no layout, those bytes should come
2697        // through unchanged as a JSON array of numbers.
2698        assert_eq!(
2699            input.value().to_json_value(),
2700            serde_json::json!(vec![0u8; 32]),
2701        );
2702    }
2703}