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