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