Skip to main content

iota_json_rpc_types/
iota_transaction.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use std::fmt::{self, Display, Formatter, Write};
6
7use enum_dispatch::enum_dispatch;
8use fastcrypto::encoding::{Base64, Encoding};
9use futures::{Stream, StreamExt, stream::FuturesOrdered};
10use iota_json::{IotaJsonValue, primitive_type};
11use iota_package_resolver::{CleverError, ErrorConstants, PackageStore, Resolver};
12use iota_sdk_types::{
13    Address, Argument, CanceledTransaction, ChangeEpoch, ChangeEpochV2, ChangeEpochV3,
14    ChangeEpochV4, Command, ConsensusCommitDigest, ConsensusDeterminedVersionAssignments,
15    EndOfEpochTransactionKind, ExecutionError as ExecutionFailureStatus, ExecutionStatus,
16    GasCostSummary, GenesisObject, Identifier, MoveCall, ObjectDigest, ObjectId, ObjectReference,
17    OwnedObjectReference, Owner, ProgrammableTransaction, SenderSignedTransaction,
18    SharedObjectReference, Transaction, TransactionDigest, TransactionEffects, TransactionEvents,
19    TransactionEventsDigest, TransactionKind, TransferObjects, TypeTag, UserSignature, Version,
20    VersionAssignment, WriteKind,
21};
22use iota_types::{
23    base_types::EpochId,
24    effects::TransactionEffectsAPI,
25    error::{ExecutionError, IotaError, IotaResult},
26    event::EventID,
27    iota_sdk_types_conversions::{identifier_sdk_to_core, type_tag_core_to_sdk},
28    iota_serde::BigInt,
29    layout_resolver::{LayoutResolver, get_layout_from_struct_tag},
30    messages_checkpoint::CheckpointSequenceNumber,
31    object::bounded_visitor::BoundedVisitor,
32    parse_iota_type_tag,
33    quorum_driver_types::ExecuteTransactionRequestType as NativeExecuteTransactionRequestType,
34    storage::DeleteKind,
35    transaction::{CallArg, InputObjectKind, TransactionAPI},
36};
37use move_binary_format::CompiledModule;
38use move_bytecode_utils::module_cache::GetModule;
39use move_core_types::{
40    account_address::AccountAddress, annotated_value::MoveTypeLayout, language_storage::ModuleId,
41};
42use schemars::JsonSchema;
43use serde::{Deserialize, Serialize};
44use serde_with::{DisplayFromStr, serde_as};
45use strum::{Display, EnumString};
46use tabled::{
47    builder::Builder as TableBuilder,
48    settings::{Panel as TablePanel, Style as TableStyle, style::HorizontalLine},
49};
50
51use crate::{
52    Filter, IotaEvent, IotaEventID, IotaMoveValue, ObjectRefSchema, Page,
53    balance_changes::BalanceChange,
54    iota_gas_cost_summary::IotaGasCostSummary,
55    iota_owner::OwnerSchema,
56    iota_primitives::{
57        Address as AddressSchema, Base58 as Base58Schema, Base64 as Base64Schema,
58        ObjectId as ObjectIdSchema, SequenceNumberString as SequenceNumberStringSchema,
59        SequenceNumberU64, TypeTag as TypeTagSchema, UserSignature as UserSignatureSchema,
60    },
61    object_changes::ObjectChange,
62};
63
64// similar to EpochId of iota-types but BigInt
65pub type IotaEpochId = BigInt<u64>;
66
67#[derive(Debug, Serialize, Deserialize, JsonSchema)]
68pub enum ExecuteTransactionRequestType {
69    WaitForEffectsCert,
70    WaitForLocalExecution,
71}
72
73impl From<NativeExecuteTransactionRequestType> for ExecuteTransactionRequestType {
74    fn from(request_type: NativeExecuteTransactionRequestType) -> Self {
75        match request_type {
76            NativeExecuteTransactionRequestType::WaitForEffectsCert => Self::WaitForEffectsCert,
77            NativeExecuteTransactionRequestType::WaitForLocalExecution => {
78                Self::WaitForLocalExecution
79            }
80        }
81    }
82}
83
84impl From<ExecuteTransactionRequestType> for NativeExecuteTransactionRequestType {
85    fn from(request_type: ExecuteTransactionRequestType) -> Self {
86        match request_type {
87            ExecuteTransactionRequestType::WaitForEffectsCert => Self::WaitForEffectsCert,
88            ExecuteTransactionRequestType::WaitForLocalExecution => Self::WaitForLocalExecution,
89        }
90    }
91}
92
93#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, Default)]
94#[serde(
95    rename_all = "camelCase",
96    rename = "TransactionBlockResponseQuery",
97    default
98)]
99pub struct IotaTransactionBlockResponseQuery {
100    /// If None, no filter will be applied
101    pub filter: Option<TransactionFilter>,
102    /// config which fields to include in the response, by default only digest
103    /// is included
104    pub options: Option<IotaTransactionBlockResponseOptions>,
105}
106
107impl IotaTransactionBlockResponseQuery {
108    pub fn new(
109        filter: Option<TransactionFilter>,
110        options: Option<IotaTransactionBlockResponseOptions>,
111    ) -> Self {
112        Self { filter, options }
113    }
114
115    pub fn new_with_filter(filter: TransactionFilter) -> Self {
116        Self {
117            filter: Some(filter),
118            options: None,
119        }
120    }
121}
122
123#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, Default)]
124#[serde(
125    rename_all = "camelCase",
126    rename = "TransactionBlockResponseQuery",
127    default
128)]
129pub struct IotaTransactionBlockResponseQueryV2 {
130    /// If None, no filter will be applied
131    pub filter: Option<TransactionFilterV2>,
132    /// config which fields to include in the response, by default only digest
133    /// is included
134    pub options: Option<IotaTransactionBlockResponseOptions>,
135}
136
137impl IotaTransactionBlockResponseQueryV2 {
138    pub fn new(
139        filter: Option<TransactionFilterV2>,
140        options: Option<IotaTransactionBlockResponseOptions>,
141    ) -> Self {
142        Self { filter, options }
143    }
144
145    pub fn new_with_filter(filter: TransactionFilterV2) -> Self {
146        Self {
147            filter: Some(filter),
148            options: None,
149        }
150    }
151}
152
153pub type TransactionBlocksPage = Page<IotaTransactionBlockResponse, TransactionDigest>;
154
155#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, Eq, PartialEq, Default)]
156#[serde(
157    rename_all = "camelCase",
158    rename = "TransactionBlockResponseOptions",
159    default
160)]
161pub struct IotaTransactionBlockResponseOptions {
162    /// Whether to show transaction input data. Default to be False
163    pub show_input: bool,
164    /// Whether to show bcs-encoded transaction input data
165    pub show_raw_input: bool,
166    /// Whether to show transaction effects. Default to be False
167    pub show_effects: bool,
168    /// Whether to show transaction events. Default to be False
169    pub show_events: bool,
170    /// Whether to show object_changes. Default to be False
171    pub show_object_changes: bool,
172    /// Whether to show balance_changes. Default to be False
173    pub show_balance_changes: bool,
174    /// Whether to show raw transaction effects. Default to be False
175    pub show_raw_effects: bool,
176}
177
178impl IotaTransactionBlockResponseOptions {
179    pub fn new() -> Self {
180        Self::default()
181    }
182
183    pub fn full_content() -> Self {
184        Self {
185            show_effects: true,
186            show_input: true,
187            show_raw_input: true,
188            show_events: true,
189            show_object_changes: true,
190            show_balance_changes: true,
191            // This field is added for graphql execution. We keep it false here
192            // so current users of `full_content` will not get raw effects unexpectedly.
193            show_raw_effects: false,
194        }
195    }
196
197    pub fn with_input(mut self) -> Self {
198        self.show_input = true;
199        self
200    }
201
202    pub fn with_raw_input(mut self) -> Self {
203        self.show_raw_input = true;
204        self
205    }
206
207    pub fn with_effects(mut self) -> Self {
208        self.show_effects = true;
209        self
210    }
211
212    pub fn with_events(mut self) -> Self {
213        self.show_events = true;
214        self
215    }
216
217    pub fn with_balance_changes(mut self) -> Self {
218        self.show_balance_changes = true;
219        self
220    }
221
222    pub fn with_object_changes(mut self) -> Self {
223        self.show_object_changes = true;
224        self
225    }
226
227    pub fn with_raw_effects(mut self) -> Self {
228        self.show_raw_effects = true;
229        self
230    }
231
232    /// default to return `WaitForEffectsCert` unless some options require
233    /// local execution
234    pub fn default_execution_request_type(&self) -> NativeExecuteTransactionRequestType {
235        // if people want effects or events, they typically want to wait for local
236        // execution
237        if self.require_effects() {
238            NativeExecuteTransactionRequestType::WaitForLocalExecution
239        } else {
240            NativeExecuteTransactionRequestType::WaitForEffectsCert
241        }
242    }
243
244    pub fn require_input(&self) -> bool {
245        self.show_input || self.show_raw_input || self.show_object_changes
246    }
247
248    pub fn require_effects(&self) -> bool {
249        self.show_effects
250            || self.show_events
251            || self.show_balance_changes
252            || self.show_object_changes
253            || self.show_raw_effects
254    }
255
256    pub fn only_digest(&self) -> bool {
257        self == &Self::default()
258    }
259}
260
261#[serde_as]
262#[derive(Serialize, Deserialize, Debug, JsonSchema, Clone, Default)]
263#[serde(rename_all = "camelCase", rename = "TransactionBlockResponse")]
264pub struct IotaTransactionBlockResponse {
265    #[serde_as(as = "Base58Schema")]
266    #[schemars(with = "Base58Schema")]
267    pub digest: TransactionDigest,
268    /// Transaction input data
269    #[serde(skip_serializing_if = "Option::is_none")]
270    pub transaction: Option<IotaTransactionBlock>,
271    /// BCS encoded [SenderSignedTransaction] that includes input object
272    /// references returns empty array if `show_raw_transaction` is false
273    #[serde_as(as = "Base64")]
274    #[schemars(with = "Base64Schema")]
275    #[serde(skip_serializing_if = "Vec::is_empty", default)]
276    pub raw_transaction: Vec<u8>,
277    #[serde(skip_serializing_if = "Option::is_none")]
278    pub effects: Option<IotaTransactionBlockEffects>,
279    #[serde(skip_serializing_if = "Option::is_none")]
280    pub events: Option<IotaTransactionBlockEvents>,
281    #[serde(skip_serializing_if = "Option::is_none")]
282    pub object_changes: Option<Vec<ObjectChange>>,
283    #[serde(skip_serializing_if = "Option::is_none")]
284    pub balance_changes: Option<Vec<BalanceChange>>,
285    #[serde(default, skip_serializing_if = "Option::is_none")]
286    #[schemars(with = "Option<String>")]
287    #[serde_as(as = "Option<DisplayFromStr>")]
288    pub timestamp_ms: Option<u64>,
289    #[serde(default, skip_serializing_if = "Option::is_none")]
290    pub confirmed_local_execution: Option<bool>,
291    /// The checkpoint number when this transaction was included and hence
292    /// finalized. This is only returned in the read api, not in the
293    /// transaction execution api.
294    #[schemars(with = "Option<String>")]
295    #[serde_as(as = "Option<DisplayFromStr>")]
296    #[serde(skip_serializing_if = "Option::is_none")]
297    pub checkpoint: Option<CheckpointSequenceNumber>,
298    #[serde(skip_serializing_if = "Vec::is_empty", default)]
299    pub errors: Vec<String>,
300    #[serde(skip_serializing_if = "Vec::is_empty", default)]
301    pub raw_effects: Vec<u8>,
302}
303
304impl IotaTransactionBlockResponse {
305    pub fn new(digest: TransactionDigest) -> Self {
306        Self {
307            digest,
308            ..Default::default()
309        }
310    }
311
312    pub fn status_ok(&self) -> Option<bool> {
313        self.effects.as_ref().map(|e| e.status().is_ok())
314    }
315
316    /// Get mutated objects if any
317    pub fn mutated_objects(&self) -> impl Iterator<Item = ObjectReference> + '_ {
318        self.object_changes.iter().flat_map(|obj_changes| {
319            obj_changes
320                .iter()
321                .filter(|change| matches!(change, ObjectChange::Mutated { .. }))
322                .map(|change| change.object_ref())
323        })
324    }
325}
326
327/// We are specifically ignoring events for now until events become more stable.
328impl PartialEq for IotaTransactionBlockResponse {
329    fn eq(&self, other: &Self) -> bool {
330        self.transaction == other.transaction
331            && self.effects == other.effects
332            && self.timestamp_ms == other.timestamp_ms
333            && self.confirmed_local_execution == other.confirmed_local_execution
334            && self.checkpoint == other.checkpoint
335    }
336}
337
338impl Display for IotaTransactionBlockResponse {
339    fn fmt(&self, writer: &mut Formatter<'_>) -> fmt::Result {
340        writeln!(writer, "Transaction Digest: {}", self.digest)?;
341
342        if let Some(t) = &self.transaction {
343            writeln!(writer, "{t}")?;
344        }
345
346        if let Some(e) = &self.effects {
347            writeln!(writer, "{e}")?;
348        }
349
350        if let Some(e) = &self.events {
351            writeln!(writer, "{e}")?;
352        }
353
354        if let Some(object_changes) = &self.object_changes {
355            let mut builder = TableBuilder::default();
356            let (
357                mut created,
358                mut deleted,
359                mut mutated,
360                mut published,
361                mut transferred,
362                mut wrapped,
363                mut unwrapped,
364            ) = (vec![], vec![], vec![], vec![], vec![], vec![], vec![]);
365
366            for obj in object_changes {
367                match obj {
368                    ObjectChange::Created { .. } => created.push(obj),
369                    ObjectChange::Deleted { .. } => deleted.push(obj),
370                    ObjectChange::Mutated { .. } => mutated.push(obj),
371                    ObjectChange::Published { .. } => published.push(obj),
372                    ObjectChange::Transferred { .. } => transferred.push(obj),
373                    ObjectChange::Wrapped { .. } => wrapped.push(obj),
374                    ObjectChange::Unwrapped { .. } => unwrapped.push(obj),
375                };
376            }
377
378            write_obj_changes(created, "Created", &mut builder)?;
379            write_obj_changes(deleted, "Deleted", &mut builder)?;
380            write_obj_changes(mutated, "Mutated", &mut builder)?;
381            write_obj_changes(published, "Published", &mut builder)?;
382            write_obj_changes(transferred, "Transferred", &mut builder)?;
383            write_obj_changes(wrapped, "Wrapped", &mut builder)?;
384            write_obj_changes(unwrapped, "Unwrapped", &mut builder)?;
385
386            let mut table = builder.build();
387            table.with(TablePanel::header("Object Changes"));
388            table.with(TableStyle::rounded().horizontals([HorizontalLine::new(
389                1,
390                TableStyle::modern().get_horizontal(),
391            )]));
392            writeln!(writer, "{table}")?;
393        }
394
395        if let Some(balance_changes) = &self.balance_changes {
396            // Only build a table if the vector of balance changes is non-empty.
397            // Empty balance changes occur, for example, for system transactions
398            // like `ConsensusCommitPrologueV1`
399            if !balance_changes.is_empty() {
400                let mut builder = TableBuilder::default();
401                for balance in balance_changes {
402                    builder.push_record(vec![format!("{balance}")]);
403                }
404                let mut table = builder.build();
405                table.with(TablePanel::header("Balance Changes"));
406                table.with(TableStyle::rounded().horizontals([HorizontalLine::new(
407                    1,
408                    TableStyle::modern().get_horizontal(),
409                )]));
410                writeln!(writer, "{table}")?;
411            } else {
412                writeln!(writer, "╭────────────────────╮")?;
413                writeln!(writer, "│ No balance changes │")?;
414                writeln!(writer, "╰────────────────────╯")?;
415            }
416        }
417        Ok(())
418    }
419}
420
421fn write_obj_changes<T: Display>(
422    values: Vec<T>,
423    output_string: &str,
424    builder: &mut TableBuilder,
425) -> std::fmt::Result {
426    if !values.is_empty() {
427        builder.push_record(vec![format!("{output_string} Objects: ")]);
428        for obj in values {
429            builder.push_record(vec![format!("{obj}")]);
430        }
431    }
432    Ok(())
433}
434
435pub fn get_new_package_obj_from_response(
436    response: &IotaTransactionBlockResponse,
437) -> Option<ObjectReference> {
438    response.object_changes.as_ref().and_then(|changes| {
439        changes
440            .iter()
441            .find(|change| matches!(change, ObjectChange::Published { .. }))
442            .map(|change| change.object_ref())
443    })
444}
445
446pub fn get_new_package_upgrade_cap_from_response(
447    response: &IotaTransactionBlockResponse,
448) -> Option<ObjectReference> {
449    response.object_changes.as_ref().and_then(|changes| {
450        changes
451            .iter()
452            .find(|change| {
453                matches!(change, ObjectChange::Created {
454                    owner: Owner::Address(_),
455                    object_type,
456                    ..
457                } if object_type.is_upgrade_cap())
458            })
459            .map(|change| change.object_ref())
460    })
461}
462
463#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
464#[serde(rename = "TransactionBlockKind", tag = "kind")]
465pub enum IotaTransactionBlockKind {
466    /// A system transaction used for initializing the initial state of the
467    /// chain.
468    Genesis(IotaGenesisTransaction),
469    /// A system transaction marking the start of a series of transactions
470    /// scheduled as part of a checkpoint
471    ConsensusCommitPrologueV1(IotaConsensusCommitPrologueV1),
472    /// A series of transactions where the results of one transaction can be
473    /// used in future transactions
474    ProgrammableTransaction(IotaProgrammableTransactionBlock),
475    /// A transaction which updates global randomness state
476    RandomnessStateUpdate(IotaRandomnessStateUpdate),
477    /// A transaction which applies an add/remove delta to the on-chain
478    /// transaction deny rules
479    TransactionDenyRulesUpdate(IotaTransactionDenyRulesUpdate),
480    /// The transaction which occurs only at the end of the epoch
481    EndOfEpochTransaction(IotaEndOfEpochTransaction),
482    // .. more transaction types go here
483}
484
485impl Display for IotaTransactionBlockKind {
486    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
487        let mut writer = String::new();
488        match &self {
489            Self::Genesis(_) => {
490                writeln!(writer, "Transaction Kind: Genesis Transaction")?;
491            }
492            Self::ConsensusCommitPrologueV1(p) => {
493                writeln!(writer, "Transaction Kind: Consensus Commit Prologue V1")?;
494                writeln!(
495                    writer,
496                    "Epoch: {}, Round: {}, SubDagIndex: {:?}, Timestamp: {}, ConsensusCommitDigest: {}",
497                    p.epoch,
498                    p.round,
499                    p.sub_dag_index,
500                    p.commit_timestamp_ms,
501                    p.consensus_commit_digest
502                )?;
503            }
504            Self::ProgrammableTransaction(p) => {
505                write!(writer, "Transaction Kind: Programmable")?;
506                write!(writer, "{}", crate::displays::Pretty(p))?;
507            }
508            Self::RandomnessStateUpdate(_) => {
509                writeln!(writer, "Transaction Kind: Randomness State Update")?;
510            }
511            Self::TransactionDenyRulesUpdate(_) => {
512                writeln!(writer, "Transaction Kind: Transaction Deny Rules Update")?;
513            }
514            Self::EndOfEpochTransaction(_) => {
515                writeln!(writer, "Transaction Kind: End of Epoch Transaction")?;
516            }
517        }
518        write!(f, "{writer}")
519    }
520}
521
522impl IotaTransactionBlockKind {
523    fn try_from_inner(
524        tx: TransactionKind,
525        tx_digest: TransactionDigest,
526    ) -> Result<Self, anyhow::Error> {
527        match tx {
528            TransactionKind::Genesis(g) => Ok(Self::Genesis(IotaGenesisTransaction {
529                objects: g.objects.iter().map(GenesisObject::id).collect(),
530                events: g
531                    .events
532                    .into_iter()
533                    .enumerate()
534                    .map(|(seq, _event)| EventID::from((tx_digest, seq as u64)))
535                    .collect(),
536            })),
537            TransactionKind::ConsensusCommitPrologueV1(p) => Ok(Self::ConsensusCommitPrologueV1(
538                IotaConsensusCommitPrologueV1 {
539                    epoch: p.epoch,
540                    round: p.round,
541                    sub_dag_index: p.sub_dag_index,
542                    commit_timestamp_ms: p.commit_timestamp_ms,
543                    consensus_commit_digest: p.consensus_commit_digest,
544                    consensus_determined_version_assignments: p
545                        .consensus_determined_version_assignments
546                        .try_into()?,
547                },
548            )),
549            TransactionKind::Programmable(_) => {
550                // This case is handled separately by the callers
551                Err(anyhow::anyhow!(
552                    "ProgrammableTransaction must be handled by the caller, not try_from_inner"
553                ))
554            }
555            #[allow(deprecated)]
556            TransactionKind::AuthenticatorStateUpdateV1Deprecated => {
557                // Deprecated: Authenticator state (JWK) is deprecated and
558                // was never enabled. These transaction kinds are retained
559                // only for BCS enum variant compatibility.
560                Err(anyhow::anyhow!(
561                    "AuthenticatorStateUpdateV1 transactions are deprecated and were never created on IOTA"
562                ))
563            }
564            TransactionKind::RandomnessStateUpdate(update) => {
565                Ok(Self::RandomnessStateUpdate(IotaRandomnessStateUpdate {
566                    epoch: update.epoch,
567                    randomness_round: update.randomness_round.value(),
568                    random_bytes: update.random_bytes,
569                }))
570            }
571            TransactionKind::TransactionDenyRulesUpdate(update) => Ok(
572                Self::TransactionDenyRulesUpdate(IotaTransactionDenyRulesUpdate {
573                    epoch: update.epoch,
574                    round: update.round,
575                    added_addresses: update.added_addresses.into_iter().collect(),
576                    removed_addresses: update.removed_addresses.into_iter().collect(),
577                    added_objects: update.added_objects.into_iter().collect(),
578                    removed_objects: update.removed_objects.into_iter().collect(),
579                    added_packages: update.added_packages.into_iter().collect(),
580                    removed_packages: update.removed_packages.into_iter().collect(),
581                    package_publish_disabled: update.package_publish_disabled,
582                    package_upgrade_disabled: update.package_upgrade_disabled,
583                    shared_object_disabled: update.shared_object_disabled,
584                    user_transaction_disabled: update.user_transaction_disabled,
585                    receiving_objects_disabled: update.receiving_objects_disabled,
586                    move_authenticator_disabled: update.move_authenticator_disabled,
587                }),
588            ),
589            TransactionKind::EndOfEpoch(end_of_epoch_tx) => {
590                Ok(Self::EndOfEpochTransaction(IotaEndOfEpochTransaction {
591                    transactions: end_of_epoch_tx
592                        .into_iter()
593                        .map(|tx| {
594                            Ok(match tx {
595                                EndOfEpochTransactionKind::ChangeEpoch(e) => {
596                                    IotaEndOfEpochTransactionKind::ChangeEpoch(e.into())
597                                }
598                                EndOfEpochTransactionKind::ChangeEpochV2(e) => {
599                                    IotaEndOfEpochTransactionKind::ChangeEpochV2(e.into())
600                                }
601                                EndOfEpochTransactionKind::ChangeEpochV3(e) => {
602                                    IotaEndOfEpochTransactionKind::ChangeEpochV2(e.into())
603                                }
604                                EndOfEpochTransactionKind::ChangeEpochV4(e) => {
605                                    IotaEndOfEpochTransactionKind::ChangeEpochV2(e.into())
606                                }
607                                EndOfEpochTransactionKind::TransactionDenyRulesCreate => {
608                                    IotaEndOfEpochTransactionKind::TransactionDenyRulesCreate
609                                }
610                                _ => anyhow::bail!("unknown EndOfEpochTransactionKind variant"),
611                            })
612                        })
613                        .collect::<Result<_, anyhow::Error>>()?,
614                }))
615            }
616            _ => Err(anyhow::anyhow!("unknown TransactionKind variant")),
617        }
618    }
619
620    fn try_from_with_module_cache(
621        tx: TransactionKind,
622        module_cache: &impl GetModule,
623        tx_digest: TransactionDigest,
624    ) -> Result<Self, anyhow::Error> {
625        match tx {
626            TransactionKind::Programmable(p) => Ok(Self::ProgrammableTransaction(
627                IotaProgrammableTransactionBlock::try_from_with_module_cache(p, module_cache)?,
628            )),
629            tx => Self::try_from_inner(tx, tx_digest),
630        }
631    }
632
633    async fn try_from_with_package_resolver(
634        tx: TransactionKind,
635        package_resolver: &Resolver<impl PackageStore>,
636        tx_digest: TransactionDigest,
637    ) -> Result<Self, anyhow::Error> {
638        match tx {
639            TransactionKind::Programmable(p) => Ok(Self::ProgrammableTransaction(
640                IotaProgrammableTransactionBlock::try_from_with_package_resolver(
641                    p,
642                    package_resolver,
643                )
644                .await?,
645            )),
646            tx => Self::try_from_inner(tx, tx_digest),
647        }
648    }
649
650    pub fn transaction_count(&self) -> usize {
651        match self {
652            Self::ProgrammableTransaction(p) => p.commands.len(),
653            _ => 1,
654        }
655    }
656
657    pub fn name(&self) -> &'static str {
658        match self {
659            Self::Genesis(_) => "Genesis",
660            Self::ConsensusCommitPrologueV1(_) => "ConsensusCommitPrologueV1",
661            Self::ProgrammableTransaction(_) => "ProgrammableTransaction",
662            Self::RandomnessStateUpdate(_) => "RandomnessStateUpdate",
663            Self::TransactionDenyRulesUpdate(_) => "TransactionDenyRulesUpdate",
664            Self::EndOfEpochTransaction(_) => "EndOfEpochTransaction",
665        }
666    }
667}
668
669#[serde_as]
670#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
671pub struct IotaChangeEpoch {
672    #[schemars(with = "String")]
673    #[serde_as(as = "DisplayFromStr")]
674    pub epoch: EpochId,
675    #[schemars(with = "String")]
676    #[serde_as(as = "DisplayFromStr")]
677    pub storage_charge: u64,
678    #[schemars(with = "String")]
679    #[serde_as(as = "DisplayFromStr")]
680    pub computation_charge: u64,
681    #[schemars(with = "String")]
682    #[serde_as(as = "DisplayFromStr")]
683    pub storage_rebate: u64,
684    #[schemars(with = "String")]
685    #[serde_as(as = "DisplayFromStr")]
686    pub epoch_start_timestamp_ms: u64,
687}
688
689impl From<ChangeEpoch> for IotaChangeEpoch {
690    fn from(e: ChangeEpoch) -> Self {
691        Self {
692            epoch: e.epoch,
693            storage_charge: e.storage_charge,
694            computation_charge: e.computation_charge,
695            storage_rebate: e.storage_rebate,
696            epoch_start_timestamp_ms: e.epoch_start_timestamp_ms,
697        }
698    }
699}
700
701#[serde_as]
702#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
703pub struct IotaChangeEpochV2 {
704    #[schemars(with = "String")]
705    #[serde_as(as = "DisplayFromStr")]
706    pub epoch: EpochId,
707    #[schemars(with = "String")]
708    #[serde_as(as = "DisplayFromStr")]
709    pub storage_charge: u64,
710    #[schemars(with = "String")]
711    #[serde_as(as = "DisplayFromStr")]
712    pub computation_charge: u64,
713    #[schemars(with = "String")]
714    #[serde_as(as = "DisplayFromStr")]
715    pub computation_charge_burned: u64,
716    #[schemars(with = "String")]
717    #[serde_as(as = "DisplayFromStr")]
718    pub storage_rebate: u64,
719    #[schemars(with = "String")]
720    #[serde_as(as = "DisplayFromStr")]
721    pub epoch_start_timestamp_ms: u64,
722    #[schemars(with = "Option<Vec<String>>")]
723    #[serde_as(as = "Option<Vec<DisplayFromStr>>")]
724    #[serde(skip_serializing_if = "Option::is_none", default)]
725    pub eligible_active_validators: Option<Vec<u64>>,
726    #[schemars(with = "Option<Vec<String>>")]
727    #[serde_as(as = "Option<Vec<DisplayFromStr>>")]
728    #[serde(skip_serializing_if = "Option::is_none", default)]
729    pub scores: Option<Vec<u64>>,
730}
731
732impl From<ChangeEpochV2> for IotaChangeEpochV2 {
733    fn from(e: ChangeEpochV2) -> Self {
734        Self {
735            epoch: e.epoch,
736            storage_charge: e.storage_charge,
737            computation_charge: e.computation_charge,
738            computation_charge_burned: e.computation_charge_burned,
739            storage_rebate: e.storage_rebate,
740            epoch_start_timestamp_ms: e.epoch_start_timestamp_ms,
741            eligible_active_validators: None,
742            scores: None,
743        }
744    }
745}
746
747impl From<ChangeEpochV3> for IotaChangeEpochV2 {
748    fn from(e: ChangeEpochV3) -> Self {
749        Self {
750            epoch: e.epoch,
751            storage_charge: e.storage_charge,
752            computation_charge: e.computation_charge,
753            computation_charge_burned: e.computation_charge_burned,
754            storage_rebate: e.storage_rebate,
755            epoch_start_timestamp_ms: e.epoch_start_timestamp_ms,
756            eligible_active_validators: Some(e.eligible_active_validators),
757            scores: None,
758        }
759    }
760}
761
762impl From<ChangeEpochV4> for IotaChangeEpochV2 {
763    fn from(e: ChangeEpochV4) -> Self {
764        Self {
765            epoch: e.epoch,
766            storage_charge: e.storage_charge,
767            computation_charge: e.computation_charge,
768            computation_charge_burned: e.computation_charge_burned,
769            storage_rebate: e.storage_rebate,
770            epoch_start_timestamp_ms: e.epoch_start_timestamp_ms,
771            eligible_active_validators: Some(e.eligible_active_validators),
772            scores: Some(e.scores),
773        }
774    }
775}
776
777#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, PartialEq, Eq)]
778#[enum_dispatch(IotaTransactionBlockEffectsAPI)]
779#[serde(
780    rename = "TransactionBlockEffects",
781    rename_all = "camelCase",
782    tag = "messageVersion"
783)]
784pub enum IotaTransactionBlockEffects {
785    V1(IotaTransactionBlockEffectsV1),
786}
787
788#[enum_dispatch]
789pub trait IotaTransactionBlockEffectsAPI {
790    fn status(&self) -> &IotaExecutionStatus;
791    fn into_status(self) -> IotaExecutionStatus;
792    fn shared_objects(&self) -> &[ObjectReference];
793    fn created(&self) -> &[OwnedObjectRef];
794    fn mutated(&self) -> &[OwnedObjectRef];
795    fn unwrapped(&self) -> &[OwnedObjectRef];
796    fn deleted(&self) -> &[ObjectReference];
797    fn unwrapped_then_deleted(&self) -> &[ObjectReference];
798    fn wrapped(&self) -> &[ObjectReference];
799    fn gas_object(&self) -> &OwnedObjectRef;
800    fn events_digest(&self) -> Option<&TransactionEventsDigest>;
801    fn dependencies(&self) -> &[TransactionDigest];
802    fn executed_epoch(&self) -> EpochId;
803    fn transaction_digest(&self) -> &TransactionDigest;
804    fn gas_cost_summary(&self) -> &GasCostSummary;
805
806    /// Return an iterator of mutated objects, but excluding the gas object.
807    fn mutated_excluding_gas(&self) -> Vec<OwnedObjectRef>;
808    fn modified_at_versions(&self) -> Vec<(ObjectId, Version)>;
809    fn all_changed_objects(&self) -> Vec<(&OwnedObjectRef, WriteKind)>;
810    fn all_deleted_objects(&self) -> Vec<(&ObjectReference, DeleteKind)>;
811}
812
813#[serde_as]
814#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize, JsonSchema)]
815#[serde(
816    rename = "TransactionBlockEffectsModifiedAtVersions",
817    rename_all = "camelCase"
818)]
819pub struct IotaTransactionBlockEffectsModifiedAtVersions {
820    #[serde_as(as = "ObjectIdSchema")]
821    #[schemars(with = "ObjectIdSchema")]
822    object_id: ObjectId,
823    #[schemars(with = "SequenceNumberStringSchema")]
824    #[serde_as(as = "SequenceNumberStringSchema")]
825    sequence_number: Version,
826}
827
828/// The response from processing a transaction or a certified transaction
829#[serde_as]
830#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize, JsonSchema)]
831#[serde(rename = "TransactionBlockEffectsV1", rename_all = "camelCase")]
832pub struct IotaTransactionBlockEffectsV1 {
833    /// The status of the execution
834    pub status: IotaExecutionStatus,
835    /// The epoch when this transaction was executed.
836    #[schemars(with = "String")]
837    #[serde_as(as = "DisplayFromStr")]
838    pub executed_epoch: EpochId,
839    #[schemars(with = "IotaGasCostSummary")]
840    #[serde_as(as = "IotaGasCostSummary")]
841    pub gas_used: GasCostSummary,
842    /// The version that every modified (mutated or deleted) object had before
843    /// it was modified by this transaction.
844    #[serde(default, skip_serializing_if = "Vec::is_empty")]
845    pub modified_at_versions: Vec<IotaTransactionBlockEffectsModifiedAtVersions>,
846    /// The object references of the shared objects used in this transaction.
847    /// Empty if no shared objects were used.
848    #[serde(default, skip_serializing_if = "Vec::is_empty")]
849    #[schemars(with = "Vec<ObjectRefSchema>")]
850    #[serde_as(as = "Vec<ObjectRefSchema>")]
851    pub shared_objects: Vec<ObjectReference>,
852    /// The transaction digest
853    #[serde_as(as = "Base58Schema")]
854    #[schemars(with = "Base58Schema")]
855    pub transaction_digest: TransactionDigest,
856    /// ObjectRef and owner of new objects created.
857    #[serde(default, skip_serializing_if = "Vec::is_empty")]
858    pub created: Vec<OwnedObjectRef>,
859    /// ObjectRef and owner of mutated objects, including gas object.
860    #[serde(default, skip_serializing_if = "Vec::is_empty")]
861    pub mutated: Vec<OwnedObjectRef>,
862    /// ObjectRef and owner of objects that are unwrapped in this
863    /// transaction. Unwrapped objects are objects that were wrapped into
864    /// other objects in the past, and just got extracted out.
865    #[serde(default, skip_serializing_if = "Vec::is_empty")]
866    pub unwrapped: Vec<OwnedObjectRef>,
867    /// Object Refs of objects now deleted (the old refs).
868    #[serde(default, skip_serializing_if = "Vec::is_empty")]
869    #[schemars(with = "Vec<ObjectRefSchema>")]
870    #[serde_as(as = "Vec<ObjectRefSchema>")]
871    pub deleted: Vec<ObjectReference>,
872    /// Object refs of objects previously wrapped in other objects but now
873    /// deleted.
874    #[serde(default, skip_serializing_if = "Vec::is_empty")]
875    #[schemars(with = "Vec<ObjectRefSchema>")]
876    #[serde_as(as = "Vec<ObjectRefSchema>")]
877    pub unwrapped_then_deleted: Vec<ObjectReference>,
878    /// Object refs of objects now wrapped in other objects.
879    #[serde(default, skip_serializing_if = "Vec::is_empty")]
880    #[schemars(with = "Vec<ObjectRefSchema>")]
881    #[serde_as(as = "Vec<ObjectRefSchema>")]
882    pub wrapped: Vec<ObjectReference>,
883    /// The updated gas object reference. Have a dedicated field for convenient
884    /// access. It's also included in mutated.
885    pub gas_object: OwnedObjectRef,
886    /// The digest of the events emitted during execution,
887    /// can be None if the transaction does not emit any event.
888    #[serde(skip_serializing_if = "Option::is_none")]
889    #[serde_as(as = "Option<Base58Schema>")]
890    #[schemars(with = "Option<Base58Schema>")]
891    pub events_digest: Option<TransactionEventsDigest>,
892    /// The set of transaction digests this transaction depends on.
893    #[serde(default, skip_serializing_if = "Vec::is_empty")]
894    #[serde_as(as = "Vec<Base58Schema>")]
895    #[schemars(with = "Vec<Base58Schema>")]
896    pub dependencies: Vec<TransactionDigest>,
897}
898
899impl IotaTransactionBlockEffectsAPI for IotaTransactionBlockEffectsV1 {
900    fn status(&self) -> &IotaExecutionStatus {
901        &self.status
902    }
903    fn into_status(self) -> IotaExecutionStatus {
904        self.status
905    }
906    fn shared_objects(&self) -> &[ObjectReference] {
907        &self.shared_objects
908    }
909    fn created(&self) -> &[OwnedObjectRef] {
910        &self.created
911    }
912    fn mutated(&self) -> &[OwnedObjectRef] {
913        &self.mutated
914    }
915    fn unwrapped(&self) -> &[OwnedObjectRef] {
916        &self.unwrapped
917    }
918    fn deleted(&self) -> &[ObjectReference] {
919        &self.deleted
920    }
921    fn unwrapped_then_deleted(&self) -> &[ObjectReference] {
922        &self.unwrapped_then_deleted
923    }
924    fn wrapped(&self) -> &[ObjectReference] {
925        &self.wrapped
926    }
927    fn gas_object(&self) -> &OwnedObjectRef {
928        &self.gas_object
929    }
930    fn events_digest(&self) -> Option<&TransactionEventsDigest> {
931        self.events_digest.as_ref()
932    }
933    fn dependencies(&self) -> &[TransactionDigest] {
934        &self.dependencies
935    }
936
937    fn executed_epoch(&self) -> EpochId {
938        self.executed_epoch
939    }
940
941    fn transaction_digest(&self) -> &TransactionDigest {
942        &self.transaction_digest
943    }
944
945    fn gas_cost_summary(&self) -> &GasCostSummary {
946        &self.gas_used
947    }
948
949    fn mutated_excluding_gas(&self) -> Vec<OwnedObjectRef> {
950        self.mutated
951            .iter()
952            .filter(|o| *o != &self.gas_object)
953            .cloned()
954            .collect()
955    }
956
957    fn modified_at_versions(&self) -> Vec<(ObjectId, Version)> {
958        self.modified_at_versions
959            .iter()
960            .map(|v| (v.object_id, v.sequence_number))
961            .collect::<Vec<_>>()
962    }
963
964    fn all_changed_objects(&self) -> Vec<(&OwnedObjectRef, WriteKind)> {
965        self.mutated
966            .iter()
967            .map(|owner_ref| (owner_ref, WriteKind::Mutate))
968            .chain(
969                self.created
970                    .iter()
971                    .map(|owner_ref| (owner_ref, WriteKind::Create)),
972            )
973            .chain(
974                self.unwrapped
975                    .iter()
976                    .map(|owner_ref| (owner_ref, WriteKind::Unwrap)),
977            )
978            .collect()
979    }
980
981    fn all_deleted_objects(&self) -> Vec<(&ObjectReference, DeleteKind)> {
982        self.deleted
983            .iter()
984            .map(|r| (r, DeleteKind::Normal))
985            .chain(
986                self.unwrapped_then_deleted
987                    .iter()
988                    .map(|r| (r, DeleteKind::UnwrapThenDelete)),
989            )
990            .chain(self.wrapped.iter().map(|r| (r, DeleteKind::Wrap)))
991            .collect()
992    }
993}
994
995impl IotaTransactionBlockEffects {
996    pub fn new_for_testing(
997        transaction_digest: TransactionDigest,
998        status: IotaExecutionStatus,
999    ) -> Self {
1000        Self::V1(IotaTransactionBlockEffectsV1 {
1001            transaction_digest,
1002            status,
1003            gas_object: OwnedObjectRef {
1004                owner: Owner::Address(Address::random()),
1005                reference: iota_types::base_types::random_object_ref(),
1006            },
1007            executed_epoch: 0,
1008            modified_at_versions: vec![],
1009            gas_used: GasCostSummary::default(),
1010            shared_objects: vec![],
1011            created: vec![],
1012            mutated: vec![],
1013            unwrapped: vec![],
1014            deleted: vec![],
1015            unwrapped_then_deleted: vec![],
1016            wrapped: vec![],
1017            events_digest: None,
1018            dependencies: vec![],
1019        })
1020    }
1021
1022    /// Construct the RPC view of the transaction effects.
1023    ///
1024    /// This differs from the `TryFrom<TransactionEffects>` implementation
1025    /// in that it tries to convert Move abort errors into human-readable form.
1026    /// This is referred to as clever error.
1027    pub async fn from_native_with_clever_error<S: PackageStore>(
1028        native: TransactionEffects,
1029        resolver: &Resolver<S>,
1030    ) -> Result<Self, anyhow::Error> {
1031        let clever_status =
1032            IotaExecutionStatus::from_native_with_clever_error(native.status().clone(), resolver)
1033                .await?;
1034        match native {
1035            TransactionEffects::V1(inner) => {
1036                let mut inner =
1037                    IotaTransactionBlockEffectsV1::try_from(TransactionEffects::V1(inner))?;
1038                inner.status = clever_status;
1039                Ok(inner.into())
1040            }
1041            _ => Err(anyhow::anyhow!("unknown TransactionEffects variant")),
1042        }
1043    }
1044}
1045
1046impl TryFrom<TransactionEffects> for IotaTransactionBlockEffects {
1047    type Error = IotaError;
1048
1049    fn try_from(native: TransactionEffects) -> Result<Self, Self::Error> {
1050        let effects = IotaTransactionBlockEffectsV1::try_from(native)
1051            .map_err(|e| IotaError::Unknown(e.to_string()))?;
1052        Ok(IotaTransactionBlockEffects::V1(effects))
1053    }
1054}
1055
1056impl TryFrom<TransactionEffects> for IotaTransactionBlockEffectsV1 {
1057    type Error = anyhow::Error;
1058
1059    fn try_from(native: TransactionEffects) -> Result<Self, Self::Error> {
1060        Ok(Self {
1061            status: native.status().clone().try_into()?,
1062            executed_epoch: native.epoch(),
1063            modified_at_versions: native
1064                .modified_at_versions()
1065                .into_iter()
1066                .map(|modified| IotaTransactionBlockEffectsModifiedAtVersions {
1067                    object_id: *modified.object_id(),
1068                    sequence_number: modified.version(),
1069                })
1070                .collect(),
1071            gas_used: native.gas_cost_summary().clone(),
1072            shared_objects: native
1073                .input_shared_objects()
1074                .into_iter()
1075                .map(|shared| shared.object_reference())
1076                .collect(),
1077            transaction_digest: *native.transaction_digest(),
1078            created: to_owned_ref(native.created()),
1079            mutated: to_owned_ref(native.mutated().to_vec()),
1080            unwrapped: to_owned_ref(native.unwrapped().to_vec()),
1081            deleted: native.deleted().to_vec(),
1082            unwrapped_then_deleted: native.unwrapped_then_deleted().to_vec(),
1083            wrapped: native.wrapped().to_vec(),
1084            gas_object: OwnedObjectRef {
1085                owner: *native.gas_object().owner(),
1086                reference: *native.gas_object().reference(),
1087            },
1088            events_digest: native.events_digest().copied(),
1089            dependencies: native.dependencies().to_vec(),
1090        })
1091    }
1092}
1093
1094fn owned_objref_string(obj: &OwnedObjectRef) -> String {
1095    format!(
1096        " ┌──\n │ ID: {} \n │ Owner: {} \n │ Version: {} \n │ Digest: {}\n └──",
1097        obj.reference.object_id, obj.owner, obj.reference.version, obj.reference.digest
1098    )
1099}
1100
1101fn objref_string(obj: &ObjectReference) -> String {
1102    format!(
1103        " ┌──\n │ ID: {} \n │ Version: {} \n │ Digest: {}\n └──",
1104        obj.object_id, obj.version, obj.digest
1105    )
1106}
1107
1108impl Display for IotaTransactionBlockEffects {
1109    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1110        let mut builder = TableBuilder::default();
1111
1112        builder.push_record(vec![format!("Digest: {}", self.transaction_digest())]);
1113        builder.push_record(vec![format!("Status: {:?}", self.status())]);
1114        builder.push_record(vec![format!("Executed Epoch: {}", self.executed_epoch())]);
1115
1116        if !self.created().is_empty() {
1117            builder.push_record(vec!["\nCreated Objects: ".to_string()]);
1118
1119            for oref in self.created() {
1120                builder.push_record(vec![owned_objref_string(oref)]);
1121            }
1122        }
1123
1124        if !self.mutated().is_empty() {
1125            builder.push_record(vec!["Mutated Objects: ".to_string()]);
1126            for oref in self.mutated() {
1127                builder.push_record(vec![owned_objref_string(oref)]);
1128            }
1129        }
1130
1131        if !self.shared_objects().is_empty() {
1132            builder.push_record(vec!["Shared Objects: ".to_string()]);
1133            for oref in self.shared_objects() {
1134                builder.push_record(vec![objref_string(oref)]);
1135            }
1136        }
1137
1138        if !self.deleted().is_empty() {
1139            builder.push_record(vec!["Deleted Objects: ".to_string()]);
1140
1141            for oref in self.deleted() {
1142                builder.push_record(vec![objref_string(oref)]);
1143            }
1144        }
1145
1146        if !self.wrapped().is_empty() {
1147            builder.push_record(vec!["Wrapped Objects: ".to_string()]);
1148
1149            for oref in self.wrapped() {
1150                builder.push_record(vec![objref_string(oref)]);
1151            }
1152        }
1153
1154        if !self.unwrapped().is_empty() {
1155            builder.push_record(vec!["Unwrapped Objects: ".to_string()]);
1156            for oref in self.unwrapped() {
1157                builder.push_record(vec![owned_objref_string(oref)]);
1158            }
1159        }
1160
1161        builder.push_record(vec![format!(
1162            "Gas Object: \n{}",
1163            owned_objref_string(self.gas_object())
1164        )]);
1165
1166        let gas_cost_summary = self.gas_cost_summary();
1167        builder.push_record(vec![format!(
1168            "Gas Cost Summary:\n   \
1169             Storage Cost: {} NANOS\n   \
1170             Computation Cost: {} NANOS\n   \
1171             Computation Cost Burned: {} NANOS\n   \
1172             Storage Rebate: {} NANOS\n   \
1173             Non-refundable Storage Fee: {} NANOS",
1174            gas_cost_summary.storage_cost,
1175            gas_cost_summary.computation_cost,
1176            gas_cost_summary.computation_cost_burned,
1177            gas_cost_summary.storage_rebate,
1178            gas_cost_summary.non_refundable_storage_fee,
1179        )]);
1180
1181        let dependencies = self.dependencies();
1182        if !dependencies.is_empty() {
1183            builder.push_record(vec!["\nTransaction Dependencies:".to_string()]);
1184            for dependency in dependencies {
1185                builder.push_record(vec![format!("   {dependency}")]);
1186            }
1187        }
1188
1189        let mut table = builder.build();
1190        table.with(TablePanel::header("Transaction Effects"));
1191        table.with(TableStyle::rounded().horizontals([HorizontalLine::new(
1192            1,
1193            TableStyle::modern().get_horizontal(),
1194        )]));
1195        write!(f, "{table}")
1196    }
1197}
1198
1199#[serde_as]
1200#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize, JsonSchema)]
1201#[serde(rename_all = "camelCase")]
1202pub struct DryRunTransactionBlockResponse {
1203    pub effects: IotaTransactionBlockEffects,
1204    pub events: IotaTransactionBlockEvents,
1205    pub object_changes: Vec<ObjectChange>,
1206    pub balance_changes: Vec<BalanceChange>,
1207    pub input: IotaTransactionBlockData,
1208    /// If an input object is congested, suggest a gas price to use.
1209    #[serde(default, skip_serializing_if = "Option::is_none")]
1210    #[schemars(with = "Option<String>")]
1211    #[serde_as(as = "Option<DisplayFromStr>")]
1212    pub suggested_gas_price: Option<u64>,
1213    pub execution_error_source: Option<String>,
1214}
1215
1216#[derive(Eq, PartialEq, Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
1217#[serde(rename = "TransactionBlockEvents", transparent)]
1218pub struct IotaTransactionBlockEvents {
1219    pub data: Vec<IotaEvent>,
1220}
1221
1222impl IotaTransactionBlockEvents {
1223    pub fn try_from(
1224        mut events: TransactionEvents,
1225        tx_digest: TransactionDigest,
1226        timestamp_ms: Option<u64>,
1227        resolver: &mut dyn LayoutResolver,
1228    ) -> IotaResult<Self> {
1229        Ok(Self {
1230            data: events
1231                .drain(..)
1232                .enumerate()
1233                .map(|(seq, event)| {
1234                    let layout = resolver.get_annotated_layout(&event.struct_tag)?;
1235                    IotaEvent::try_from(event, tx_digest, seq as u64, timestamp_ms, layout)
1236                })
1237                .collect::<Result<_, _>>()?,
1238        })
1239    }
1240
1241    // TODO: this is only called from the indexer. Remove this once indexer moves to
1242    // its own resolver.
1243    pub fn try_from_using_module_resolver(
1244        mut events: TransactionEvents,
1245        tx_digest: TransactionDigest,
1246        timestamp_ms: Option<u64>,
1247        resolver: &impl GetModule,
1248    ) -> IotaResult<Self> {
1249        Ok(Self {
1250            data: events
1251                .drain(..)
1252                .enumerate()
1253                .map(|(seq, event)| {
1254                    let layout = get_layout_from_struct_tag(event.struct_tag.clone(), resolver)?;
1255                    IotaEvent::try_from(event, tx_digest, seq as u64, timestamp_ms, layout)
1256                })
1257                .collect::<Result<_, _>>()?,
1258        })
1259    }
1260}
1261
1262impl Display for IotaTransactionBlockEvents {
1263    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1264        if self.data.is_empty() {
1265            writeln!(f, "╭─────────────────────────────╮")?;
1266            writeln!(f, "│ No transaction block events │")?;
1267            writeln!(f, "╰─────────────────────────────╯")
1268        } else {
1269            let mut builder = TableBuilder::default();
1270
1271            for event in &self.data {
1272                builder.push_record(vec![format!("{event}")]);
1273            }
1274
1275            let mut table = builder.build();
1276            table.with(TablePanel::header("Transaction Block Events"));
1277            table.with(TableStyle::rounded().horizontals([HorizontalLine::new(
1278                1,
1279                TableStyle::modern().get_horizontal(),
1280            )]));
1281            write!(f, "{table}")
1282        }
1283    }
1284}
1285
1286// TODO: this file might not be the best place for this struct.
1287/// Additional arguments supplied to dev inspect beyond what is allowed in
1288/// today's API.
1289#[serde_as]
1290#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema)]
1291#[serde(rename = "DevInspectArgs", rename_all = "camelCase")]
1292pub struct DevInspectArgs {
1293    /// The sponsor of the gas for the transaction, might be different from the
1294    /// sender.
1295    #[serde_as(as = "Option<AddressSchema>")]
1296    #[schemars(with = "Option<AddressSchema>")]
1297    pub gas_sponsor: Option<Address>,
1298    /// The gas budget for the transaction.
1299    #[schemars(with = "Option<String>")]
1300    #[serde_as(as = "Option<DisplayFromStr>")]
1301    pub gas_budget: Option<u64>,
1302    /// The gas objects used to pay for the transaction.
1303    #[schemars(with = "Option<Vec<ObjectRefSchema>>")]
1304    #[serde_as(as = "Option<Vec<ObjectRefSchema>>")]
1305    pub gas_objects: Option<Vec<ObjectReference>>,
1306    /// Whether to skip transaction checks for the transaction.
1307    pub skip_checks: Option<bool>,
1308    /// Whether to return the raw transaction data and effects.
1309    pub show_raw_txn_data_and_effects: Option<bool>,
1310}
1311
1312/// The response from processing a dev inspect transaction
1313#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1314#[serde(rename = "DevInspectResults", rename_all = "camelCase")]
1315pub struct DevInspectResults {
1316    /// Summary of effects that likely would be generated if the transaction is
1317    /// actually run. Note however, that not all dev-inspect transactions
1318    /// are actually usable as transactions so it might not be possible
1319    /// actually generate these effects from a normal transaction.
1320    pub effects: IotaTransactionBlockEffects,
1321    /// Events that likely would be generated if the transaction is actually
1322    /// run.
1323    pub events: IotaTransactionBlockEvents,
1324    /// Execution results (including return values) from executing the
1325    /// transactions
1326    #[serde(skip_serializing_if = "Option::is_none")]
1327    pub results: Option<Vec<IotaExecutionResult>>,
1328    /// Execution error from executing the transactions
1329    #[serde(skip_serializing_if = "Option::is_none")]
1330    pub error: Option<String>,
1331    /// The raw transaction data that was dev inspected.
1332    #[serde(skip_serializing_if = "Vec::is_empty", default)]
1333    pub raw_txn_data: Vec<u8>,
1334    /// The raw effects of the transaction that was dev inspected.
1335    #[serde(skip_serializing_if = "Vec::is_empty", default)]
1336    pub raw_effects: Vec<u8>,
1337}
1338
1339#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1340#[serde(rename = "IotaExecutionResult", rename_all = "camelCase")]
1341pub struct IotaExecutionResult {
1342    /// The value of any arguments that were mutably borrowed.
1343    /// Non-mut borrowed values are not included
1344    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1345    #[schemars(with = "Vec<(IotaArgument, Vec<u8>, TypeTagSchema)>")]
1346    pub mutable_reference_outputs: Vec<(/* argument */ IotaArgument, Vec<u8>, IotaTypeTag)>,
1347    /// The return values from the transaction
1348    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1349    #[schemars(with = "Vec<(Vec<u8>, TypeTagSchema)>")]
1350    pub return_values: Vec<(Vec<u8>, IotaTypeTag)>,
1351}
1352
1353impl IotaExecutionResult {
1354    fn into_stream_return_value_layouts<S: PackageStore>(
1355        self,
1356        package_resolver: &Resolver<S>,
1357    ) -> impl Stream<Item = anyhow::Result<(Vec<u8>, MoveTypeLayout)>> + use<'_, S> {
1358        self.return_values
1359            .into_iter()
1360            .map(|(bytes, iota_type_tag)| async {
1361                let type_tag = TypeTag::try_from(iota_type_tag)?;
1362                let move_type_layout = package_resolver.type_layout(type_tag).await?;
1363                Ok((bytes, move_type_layout))
1364            })
1365            .collect::<FuturesOrdered<_>>()
1366    }
1367}
1368
1369#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1370pub enum IotaMoveViewCallResults {
1371    /// Execution error from executing the move view call
1372    #[serde(rename = "executionError")]
1373    Error(String),
1374    /// The return values of the move view function
1375    #[serde(rename = "functionReturnValues")]
1376    Results(Vec<IotaMoveValue>),
1377}
1378
1379impl IotaMoveViewCallResults {
1380    /// Processes the dev-inspect results to produce the response
1381    /// of the move-view function call.
1382    pub async fn from_dev_inspect_results<S: PackageStore>(
1383        package_store: S,
1384        dev_inspect_results: DevInspectResults,
1385    ) -> anyhow::Result<Self> {
1386        if let Some(error) = dev_inspect_results.error {
1387            return Ok(Self::Error(error));
1388        }
1389        let Some(mut tx_execution_results) = dev_inspect_results.results else {
1390            return Ok(Self::Error("function call returned no values".into()));
1391        };
1392        let Some(execution_results) = tx_execution_results.pop() else {
1393            return Ok(Self::Error(
1394                "no results from move view function call".into(),
1395            ));
1396        };
1397        if !tx_execution_results.is_empty() {
1398            return Ok(Self::Error("multiple transactions executed".into()));
1399        }
1400        let mut move_call_results = Vec::with_capacity(execution_results.return_values.len());
1401        let package_resolver = Resolver::new(package_store);
1402        let mut execution_results =
1403            execution_results.into_stream_return_value_layouts(&package_resolver);
1404        while let Some(result) = execution_results.next().await {
1405            let (bytes, move_type_layout) = result?;
1406            let move_value = BoundedVisitor::deserialize_value(&bytes, &move_type_layout)?;
1407            move_call_results.push(IotaMoveValue::from(move_value));
1408        }
1409        Ok(Self::Results(move_call_results))
1410    }
1411
1412    pub fn into_return_values(self) -> Vec<IotaMoveValue> {
1413        match self {
1414            IotaMoveViewCallResults::Error(_) => Default::default(),
1415            IotaMoveViewCallResults::Results(values) => values,
1416        }
1417    }
1418
1419    pub fn error(&self) -> Option<&str> {
1420        match self {
1421            IotaMoveViewCallResults::Error(e) => Some(e.as_str()),
1422            IotaMoveViewCallResults::Results(_) => None,
1423        }
1424    }
1425}
1426
1427type ExecutionResult = (
1428    // mutable_reference_outputs
1429    Vec<(Argument, Vec<u8>, TypeTag)>,
1430    // return_values
1431    Vec<(Vec<u8>, TypeTag)>,
1432);
1433
1434impl DevInspectResults {
1435    pub fn new(
1436        effects: TransactionEffects,
1437        events: TransactionEvents,
1438        return_values: Result<Vec<ExecutionResult>, ExecutionError>,
1439        raw_txn_data: Vec<u8>,
1440        raw_effects: Vec<u8>,
1441        resolver: &mut dyn LayoutResolver,
1442    ) -> IotaResult<Self> {
1443        let tx_digest = *effects.transaction_digest();
1444        let mut error = None;
1445        let mut results = None;
1446        match return_values {
1447            Err(e) => error = Some(e.to_string()),
1448            Ok(srvs) => {
1449                results = Some(
1450                    srvs.into_iter()
1451                        .map(|srv| {
1452                            let (mutable_reference_outputs, return_values) = srv;
1453                            let mutable_reference_outputs = mutable_reference_outputs
1454                                .into_iter()
1455                                .map(|(a, bytes, tag)| {
1456                                    Ok((IotaArgument::try_from(a)?, bytes, IotaTypeTag::from(tag)))
1457                                })
1458                                .collect::<Result<_, anyhow::Error>>()?;
1459                            let return_values = return_values
1460                                .into_iter()
1461                                .map(|(bytes, tag)| (bytes, IotaTypeTag::from(tag)))
1462                                .collect();
1463                            Ok(IotaExecutionResult {
1464                                mutable_reference_outputs,
1465                                return_values,
1466                            })
1467                        })
1468                        .collect::<Result<_, anyhow::Error>>()
1469                        .map_err(|e| IotaError::Unknown(e.to_string()))?,
1470                )
1471            }
1472        };
1473        Ok(Self {
1474            effects: effects.try_into()?,
1475            events: IotaTransactionBlockEvents::try_from(events, tx_digest, None, resolver)?,
1476            results,
1477            error,
1478            raw_txn_data,
1479            raw_effects,
1480        })
1481    }
1482}
1483
1484#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize, JsonSchema)]
1485pub enum IotaTransactionBlockBuilderMode {
1486    /// Regular IOTA Transactions that are committed on chain
1487    Commit,
1488    /// Simulated transaction that allows calling any Move function with
1489    /// arbitrary values.
1490    DevInspect,
1491}
1492
1493#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize, JsonSchema)]
1494#[serde(rename = "ExecutionStatus", rename_all = "camelCase", tag = "status")]
1495pub enum IotaExecutionStatus {
1496    // Gas used in the success case.
1497    Success,
1498    // Gas used in the failed case, and the error.
1499    Failure { error: String },
1500}
1501
1502impl IotaExecutionStatus {
1503    /// Construct the RPC view of the execution status.
1504    ///
1505    /// This differs from the `TryFrom<ExecutionStatus>` implementation
1506    /// in that it tries to convert Move abort errors into human-readable form.
1507    /// This is referred to as clever error.
1508    pub async fn from_native_with_clever_error<S: PackageStore>(
1509        native: ExecutionStatus,
1510        resolver: &Resolver<S>,
1511    ) -> Result<Self, anyhow::Error> {
1512        match native {
1513            ExecutionStatus::Failure {
1514                error,
1515                command: Some(mut command_index),
1516            } => {
1517                let error = 'error: {
1518                    let ExecutionFailureStatus::MoveAbort { location, code } = &error else {
1519                        break 'error error.to_string();
1520                    };
1521                    let fname_string = if let Some(fname) = &location.function_name {
1522                        format!("::{fname}'")
1523                    } else {
1524                        "'".to_string()
1525                    };
1526
1527                    let module_id = ModuleId::new(
1528                        AccountAddress::from(location.package.into_bytes()),
1529                        identifier_sdk_to_core(&location.module),
1530                    );
1531
1532                    let Some(CleverError {
1533                        module_id,
1534                        source_line_number,
1535                        error_info,
1536                        error_code,
1537                    }) = resolver
1538                        .resolve_clever_error(module_id.clone(), *code)
1539                        .await
1540                    else {
1541                        break 'error format!(
1542                            "from '{}{fname_string} (instruction {}), abort code: {code}",
1543                            module_id.to_canonical_display(true),
1544                            location.instruction,
1545                        );
1546                    };
1547
1548                    let error_code_str = match error_code {
1549                        Some(code) => format!("(code = {code})"),
1550                        None => String::new(),
1551                    };
1552
1553                    match error_info {
1554                        ErrorConstants::Rendered {
1555                            identifier,
1556                            constant,
1557                        } => {
1558                            format!(
1559                                "from '{}{fname_string} (line {source_line_number}), abort{error_code_str} '{identifier}': {constant}",
1560                                module_id.to_canonical_display(true)
1561                            )
1562                        }
1563                        ErrorConstants::Raw { identifier, bytes } => {
1564                            let const_str = Base64::encode(bytes);
1565                            format!(
1566                                "from '{}{fname_string} (line {source_line_number}), abort{error_code_str} '{identifier}': {const_str}",
1567                                module_id.to_canonical_display(true)
1568                            )
1569                        }
1570                        ErrorConstants::None => {
1571                            format!(
1572                                "from '{}{fname_string} (line {source_line_number}){}",
1573                                module_id.to_canonical_display(true),
1574                                match error_code {
1575                                    Some(code) => format!(" abort(code = {code})"),
1576                                    None => String::new(),
1577                                }
1578                            )
1579                        }
1580                    }
1581                };
1582                // Convert the command index into an ordinal.
1583                command_index += 1;
1584                let suffix = match command_index % 10 {
1585                    1 if command_index % 100 != 11 => "st",
1586                    2 if command_index % 100 != 12 => "nd",
1587                    3 if command_index % 100 != 13 => "rd",
1588                    _ => "th",
1589                };
1590                Ok(IotaExecutionStatus::Failure {
1591                    error: format!("Error in {command_index}{suffix} command, {error}"),
1592                })
1593            }
1594            _ => native.try_into(),
1595        }
1596    }
1597}
1598
1599impl Display for IotaExecutionStatus {
1600    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1601        match self {
1602            Self::Success => write!(f, "success"),
1603            Self::Failure { error } => write!(f, "failure due to {error}"),
1604        }
1605    }
1606}
1607
1608impl IotaExecutionStatus {
1609    pub fn is_ok(&self) -> bool {
1610        matches!(self, IotaExecutionStatus::Success)
1611    }
1612    pub fn is_err(&self) -> bool {
1613        matches!(self, IotaExecutionStatus::Failure { .. })
1614    }
1615}
1616
1617impl TryFrom<ExecutionStatus> for IotaExecutionStatus {
1618    type Error = anyhow::Error;
1619
1620    fn try_from(status: ExecutionStatus) -> Result<Self, Self::Error> {
1621        Ok(match status {
1622            ExecutionStatus::Success => Self::Success,
1623            ExecutionStatus::Failure {
1624                error,
1625                command: None,
1626            } => Self::Failure {
1627                error: error.to_string(),
1628            },
1629            ExecutionStatus::Failure {
1630                error,
1631                command: Some(idx),
1632            } => Self::Failure {
1633                error: format!("{error} in command {idx}"),
1634            },
1635            _ => anyhow::bail!("unknown ExecutionStatus variant"),
1636        })
1637    }
1638}
1639
1640fn to_owned_ref(owned_refs: Vec<OwnedObjectReference>) -> Vec<OwnedObjectRef> {
1641    owned_refs
1642        .into_iter()
1643        .map(|owned| OwnedObjectRef {
1644            owner: *owned.owner(),
1645            reference: *owned.reference(),
1646        })
1647        .collect()
1648}
1649
1650#[serde_as]
1651#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, PartialEq, Eq)]
1652#[serde(rename = "GasData", rename_all = "camelCase")]
1653pub struct IotaGasData {
1654    #[schemars(with = "Vec<ObjectRefSchema>")]
1655    #[serde_as(as = "Vec<ObjectRefSchema>")]
1656    pub payment: Vec<ObjectReference>,
1657    #[serde_as(as = "AddressSchema")]
1658    #[schemars(with = "AddressSchema")]
1659    pub owner: Address,
1660    #[schemars(with = "String")]
1661    #[serde_as(as = "DisplayFromStr")]
1662    pub price: u64,
1663    #[schemars(with = "String")]
1664    #[serde_as(as = "DisplayFromStr")]
1665    pub budget: u64,
1666}
1667
1668impl Display for IotaGasData {
1669    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1670        writeln!(f, "Gas Owner: {}", self.owner)?;
1671        writeln!(f, "Gas Budget: {} NANOS", self.budget)?;
1672        writeln!(f, "Gas Price: {} NANOS", self.price)?;
1673        writeln!(f, "Gas Payment:")?;
1674        for payment in &self.payment {
1675            write!(f, "{} ", objref_string(payment))?;
1676        }
1677        writeln!(f)
1678    }
1679}
1680
1681#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, PartialEq, Eq)]
1682#[enum_dispatch(IotaTransactionBlockDataAPI)]
1683#[serde(
1684    rename = "TransactionBlockData",
1685    rename_all = "camelCase",
1686    tag = "messageVersion"
1687)]
1688pub enum IotaTransactionBlockData {
1689    V1(IotaTransactionBlockDataV1),
1690}
1691
1692#[enum_dispatch]
1693pub trait IotaTransactionBlockDataAPI {
1694    fn transaction(&self) -> &IotaTransactionBlockKind;
1695    fn sender(&self) -> &Address;
1696    fn gas_data(&self) -> &IotaGasData;
1697}
1698
1699#[serde_as]
1700#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, PartialEq, Eq)]
1701#[serde(rename = "TransactionBlockDataV1", rename_all = "camelCase")]
1702pub struct IotaTransactionBlockDataV1 {
1703    pub transaction: IotaTransactionBlockKind,
1704    #[serde_as(as = "AddressSchema")]
1705    #[schemars(with = "AddressSchema")]
1706    pub sender: Address,
1707    pub gas_data: IotaGasData,
1708}
1709
1710impl IotaTransactionBlockDataAPI for IotaTransactionBlockDataV1 {
1711    fn transaction(&self) -> &IotaTransactionBlockKind {
1712        &self.transaction
1713    }
1714    fn sender(&self) -> &Address {
1715        &self.sender
1716    }
1717    fn gas_data(&self) -> &IotaGasData {
1718        &self.gas_data
1719    }
1720}
1721
1722impl IotaTransactionBlockData {
1723    pub fn move_calls(&self) -> Vec<&IotaProgrammableMoveCall> {
1724        match self {
1725            Self::V1(data) => match &data.transaction {
1726                IotaTransactionBlockKind::ProgrammableTransaction(pt) => pt
1727                    .commands
1728                    .iter()
1729                    .filter_map(|command| match command {
1730                        IotaCommand::MoveCall(c) => Some(&**c),
1731                        _ => None,
1732                    })
1733                    .collect(),
1734                _ => vec![],
1735            },
1736        }
1737    }
1738
1739    fn try_from_inner(
1740        tx: Transaction,
1741        transaction: IotaTransactionBlockKind,
1742    ) -> Result<Self, anyhow::Error> {
1743        let message_version = tx.message_version();
1744        let sender = tx.sender();
1745        let gas_data = IotaGasData {
1746            payment: tx.gas().to_vec(),
1747            owner: tx.gas_owner(),
1748            price: tx.gas_price(),
1749            budget: tx.gas_budget(),
1750        };
1751
1752        match message_version {
1753            1 => Ok(IotaTransactionBlockData::V1(IotaTransactionBlockDataV1 {
1754                transaction,
1755                sender,
1756                gas_data,
1757            })),
1758            _ => Err(anyhow::anyhow!(
1759                "Support for Transaction version {message_version} not implemented"
1760            )),
1761        }
1762    }
1763
1764    pub fn try_from_with_module_cache(
1765        tx: Transaction,
1766        module_cache: &impl GetModule,
1767        tx_digest: TransactionDigest,
1768    ) -> Result<Self, anyhow::Error> {
1769        let transaction = IotaTransactionBlockKind::try_from_with_module_cache(
1770            tx.kind().clone(),
1771            module_cache,
1772            tx_digest,
1773        )?;
1774        Self::try_from_inner(tx, transaction)
1775    }
1776
1777    pub async fn try_from_with_package_resolver(
1778        tx: Transaction,
1779        package_resolver: &Resolver<impl PackageStore>,
1780        tx_digest: TransactionDigest,
1781    ) -> Result<Self, anyhow::Error> {
1782        let transaction = IotaTransactionBlockKind::try_from_with_package_resolver(
1783            tx.kind().clone(),
1784            package_resolver,
1785            tx_digest,
1786        )
1787        .await?;
1788        Self::try_from_inner(tx, transaction)
1789    }
1790}
1791
1792impl Display for IotaTransactionBlockData {
1793    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1794        match self {
1795            Self::V1(data) => {
1796                writeln!(f, "Sender: {}", data.sender)?;
1797                writeln!(f, "{}", self.gas_data())?;
1798                writeln!(f, "{}", data.transaction)
1799            }
1800        }
1801    }
1802}
1803
1804#[serde_as]
1805#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, PartialEq, Eq)]
1806#[serde(rename = "TransactionBlock", rename_all = "camelCase")]
1807pub struct IotaTransactionBlock {
1808    pub data: IotaTransactionBlockData,
1809    #[serde_as(as = "Vec<UserSignatureSchema>")]
1810    #[schemars(with = "Vec<UserSignatureSchema>")]
1811    pub tx_signatures: Vec<UserSignature>,
1812}
1813
1814impl IotaTransactionBlock {
1815    pub fn try_from(
1816        tx: SenderSignedTransaction,
1817        module_cache: &impl GetModule,
1818        tx_digest: TransactionDigest,
1819    ) -> Result<Self, anyhow::Error> {
1820        Ok(Self {
1821            data: IotaTransactionBlockData::try_from_with_module_cache(
1822                tx.transaction().clone(),
1823                module_cache,
1824                tx_digest,
1825            )?,
1826            tx_signatures: tx.signatures().to_vec(),
1827        })
1828    }
1829
1830    // TODO: the IotaTransactionBlock `try_from` can be removed after cleaning up
1831    // indexer v1, so are the related `try_from` methods for nested structs like
1832    // IotaTransactionBlockData etc.
1833    pub async fn try_from_with_package_resolver(
1834        tx: SenderSignedTransaction,
1835        package_resolver: &Resolver<impl PackageStore>,
1836        tx_digest: TransactionDigest,
1837    ) -> Result<Self, anyhow::Error> {
1838        Ok(Self {
1839            data: IotaTransactionBlockData::try_from_with_package_resolver(
1840                tx.transaction().clone(),
1841                package_resolver,
1842                tx_digest,
1843            )
1844            .await?,
1845            tx_signatures: tx.signatures().to_vec(),
1846        })
1847    }
1848}
1849
1850impl Display for IotaTransactionBlock {
1851    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1852        let mut builder = TableBuilder::default();
1853
1854        builder.push_record(vec![format!("{}", self.data)]);
1855        builder.push_record(vec!["Signatures:".to_string()]);
1856        for tx_sig in &self.tx_signatures {
1857            builder.push_record(vec![format!(
1858                "   {}\n",
1859                match tx_sig {
1860                    UserSignature::Simple(sig) =>
1861                        Base64::from_bytes(sig.signature_bytes()).encoded(),
1862                    // the signatures for multisig and zklogin
1863                    // are not suited to be parsed out. they
1864                    // should be interpreted as a whole
1865                    _ => Base64::from_bytes(&tx_sig.to_bytes()).encoded(),
1866                }
1867            )]);
1868        }
1869
1870        let mut table = builder.build();
1871        table.with(TablePanel::header("Transaction Data"));
1872        table.with(TableStyle::rounded().horizontals([HorizontalLine::new(
1873            1,
1874            TableStyle::modern().get_horizontal(),
1875        )]));
1876        write!(f, "{table}")
1877    }
1878}
1879
1880#[serde_as]
1881#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1882pub struct IotaGenesisTransaction {
1883    #[serde_as(as = "Vec<ObjectIdSchema>")]
1884    #[schemars(with = "Vec<ObjectIdSchema>")]
1885    pub objects: Vec<ObjectId>,
1886    #[schemars(with = "Vec<IotaEventID>")]
1887    pub events: Vec<EventID>,
1888}
1889
1890#[serde_as]
1891#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1892pub struct IotaConsensusCommitPrologueV1 {
1893    #[schemars(with = "String")]
1894    #[serde_as(as = "DisplayFromStr")]
1895    pub epoch: u64,
1896    #[schemars(with = "String")]
1897    #[serde_as(as = "DisplayFromStr")]
1898    pub round: u64,
1899    #[schemars(with = "Option<String>")]
1900    #[serde_as(as = "Option<DisplayFromStr>")]
1901    pub sub_dag_index: Option<u64>,
1902    #[schemars(with = "String")]
1903    #[serde_as(as = "DisplayFromStr")]
1904    pub commit_timestamp_ms: u64,
1905    #[serde_as(as = "Base58Schema")]
1906    #[schemars(with = "Base58Schema")]
1907    pub consensus_commit_digest: ConsensusCommitDigest,
1908    pub consensus_determined_version_assignments: IotaConsensusDeterminedVersionAssignments,
1909}
1910
1911/// Uses an enum to allow for future expansion of the
1912/// ConsensusDeterminedVersionAssignments.
1913#[serde_as]
1914#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, JsonSchema)]
1915#[schemars(rename = "ConsensusDeterminedVersionAssignments")]
1916pub enum IotaConsensusDeterminedVersionAssignments {
1917    // Cancelled transaction version assignment.
1918    CancelledTransactions(
1919        #[serde_as(as = "Vec<(Base58Schema, Vec<(ObjectIdSchema, serde_with::Same)>)>")]
1920        #[schemars(with = "Vec<(Base58Schema, Vec<(ObjectIdSchema, SequenceNumberU64)>)>")]
1921        Vec<(TransactionDigest, Vec<(ObjectId, SequenceNumberU64)>)>,
1922    ),
1923}
1924
1925impl TryFrom<ConsensusDeterminedVersionAssignments> for IotaConsensusDeterminedVersionAssignments {
1926    type Error = anyhow::Error;
1927
1928    fn try_from(
1929        consensus_determined_version_assignments: ConsensusDeterminedVersionAssignments,
1930    ) -> Result<Self, Self::Error> {
1931        Ok(match consensus_determined_version_assignments {
1932            ConsensusDeterminedVersionAssignments::CanceledTransactions {
1933                canceled_transactions,
1934            } => IotaConsensusDeterminedVersionAssignments::CancelledTransactions(
1935                canceled_transactions
1936                    .into_iter()
1937                    .map(|cancelled| {
1938                        (
1939                            cancelled.digest,
1940                            cancelled
1941                                .version_assignments
1942                                .into_iter()
1943                                .map(|va| (va.object_id, va.version.into()))
1944                                .collect(),
1945                        )
1946                    })
1947                    .collect(),
1948            ),
1949            _ => anyhow::bail!("unknown ConsensusDeterminedVersionAssignments variant"),
1950        })
1951    }
1952}
1953
1954impl From<IotaConsensusDeterminedVersionAssignments> for ConsensusDeterminedVersionAssignments {
1955    fn from(
1956        iota_consensus_determined_version_assignments: IotaConsensusDeterminedVersionAssignments,
1957    ) -> Self {
1958        match iota_consensus_determined_version_assignments {
1959            IotaConsensusDeterminedVersionAssignments::CancelledTransactions(assignments) => {
1960                ConsensusDeterminedVersionAssignments::CanceledTransactions {
1961                    canceled_transactions: assignments
1962                        .into_iter()
1963                        .map(|(digest, version_assignments)| CanceledTransaction {
1964                            digest,
1965                            version_assignments: version_assignments
1966                                .into_iter()
1967                                .map(|(object_id, version)| VersionAssignment {
1968                                    object_id,
1969                                    version: version.into(),
1970                                })
1971                                .collect(),
1972                        })
1973                        .collect(),
1974                }
1975            }
1976        }
1977    }
1978}
1979
1980#[serde_as]
1981#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1982pub struct IotaRandomnessStateUpdate {
1983    #[schemars(with = "String")]
1984    #[serde_as(as = "DisplayFromStr")]
1985    pub epoch: u64,
1986
1987    #[schemars(with = "String")]
1988    #[serde_as(as = "DisplayFromStr")]
1989    pub randomness_round: u64,
1990    pub random_bytes: Vec<u8>,
1991}
1992
1993/// The add/remove delta a `TransactionDenyRulesUpdate` system transaction
1994/// applies to the on-chain transaction deny rules; the switch states are
1995/// absolute.
1996#[serde_as]
1997#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1998pub struct IotaTransactionDenyRulesUpdate {
1999    #[schemars(with = "String")]
2000    #[serde_as(as = "DisplayFromStr")]
2001    pub epoch: u64,
2002
2003    #[schemars(with = "String")]
2004    #[serde_as(as = "DisplayFromStr")]
2005    pub round: u64,
2006    #[schemars(with = "Vec<AddressSchema>")]
2007    #[serde_as(as = "Vec<AddressSchema>")]
2008    pub added_addresses: Vec<Address>,
2009    #[schemars(with = "Vec<AddressSchema>")]
2010    #[serde_as(as = "Vec<AddressSchema>")]
2011    pub removed_addresses: Vec<Address>,
2012    #[schemars(with = "Vec<ObjectIdSchema>")]
2013    #[serde_as(as = "Vec<ObjectIdSchema>")]
2014    pub added_objects: Vec<ObjectId>,
2015    #[schemars(with = "Vec<ObjectIdSchema>")]
2016    #[serde_as(as = "Vec<ObjectIdSchema>")]
2017    pub removed_objects: Vec<ObjectId>,
2018    #[schemars(with = "Vec<ObjectIdSchema>")]
2019    #[serde_as(as = "Vec<ObjectIdSchema>")]
2020    pub added_packages: Vec<ObjectId>,
2021    #[schemars(with = "Vec<ObjectIdSchema>")]
2022    #[serde_as(as = "Vec<ObjectIdSchema>")]
2023    pub removed_packages: Vec<ObjectId>,
2024    pub package_publish_disabled: bool,
2025    pub package_upgrade_disabled: bool,
2026    pub shared_object_disabled: bool,
2027    pub user_transaction_disabled: bool,
2028    pub receiving_objects_disabled: bool,
2029    pub move_authenticator_disabled: bool,
2030}
2031
2032#[serde_as]
2033#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
2034pub struct IotaEndOfEpochTransaction {
2035    pub transactions: Vec<IotaEndOfEpochTransactionKind>,
2036}
2037
2038#[serde_as]
2039#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
2040pub enum IotaEndOfEpochTransactionKind {
2041    ChangeEpoch(IotaChangeEpoch),
2042    ChangeEpochV2(IotaChangeEpochV2),
2043    TransactionDenyRulesCreate,
2044}
2045
2046#[serde_as]
2047#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, JsonSchema)]
2048#[serde(rename = "InputObjectKind")]
2049pub enum IotaInputObjectKind {
2050    // A Move package, must be immutable.
2051    MovePackage(
2052        #[serde_as(as = "ObjectIdSchema")]
2053        #[schemars(with = "ObjectIdSchema")]
2054        ObjectId,
2055    ),
2056    // A Move object, either immutable, or owned mutable.
2057    ImmOrOwnedMoveObject(
2058        #[schemars(with = "ObjectRefSchema")]
2059        #[serde_as(as = "ObjectRefSchema")]
2060        ObjectReference,
2061    ),
2062    // A Move object that's shared and mutable.
2063    SharedMoveObject {
2064        #[serde_as(as = "ObjectIdSchema")]
2065        #[schemars(with = "ObjectIdSchema")]
2066        id: ObjectId,
2067        #[schemars(with = "SequenceNumberStringSchema")]
2068        #[serde_as(as = "SequenceNumberStringSchema")]
2069        initial_shared_version: Version,
2070        #[serde(default = "default_shared_object_mutability")]
2071        mutable: bool,
2072    },
2073}
2074
2075/// A series of commands where the results of one command can be used in future
2076/// commands
2077#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
2078pub struct IotaProgrammableTransactionBlock {
2079    /// Input objects or primitive values
2080    pub inputs: Vec<IotaCallArg>,
2081    #[serde(rename = "transactions")]
2082    /// The transactions to be executed sequentially. A failure in any
2083    /// transaction will result in the failure of the entire programmable
2084    /// transaction block.
2085    pub commands: Vec<IotaCommand>,
2086}
2087
2088impl Display for IotaProgrammableTransactionBlock {
2089    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
2090        let Self { inputs, commands } = self;
2091        writeln!(f, "Inputs: {inputs:?}")?;
2092        writeln!(f, "Commands: [")?;
2093        for c in commands {
2094            writeln!(f, "  {c},")?;
2095        }
2096        writeln!(f, "]")
2097    }
2098}
2099
2100impl IotaProgrammableTransactionBlock {
2101    fn try_from_with_module_cache(
2102        value: ProgrammableTransaction,
2103        module_cache: &impl GetModule,
2104    ) -> Result<Self, anyhow::Error> {
2105        let ProgrammableTransaction { inputs, commands } = value;
2106        let input_types = Self::resolve_input_type(&inputs, &commands, module_cache);
2107        Ok(IotaProgrammableTransactionBlock {
2108            inputs: inputs
2109                .into_iter()
2110                .zip(input_types)
2111                .map(|(arg, layout)| IotaCallArg::try_from(arg, layout.as_ref()))
2112                .collect::<Result<_, _>>()?,
2113            commands: commands
2114                .into_iter()
2115                .map(IotaCommand::try_from)
2116                .collect::<Result<_, _>>()?,
2117        })
2118    }
2119
2120    async fn try_from_with_package_resolver(
2121        value: ProgrammableTransaction,
2122        package_resolver: &Resolver<impl PackageStore>,
2123    ) -> Result<Self, anyhow::Error> {
2124        // If the pure input layouts cannot be built, we will use `None` for the input
2125        // types.
2126        let input_types = package_resolver
2127            .pure_input_layouts(&value)
2128            .await
2129            .unwrap_or_else(|e| {
2130                tracing::warn!("pure_input_layouts failed: {:?}", e);
2131                vec![None; value.inputs.len()]
2132            });
2133
2134        let ProgrammableTransaction { inputs, commands } = value;
2135        Ok(IotaProgrammableTransactionBlock {
2136            inputs: inputs
2137                .into_iter()
2138                .zip(input_types)
2139                .map(|(arg, layout)| IotaCallArg::try_from(arg, layout.as_ref()))
2140                .collect::<Result<_, _>>()?,
2141            commands: commands
2142                .into_iter()
2143                .map(IotaCommand::try_from)
2144                .collect::<Result<_, _>>()?,
2145        })
2146    }
2147
2148    fn resolve_input_type(
2149        inputs: &[CallArg],
2150        commands: &[Command],
2151        module_cache: &impl GetModule,
2152    ) -> Vec<Option<MoveTypeLayout>> {
2153        let mut result_types = vec![None; inputs.len()];
2154        for command in commands.iter() {
2155            match command {
2156                Command::MoveCall(cmd) => {
2157                    let module = identifier_sdk_to_core(&cmd.module);
2158                    let id = ModuleId::new(AccountAddress::new(cmd.package.into_bytes()), module);
2159                    let Some(types) = get_signature_types(id, &cmd.function, module_cache) else {
2160                        return result_types;
2161                    };
2162                    for (arg, type_) in cmd.arguments.iter().zip(types) {
2163                        if let (&Argument::Input(i), Some(type_)) = (arg, type_) {
2164                            if let Some(x) = result_types.get_mut(i as usize) {
2165                                x.replace(type_);
2166                            }
2167                        }
2168                    }
2169                }
2170                Command::SplitCoins(cmd) => {
2171                    for arg in &cmd.amounts {
2172                        if let &Argument::Input(i) = arg {
2173                            if let Some(x) = result_types.get_mut(i as usize) {
2174                                x.replace(MoveTypeLayout::U64);
2175                            }
2176                        }
2177                    }
2178                }
2179                Command::TransferObjects(TransferObjects {
2180                    address: Argument::Input(i),
2181                    ..
2182                }) => {
2183                    if let Some(x) = result_types.get_mut((*i) as usize) {
2184                        x.replace(MoveTypeLayout::Address);
2185                    }
2186                }
2187                _ => {}
2188            }
2189        }
2190        result_types
2191    }
2192}
2193
2194fn get_signature_types(
2195    id: ModuleId,
2196    function: &Identifier,
2197    module_cache: &impl GetModule,
2198) -> Option<Vec<Option<MoveTypeLayout>>> {
2199    use std::borrow::Borrow;
2200    if let Ok(Some(module)) = module_cache.get_module_by_id(&id) {
2201        let module: &CompiledModule = module.borrow();
2202        let func = module
2203            .function_handles
2204            .iter()
2205            .find(|f| module.identifier_at(f.name).as_str() == function.as_str())?;
2206        Some(
2207            module
2208                .signature_at(func.parameters)
2209                .0
2210                .iter()
2211                .map(|s| primitive_type(module, &[], s))
2212                .collect(),
2213        )
2214    } else {
2215        None
2216    }
2217}
2218
2219/// A single transaction in a programmable transaction block.
2220#[serde_as]
2221#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
2222#[serde(rename = "IotaTransaction")]
2223pub enum IotaCommand {
2224    /// A call to either an entry or a public Move function
2225    MoveCall(Box<IotaProgrammableMoveCall>),
2226    /// `(Vec<forall T:key+store. T>, address)`
2227    /// It sends n-objects to the specified address. These objects must have
2228    /// store (public transfer) and either the previous owner must be an
2229    /// address or the object must be newly created.
2230    TransferObjects(Vec<IotaArgument>, IotaArgument),
2231    /// `(&mut Coin<T>, Vec<u64>)` -> `Vec<Coin<T>>`
2232    /// It splits off some amounts into a new coins with those amounts
2233    SplitCoins(IotaArgument, Vec<IotaArgument>),
2234    /// `(&mut Coin<T>, Vec<Coin<T>>)`
2235    /// It merges n-coins into the first coin
2236    MergeCoins(IotaArgument, Vec<IotaArgument>),
2237    /// Publishes a Move package. It takes the package bytes and a list of the
2238    /// package's transitive dependencies to link against on-chain.
2239    Publish(
2240        #[serde_as(as = "Vec<ObjectIdSchema>")]
2241        #[schemars(with = "Vec<ObjectIdSchema>")]
2242        Vec<ObjectId>,
2243    ),
2244    /// Upgrades a Move package
2245    Upgrade(
2246        #[serde_as(as = "Vec<ObjectIdSchema>")]
2247        #[schemars(with = "Vec<ObjectIdSchema>")]
2248        Vec<ObjectId>,
2249        #[serde_as(as = "ObjectIdSchema")]
2250        #[schemars(with = "ObjectIdSchema")]
2251        ObjectId,
2252        IotaArgument,
2253    ),
2254    /// `forall T: Vec<T> -> vector<T>`
2255    /// Given n-values of the same type, it constructs a vector. For non objects
2256    /// or an empty vector, the type tag must be specified.
2257    MakeMoveVec(Option<String>, Vec<IotaArgument>),
2258}
2259
2260impl Display for IotaCommand {
2261    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
2262        match self {
2263            Self::MoveCall(p) => {
2264                write!(f, "MoveCall({p})")
2265            }
2266            Self::MakeMoveVec(ty_opt, elems) => {
2267                write!(f, "MakeMoveVec(")?;
2268                if let Some(ty) = ty_opt {
2269                    write!(f, "Some{ty}")?;
2270                } else {
2271                    write!(f, "None")?;
2272                }
2273                write!(f, ",[")?;
2274                write_sep(f, elems, None, ",")?;
2275                write!(f, "])")
2276            }
2277            Self::TransferObjects(objs, addr) => {
2278                write!(f, "TransferObjects([")?;
2279                write_sep(f, objs, None, ",")?;
2280                write!(f, "],{addr})")
2281            }
2282            Self::SplitCoins(coin, amounts) => {
2283                write!(f, "SplitCoins({coin},")?;
2284                write_sep(f, amounts, None, ",")?;
2285                write!(f, ")")
2286            }
2287            Self::MergeCoins(target, coins) => {
2288                write!(f, "MergeCoins({target},")?;
2289                write_sep(f, coins, None, ",")?;
2290                write!(f, ")")
2291            }
2292            Self::Publish(deps) => {
2293                write!(f, "Publish(<modules>,")?;
2294                write_sep(f, deps, None, ",")?;
2295                write!(f, ")")
2296            }
2297            Self::Upgrade(deps, current_package_id, ticket) => {
2298                write!(f, "Upgrade(<modules>, {ticket},")?;
2299                write_sep(f, deps, None, ",")?;
2300                write!(f, ", {current_package_id}")?;
2301                write!(f, ")")
2302            }
2303        }
2304    }
2305}
2306
2307impl TryFrom<Command> for IotaCommand {
2308    type Error = anyhow::Error;
2309
2310    fn try_from(value: Command) -> Result<Self, Self::Error> {
2311        Ok(match value {
2312            Command::MoveCall(cmd) => IotaCommand::MoveCall(Box::new(cmd.try_into()?)),
2313            Command::TransferObjects(cmd) => IotaCommand::TransferObjects(
2314                cmd.objects
2315                    .into_iter()
2316                    .map(IotaArgument::try_from)
2317                    .collect::<Result<_, _>>()?,
2318                cmd.address.try_into()?,
2319            ),
2320            Command::SplitCoins(cmd) => IotaCommand::SplitCoins(
2321                cmd.coin.try_into()?,
2322                cmd.amounts
2323                    .into_iter()
2324                    .map(IotaArgument::try_from)
2325                    .collect::<Result<_, _>>()?,
2326            ),
2327            Command::MergeCoins(cmd) => IotaCommand::MergeCoins(
2328                cmd.coin.try_into()?,
2329                cmd.coins_to_merge
2330                    .into_iter()
2331                    .map(IotaArgument::try_from)
2332                    .collect::<Result<_, _>>()?,
2333            ),
2334            Command::Publish(cmd) => IotaCommand::Publish(cmd.dependencies),
2335            Command::MakeMoveVector(cmd) => IotaCommand::MakeMoveVec(
2336                cmd.type_tag.map(|tag| tag.to_string()),
2337                cmd.elements
2338                    .into_iter()
2339                    .map(IotaArgument::try_from)
2340                    .collect::<Result<_, _>>()?,
2341            ),
2342            Command::Upgrade(cmd) => IotaCommand::Upgrade(
2343                cmd.dependencies,
2344                cmd.package,
2345                IotaArgument::try_from(cmd.ticket)?,
2346            ),
2347            _ => anyhow::bail!("unknown Command variant"),
2348        })
2349    }
2350}
2351
2352/// An argument to a transaction in a programmable transaction block
2353#[derive(Debug, Copy, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
2354pub enum IotaArgument {
2355    /// The gas coin. The gas coin can only be used by-ref, except for with
2356    /// `TransferObjects`, which can use it by-value.
2357    GasCoin,
2358    /// One of the input objects or primitive values (from
2359    /// `ProgrammableTransactionBlock` inputs)
2360    Input(u16),
2361    /// The result of another transaction (from `ProgrammableTransactionBlock`
2362    /// transactions)
2363    Result(u16),
2364    /// Like a `Result` but it accesses a nested result. Currently, the only
2365    /// usage of this is to access a value from a Move call with multiple
2366    /// return values.
2367    NestedResult(u16, u16),
2368}
2369
2370impl Display for IotaArgument {
2371    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
2372        match self {
2373            Self::GasCoin => write!(f, "GasCoin"),
2374            Self::Input(i) => write!(f, "Input({i})"),
2375            Self::Result(i) => write!(f, "Result({i})"),
2376            Self::NestedResult(i, j) => write!(f, "NestedResult({i},{j})"),
2377        }
2378    }
2379}
2380
2381impl TryFrom<Argument> for IotaArgument {
2382    type Error = anyhow::Error;
2383
2384    fn try_from(value: Argument) -> Result<Self, Self::Error> {
2385        Ok(match value {
2386            Argument::Gas => Self::GasCoin,
2387            Argument::Input(i) => Self::Input(i),
2388            Argument::Result(i) => Self::Result(i),
2389            Argument::NestedResult(i, j) => Self::NestedResult(i, j),
2390            _ => anyhow::bail!("unknown Argument variant"),
2391        })
2392    }
2393}
2394
2395#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
2396#[serde(untagged)]
2397pub enum PtbInput {
2398    PtbRef(IotaArgument),
2399    CallArg(IotaJsonValue),
2400}
2401
2402/// The transaction for calling a Move function, either an entry function or a
2403/// public function (which cannot return references).
2404#[serde_as]
2405#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
2406pub struct IotaProgrammableMoveCall {
2407    /// The package containing the module and function.
2408    #[serde_as(as = "ObjectIdSchema")]
2409    #[schemars(with = "ObjectIdSchema")]
2410    pub package: ObjectId,
2411    /// The specific module in the package containing the function.
2412    pub module: String,
2413    /// The function to be called.
2414    pub function: String,
2415    #[serde(default, skip_serializing_if = "Vec::is_empty")]
2416    /// The type arguments to the function.
2417    pub type_arguments: Vec<String>,
2418    #[serde(default, skip_serializing_if = "Vec::is_empty")]
2419    /// The arguments to the function.
2420    pub arguments: Vec<IotaArgument>,
2421}
2422
2423/// Writes `items` to `f`, joined by `separator`.
2424///
2425/// When `delimiters` is given the output is wrapped in them. An empty iterator
2426/// writes nothing at all, delimiters included.
2427pub(crate) fn write_sep<T: Display>(
2428    f: &mut Formatter<'_>,
2429    items: impl IntoIterator<Item = T>,
2430    delimiters: Option<(&str, &str)>,
2431    separator: &str,
2432) -> std::fmt::Result {
2433    let mut xs = items.into_iter();
2434    let Some(x) = xs.next() else {
2435        return Ok(());
2436    };
2437    if let Some((left, _)) = delimiters {
2438        write!(f, "{left}")?;
2439    }
2440    write!(f, "{x}")?;
2441    for x in xs {
2442        write!(f, "{separator}{x}")?;
2443    }
2444    if let Some((_, right)) = delimiters {
2445        write!(f, "{right}")?;
2446    }
2447    Ok(())
2448}
2449
2450impl Display for IotaProgrammableMoveCall {
2451    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
2452        let Self {
2453            package,
2454            module,
2455            function,
2456            type_arguments,
2457            arguments,
2458        } = self;
2459        write!(f, "{package}::{module}::{function}")?;
2460        if !type_arguments.is_empty() {
2461            write!(f, "<")?;
2462            write_sep(f, type_arguments, None, ",")?;
2463            write!(f, ">")?;
2464        }
2465        write!(f, "(")?;
2466        write_sep(f, arguments, None, ",")?;
2467        write!(f, ")")
2468    }
2469}
2470
2471impl TryFrom<MoveCall> for IotaProgrammableMoveCall {
2472    type Error = anyhow::Error;
2473
2474    fn try_from(value: MoveCall) -> Result<Self, Self::Error> {
2475        let MoveCall {
2476            package,
2477            module,
2478            function,
2479            type_arguments,
2480            arguments,
2481        } = value;
2482        Ok(Self {
2483            package,
2484            module: module.to_string(),
2485            function: function.to_string(),
2486            type_arguments: type_arguments.into_iter().map(|t| t.to_string()).collect(),
2487            arguments: arguments
2488                .into_iter()
2489                .map(IotaArgument::try_from)
2490                .collect::<Result<_, _>>()?,
2491        })
2492    }
2493}
2494
2495const fn default_shared_object_mutability() -> bool {
2496    true
2497}
2498
2499impl From<InputObjectKind> for IotaInputObjectKind {
2500    fn from(input: InputObjectKind) -> Self {
2501        match input {
2502            InputObjectKind::MovePackage(id) => Self::MovePackage(id),
2503            InputObjectKind::ImmOrOwnedMoveObject(oref) => Self::ImmOrOwnedMoveObject(oref),
2504            InputObjectKind::SharedMoveObject {
2505                id,
2506                initial_shared_version,
2507                mutable,
2508            } => Self::SharedMoveObject {
2509                id,
2510                initial_shared_version,
2511                mutable,
2512            },
2513        }
2514    }
2515}
2516
2517#[derive(Debug, Serialize, Deserialize, Clone)]
2518#[serde(rename = "TypeTag", rename_all = "camelCase")]
2519pub struct IotaTypeTag(String);
2520
2521impl IotaTypeTag {
2522    pub fn new(tag: String) -> Self {
2523        Self(tag)
2524    }
2525}
2526
2527impl AsRef<str> for IotaTypeTag {
2528    fn as_ref(&self) -> &str {
2529        &self.0
2530    }
2531}
2532
2533impl TryFrom<IotaTypeTag> for TypeTag {
2534    type Error = anyhow::Error;
2535    fn try_from(tag: IotaTypeTag) -> Result<Self, Self::Error> {
2536        parse_iota_type_tag(&tag.0)
2537    }
2538}
2539
2540impl From<TypeTag> for IotaTypeTag {
2541    fn from(tag: TypeTag) -> Self {
2542        Self(format!("{tag}"))
2543    }
2544}
2545
2546#[derive(Serialize, Deserialize, JsonSchema)]
2547#[serde(rename_all = "camelCase")]
2548pub enum RPCTransactionRequestParams {
2549    TransferObjectRequestParams(TransferObjectParams),
2550    MoveCallRequestParams(MoveCallParams),
2551}
2552
2553#[serde_as]
2554#[derive(Serialize, Deserialize, JsonSchema)]
2555#[serde(rename_all = "camelCase")]
2556pub struct TransferObjectParams {
2557    #[serde_as(as = "AddressSchema")]
2558    #[schemars(with = "AddressSchema")]
2559    pub recipient: Address,
2560    #[serde_as(as = "ObjectIdSchema")]
2561    #[schemars(with = "ObjectIdSchema")]
2562    pub object_id: ObjectId,
2563}
2564
2565#[serde_as]
2566#[derive(Serialize, Deserialize, JsonSchema)]
2567#[serde(rename_all = "camelCase")]
2568pub struct MoveCallParams {
2569    #[serde_as(as = "ObjectIdSchema")]
2570    #[schemars(with = "ObjectIdSchema")]
2571    pub package_object_id: ObjectId,
2572    pub module: String,
2573    pub function: String,
2574    #[serde(default)]
2575    #[schemars(with = "Vec<TypeTagSchema>")]
2576    pub type_arguments: Vec<IotaTypeTag>,
2577    pub arguments: Vec<PtbInput>,
2578}
2579
2580#[serde_as]
2581#[derive(Serialize, Deserialize, Clone, JsonSchema)]
2582#[serde(rename_all = "camelCase")]
2583pub struct TransactionBlockBytes {
2584    /// BCS serialized transaction data bytes without its type tag, as base-64
2585    /// encoded string.
2586    #[schemars(with = "Base64Schema")]
2587    pub tx_bytes: Base64,
2588    /// the gas objects to be used
2589    #[schemars(with = "Vec<ObjectRefSchema>")]
2590    #[serde_as(as = "Vec<ObjectRefSchema>")]
2591    pub gas: Vec<ObjectReference>,
2592    /// objects to be used in this transaction
2593    pub input_objects: Vec<IotaInputObjectKind>,
2594}
2595
2596impl TransactionBlockBytes {
2597    pub fn from_data(tx: Transaction) -> Result<Self, anyhow::Error> {
2598        Ok(Self {
2599            tx_bytes: Base64::from_bytes(&tx.to_bcs()),
2600            gas: tx.gas().to_vec(),
2601            input_objects: tx
2602                .input_objects()?
2603                .into_iter()
2604                .map(IotaInputObjectKind::from)
2605                .collect(),
2606        })
2607    }
2608
2609    pub fn to_data(self) -> Result<Transaction, anyhow::Error> {
2610        bcs::from_bytes::<Transaction>(&self.tx_bytes.to_vec().map_err(|e| anyhow::anyhow!(e))?)
2611            .map_err(|e| anyhow::anyhow!(e))
2612    }
2613}
2614
2615#[serde_as]
2616#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize, JsonSchema)]
2617#[serde(rename = "OwnedObjectRef")]
2618pub struct OwnedObjectRef {
2619    #[schemars(with = "OwnerSchema")]
2620    #[serde_as(as = "OwnerSchema")]
2621    pub owner: Owner,
2622    #[schemars(with = "ObjectRefSchema")]
2623    #[serde_as(as = "ObjectRefSchema")]
2624    pub reference: ObjectReference,
2625}
2626
2627impl OwnedObjectRef {
2628    pub fn object_id(&self) -> ObjectId {
2629        self.reference.object_id
2630    }
2631    pub fn version(&self) -> Version {
2632        self.reference.version
2633    }
2634}
2635
2636#[derive(Eq, PartialEq, Debug, Clone, Serialize, Deserialize, JsonSchema)]
2637#[serde(tag = "type", rename_all = "camelCase")]
2638pub enum IotaCallArg {
2639    // Needs to become an Object Ref or Object ID, depending on object type
2640    Object(IotaObjectArg),
2641    // pure value, bcs encoded
2642    Pure(IotaPureValue),
2643}
2644
2645impl IotaCallArg {
2646    pub fn try_from(
2647        value: CallArg,
2648        layout: Option<&MoveTypeLayout>,
2649    ) -> Result<Self, anyhow::Error> {
2650        Ok(match value {
2651            CallArg::Pure(p) => IotaCallArg::Pure(IotaPureValue {
2652                value_type: layout.map(|l| type_tag_core_to_sdk(&l.into())),
2653                value: IotaJsonValue::from_bcs_bytes(layout, &p)?,
2654            }),
2655            CallArg::ImmutableOrOwned(object_ref) => {
2656                IotaCallArg::Object(IotaObjectArg::ImmOrOwnedObject {
2657                    object_id: object_ref.object_id,
2658                    version: object_ref.version,
2659                    digest: object_ref.digest,
2660                })
2661            }
2662            CallArg::Shared(SharedObjectReference {
2663                object_id: id,
2664                initial_shared_version,
2665                mutable,
2666            }) => IotaCallArg::Object(IotaObjectArg::SharedObject {
2667                object_id: id,
2668                initial_shared_version,
2669                mutable,
2670            }),
2671            CallArg::Receiving(object_ref) => IotaCallArg::Object(IotaObjectArg::Receiving {
2672                object_id: object_ref.object_id,
2673                version: object_ref.version,
2674                digest: object_ref.digest,
2675            }),
2676            _ => anyhow::bail!("unknown CallArg variant"),
2677        })
2678    }
2679
2680    pub fn pure(&self) -> Option<&IotaJsonValue> {
2681        match self {
2682            IotaCallArg::Pure(v) => Some(&v.value),
2683            _ => None,
2684        }
2685    }
2686
2687    pub fn object(&self) -> Option<&ObjectId> {
2688        match self {
2689            IotaCallArg::Object(IotaObjectArg::SharedObject { object_id, .. })
2690            | IotaCallArg::Object(IotaObjectArg::ImmOrOwnedObject { object_id, .. })
2691            | IotaCallArg::Object(IotaObjectArg::Receiving { object_id, .. }) => Some(object_id),
2692            _ => None,
2693        }
2694    }
2695}
2696
2697#[serde_as]
2698#[derive(Eq, PartialEq, Debug, Clone, Serialize, Deserialize, JsonSchema)]
2699#[serde(rename_all = "camelCase")]
2700pub struct IotaPureValue {
2701    #[schemars(with = "Option<TypeTagSchema>")]
2702    #[serde_as(as = "Option<TypeTagSchema>")]
2703    value_type: Option<TypeTag>,
2704    value: IotaJsonValue,
2705}
2706
2707impl IotaPureValue {
2708    pub fn value(&self) -> IotaJsonValue {
2709        self.value.clone()
2710    }
2711
2712    pub fn value_type(&self) -> Option<TypeTag> {
2713        self.value_type.clone()
2714    }
2715}
2716
2717#[serde_as]
2718#[derive(Eq, PartialEq, Debug, Clone, Serialize, Deserialize, JsonSchema)]
2719#[serde(tag = "objectType", rename_all = "camelCase")]
2720pub enum IotaObjectArg {
2721    // A Move object, either immutable, or owned mutable.
2722    #[serde(rename_all = "camelCase")]
2723    ImmOrOwnedObject {
2724        #[serde_as(as = "ObjectIdSchema")]
2725        #[schemars(with = "ObjectIdSchema")]
2726        object_id: ObjectId,
2727        #[schemars(with = "SequenceNumberStringSchema")]
2728        #[serde_as(as = "SequenceNumberStringSchema")]
2729        version: Version,
2730        #[serde_as(as = "Base58Schema")]
2731        #[schemars(with = "Base58Schema")]
2732        digest: ObjectDigest,
2733    },
2734    // A Move object that's shared.
2735    // SharedObject::mutable controls whether caller asks for a mutable reference to shared
2736    // object.
2737    #[serde(rename_all = "camelCase")]
2738    SharedObject {
2739        #[serde_as(as = "ObjectIdSchema")]
2740        #[schemars(with = "ObjectIdSchema")]
2741        object_id: ObjectId,
2742        #[schemars(with = "SequenceNumberStringSchema")]
2743        #[serde_as(as = "SequenceNumberStringSchema")]
2744        initial_shared_version: Version,
2745        mutable: bool,
2746    },
2747    // A reference to a Move object that's going to be received in the transaction.
2748    #[serde(rename_all = "camelCase")]
2749    Receiving {
2750        #[serde_as(as = "ObjectIdSchema")]
2751        #[schemars(with = "ObjectIdSchema")]
2752        object_id: ObjectId,
2753        #[schemars(with = "SequenceNumberStringSchema")]
2754        #[serde_as(as = "SequenceNumberStringSchema")]
2755        version: Version,
2756        #[serde_as(as = "Base58Schema")]
2757        #[schemars(with = "Base58Schema")]
2758        digest: ObjectDigest,
2759    },
2760}
2761
2762#[derive(Clone)]
2763pub struct EffectsWithInput {
2764    pub effects: IotaTransactionBlockEffects,
2765    pub input: Transaction,
2766}
2767
2768impl From<EffectsWithInput> for IotaTransactionBlockEffects {
2769    fn from(e: EffectsWithInput) -> Self {
2770        e.effects
2771    }
2772}
2773
2774#[serde_as]
2775#[derive(Clone, Debug, JsonSchema, Serialize, Deserialize)]
2776pub enum TransactionFilter {
2777    /// Query by checkpoint.
2778    Checkpoint(
2779        #[schemars(with = "String")]
2780        #[serde_as(as = "DisplayFromStr")]
2781        CheckpointSequenceNumber,
2782    ),
2783    /// Query by move function.
2784    MoveFunction {
2785        #[serde_as(as = "ObjectIdSchema")]
2786        #[schemars(with = "ObjectIdSchema")]
2787        package: ObjectId,
2788        module: Option<String>,
2789        function: Option<String>,
2790    },
2791    /// Query by input object.
2792    InputObject(
2793        #[serde_as(as = "ObjectIdSchema")]
2794        #[schemars(with = "ObjectIdSchema")]
2795        ObjectId,
2796    ),
2797    /// Query by changed object, including created, mutated and unwrapped
2798    /// objects.
2799    ChangedObject(
2800        #[serde_as(as = "ObjectIdSchema")]
2801        #[schemars(with = "ObjectIdSchema")]
2802        ObjectId,
2803    ),
2804    /// Query by sender address.
2805    FromAddress(
2806        #[serde_as(as = "AddressSchema")]
2807        #[schemars(with = "AddressSchema")]
2808        Address,
2809    ),
2810    /// Query by recipient address.
2811    ToAddress(
2812        #[serde_as(as = "AddressSchema")]
2813        #[schemars(with = "AddressSchema")]
2814        Address,
2815    ),
2816    /// Query by sender and recipient address.
2817    FromAndToAddress {
2818        #[serde_as(as = "AddressSchema")]
2819        #[schemars(with = "AddressSchema")]
2820        from: Address,
2821        #[serde_as(as = "AddressSchema")]
2822        #[schemars(with = "AddressSchema")]
2823        to: Address,
2824    },
2825    /// Query txs that have a given address as sender or recipient.
2826    FromOrToAddress {
2827        #[serde_as(as = "AddressSchema")]
2828        #[schemars(with = "AddressSchema")]
2829        addr: Address,
2830    },
2831    /// Query by transaction kind
2832    TransactionKind(IotaTransactionKind),
2833    /// Query transactions of any given kind in the input.
2834    TransactionKindIn(Vec<IotaTransactionKind>),
2835}
2836
2837impl TransactionFilter {
2838    pub fn as_v2(&self) -> TransactionFilterV2 {
2839        match self {
2840            TransactionFilter::InputObject(o) => TransactionFilterV2::InputObject(*o),
2841            TransactionFilter::ChangedObject(o) => TransactionFilterV2::ChangedObject(*o),
2842            TransactionFilter::FromAddress(a) => TransactionFilterV2::FromAddress(*a),
2843            TransactionFilter::ToAddress(a) => TransactionFilterV2::ToAddress(*a),
2844            TransactionFilter::FromAndToAddress { from, to } => {
2845                TransactionFilterV2::FromAndToAddress {
2846                    from: *from,
2847                    to: *to,
2848                }
2849            }
2850            TransactionFilter::FromOrToAddress { addr } => {
2851                TransactionFilterV2::FromOrToAddress { addr: *addr }
2852            }
2853            TransactionFilter::MoveFunction {
2854                package,
2855                module,
2856                function,
2857            } => TransactionFilterV2::MoveFunction {
2858                package: *package,
2859                module: module.clone(),
2860                function: function.clone(),
2861            },
2862            TransactionFilter::TransactionKind(kind) => TransactionFilterV2::TransactionKind(*kind),
2863            TransactionFilter::TransactionKindIn(kinds) => {
2864                TransactionFilterV2::TransactionKindIn(kinds.clone())
2865            }
2866            TransactionFilter::Checkpoint(checkpoint) => {
2867                TransactionFilterV2::Checkpoint(*checkpoint)
2868            }
2869        }
2870    }
2871}
2872
2873impl TransactionFilter {
2874    fn transaction_kind(item: &EffectsWithInput) -> Option<IotaTransactionKind> {
2875        IotaTransactionKind::try_from(item.input.kind())
2876            .inspect_err(|e| {
2877                tracing::warn!(
2878                    digest = %item.effects.transaction_digest(),
2879                    "transaction kind filter skipped: {e}"
2880                )
2881            })
2882            .ok()
2883    }
2884}
2885
2886impl Filter<EffectsWithInput> for TransactionFilter {
2887    fn matches(&self, item: &EffectsWithInput) -> bool {
2888        match self {
2889            TransactionFilter::InputObject(o) => {
2890                let Ok(input_objects) = item.input.input_objects() else {
2891                    return false;
2892                };
2893                input_objects.iter().any(|object| object.object_id() == *o)
2894            }
2895            TransactionFilter::ChangedObject(o) => item
2896                .effects
2897                .mutated()
2898                .iter()
2899                .any(|oref: &OwnedObjectRef| &oref.reference.object_id == o),
2900            TransactionFilter::FromAddress(a) => &item.input.sender() == a,
2901            TransactionFilter::ToAddress(a) => {
2902                let mutated: &[OwnedObjectRef] = item.effects.mutated();
2903                mutated.iter().chain(item.effects.unwrapped().iter()).any(|oref: &OwnedObjectRef| {
2904                    matches!(oref.owner, Owner::Address(owner) if owner == *a)
2905                })
2906            }
2907            TransactionFilter::FromAndToAddress { from, to } => {
2908                Self::FromAddress(*from).matches(item) && Self::ToAddress(*to).matches(item)
2909            }
2910            TransactionFilter::FromOrToAddress { addr } => {
2911                Self::FromAddress(*addr).matches(item) || Self::ToAddress(*addr).matches(item)
2912            }
2913            TransactionFilter::MoveFunction {
2914                package,
2915                module,
2916                function,
2917            } => item.input.move_calls().into_iter().any(|(p, m, f)| {
2918                p == package
2919                    && (module.is_none() || matches!(module,  Some(m2) if m2 == &m.to_string()))
2920                    && (function.is_none() || matches!(function, Some(f2) if f2 == &f.to_string()))
2921            }),
2922            TransactionFilter::TransactionKind(kind) => {
2923                Self::transaction_kind(item).is_some_and(|k| k == *kind)
2924            }
2925            TransactionFilter::TransactionKindIn(kinds) => {
2926                Self::transaction_kind(item).is_some_and(|k| kinds.contains(&k))
2927            }
2928            // this filter is not supported, RPC will reject it on subscription
2929            TransactionFilter::Checkpoint(_) => false,
2930        }
2931    }
2932}
2933
2934#[serde_as]
2935#[derive(Clone, Debug, JsonSchema, Serialize, Deserialize)]
2936#[non_exhaustive]
2937pub enum TransactionFilterV2 {
2938    /// Query by checkpoint.
2939    Checkpoint(
2940        #[schemars(with = "String")]
2941        #[serde_as(as = "DisplayFromStr")]
2942        CheckpointSequenceNumber,
2943    ),
2944    /// Query by move function.
2945    MoveFunction {
2946        #[serde_as(as = "ObjectIdSchema")]
2947        #[schemars(with = "ObjectIdSchema")]
2948        package: ObjectId,
2949        module: Option<String>,
2950        function: Option<String>,
2951    },
2952    /// Query by input object.
2953    InputObject(
2954        #[serde_as(as = "ObjectIdSchema")]
2955        #[schemars(with = "ObjectIdSchema")]
2956        ObjectId,
2957    ),
2958    /// Query by changed object, including created, mutated and unwrapped
2959    /// objects.
2960    ChangedObject(
2961        #[serde_as(as = "ObjectIdSchema")]
2962        #[schemars(with = "ObjectIdSchema")]
2963        ObjectId,
2964    ),
2965    /// Query transactions that wrapped or deleted the specified object.
2966    /// Includes transactions that either created and immediately wrapped
2967    /// the object or unwrapped and immediately deleted it.
2968    WrappedOrDeletedObject(
2969        #[serde_as(as = "ObjectIdSchema")]
2970        #[schemars(with = "ObjectIdSchema")]
2971        ObjectId,
2972    ),
2973    /// Query by sender address.
2974    FromAddress(
2975        #[serde_as(as = "AddressSchema")]
2976        #[schemars(with = "AddressSchema")]
2977        Address,
2978    ),
2979    /// Query by recipient address.
2980    ToAddress(
2981        #[serde_as(as = "AddressSchema")]
2982        #[schemars(with = "AddressSchema")]
2983        Address,
2984    ),
2985    /// Query by sender and recipient address.
2986    FromAndToAddress {
2987        #[serde_as(as = "AddressSchema")]
2988        #[schemars(with = "AddressSchema")]
2989        from: Address,
2990        #[serde_as(as = "AddressSchema")]
2991        #[schemars(with = "AddressSchema")]
2992        to: Address,
2993    },
2994    /// Query txs that have a given address as sender or recipient.
2995    FromOrToAddress {
2996        #[serde_as(as = "AddressSchema")]
2997        #[schemars(with = "AddressSchema")]
2998        addr: Address,
2999    },
3000    /// Query by transaction kind
3001    TransactionKind(IotaTransactionKind),
3002    /// Query transactions of any given kind in the input.
3003    TransactionKindIn(Vec<IotaTransactionKind>),
3004}
3005
3006impl TransactionFilterV2 {
3007    pub fn as_v1(&self) -> Option<TransactionFilter> {
3008        match self {
3009            TransactionFilterV2::InputObject(o) => Some(TransactionFilter::InputObject(*o)),
3010            TransactionFilterV2::ChangedObject(o) => Some(TransactionFilter::ChangedObject(*o)),
3011            TransactionFilterV2::FromAddress(a) => Some(TransactionFilter::FromAddress(*a)),
3012            TransactionFilterV2::ToAddress(a) => Some(TransactionFilter::ToAddress(*a)),
3013            TransactionFilterV2::FromAndToAddress { from, to } => {
3014                Some(TransactionFilter::FromAndToAddress {
3015                    from: *from,
3016                    to: *to,
3017                })
3018            }
3019            TransactionFilterV2::FromOrToAddress { addr } => {
3020                Some(TransactionFilter::FromOrToAddress { addr: *addr })
3021            }
3022            TransactionFilterV2::MoveFunction {
3023                package,
3024                module,
3025                function,
3026            } => Some(TransactionFilter::MoveFunction {
3027                package: *package,
3028                module: module.clone(),
3029                function: function.clone(),
3030            }),
3031            TransactionFilterV2::TransactionKind(kind) => {
3032                Some(TransactionFilter::TransactionKind(*kind))
3033            }
3034            TransactionFilterV2::TransactionKindIn(kinds) => {
3035                Some(TransactionFilter::TransactionKindIn(kinds.clone()))
3036            }
3037            TransactionFilterV2::Checkpoint(checkpoint) => {
3038                Some(TransactionFilter::Checkpoint(*checkpoint))
3039            }
3040            // V2-only variants which do not have a V1 equivalent
3041            TransactionFilterV2::WrappedOrDeletedObject(_) => None,
3042        }
3043    }
3044}
3045
3046impl Filter<EffectsWithInput> for TransactionFilterV2 {
3047    fn matches(&self, item: &EffectsWithInput) -> bool {
3048        if let Some(v1) = self.as_v1() {
3049            return v1.matches(item);
3050        }
3051        // Fallback for new V2-only variants:
3052        match self {
3053            TransactionFilterV2::WrappedOrDeletedObject(o) => item
3054                .effects
3055                .wrapped()
3056                .iter()
3057                .chain(item.effects.deleted())
3058                .chain(item.effects.unwrapped_then_deleted())
3059                .any(|oref| &oref.object_id == o),
3060
3061            _ => false,
3062        }
3063    }
3064}
3065
3066/// Represents the type of a transaction. All transactions except
3067/// `ProgrammableTransaction` are considered system transactions.
3068#[derive(
3069    Debug, Clone, Copy, PartialEq, Eq, EnumString, Display, Serialize, Deserialize, JsonSchema,
3070)]
3071#[non_exhaustive]
3072pub enum IotaTransactionKind {
3073    /// The `SystemTransaction` variant can be used to filter for all types of
3074    /// system transactions.
3075    SystemTransaction = 0,
3076    ProgrammableTransaction = 1,
3077    Genesis = 2,
3078    ConsensusCommitPrologueV1 = 3,
3079    RandomnessStateUpdate = 5,
3080    EndOfEpochTransaction = 6,
3081    TransactionDenyRulesUpdate = 7,
3082}
3083
3084impl IotaTransactionKind {
3085    /// Returns true if the transaction is a system transaction.
3086    pub fn is_system_transaction(&self) -> bool {
3087        !matches!(self, Self::ProgrammableTransaction)
3088    }
3089}
3090
3091impl TryFrom<&TransactionKind> for IotaTransactionKind {
3092    type Error = anyhow::Error;
3093
3094    fn try_from(kind: &TransactionKind) -> Result<Self, Self::Error> {
3095        Ok(match kind {
3096            TransactionKind::Genesis(_) => Self::Genesis,
3097            TransactionKind::ConsensusCommitPrologueV1(_) => Self::ConsensusCommitPrologueV1,
3098            #[allow(deprecated)]
3099            TransactionKind::AuthenticatorStateUpdateV1Deprecated => Self::SystemTransaction,
3100            TransactionKind::RandomnessStateUpdate(_) => Self::RandomnessStateUpdate,
3101            TransactionKind::TransactionDenyRulesUpdate(_) => Self::TransactionDenyRulesUpdate,
3102            TransactionKind::EndOfEpoch(_) => Self::EndOfEpochTransaction,
3103            TransactionKind::Programmable(_) => Self::ProgrammableTransaction,
3104            _ => anyhow::bail!("unknown TransactionKind variant"),
3105        })
3106    }
3107}