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