1use std::{
6 cmp::Ordering,
7 collections::BTreeMap,
8 fmt,
9 fmt::{Display, Formatter, Write},
10};
11
12use anyhow::{anyhow, bail};
13use colored::Colorize;
14use fastcrypto::encoding::Base64;
15use iota_protocol_config::ProtocolConfig;
16use iota_types::{
17 base_types::{
18 IotaAddress, ObjectDigest, ObjectID, ObjectInfo, ObjectRef, ObjectType, SequenceNumber,
19 TransactionDigest,
20 },
21 error::{
22 ExecutionError, IotaError, IotaObjectResponseError, IotaResult, UserInputError,
23 UserInputResult,
24 },
25 gas_coin::GasCoin,
26 iota_serde::{BigInt, IotaStructTag, SequenceNumber as AsSequenceNumber},
27 messages_checkpoint::CheckpointSequenceNumber,
28 move_package::{MovePackage, TypeOrigin, UpgradeInfo},
29 object::{Data, MoveObject, Object, ObjectInner, ObjectRead, Owner},
30};
31use move_bytecode_utils::module_cache::GetModule;
32use move_core_types::{
33 annotated_value::{MoveStructLayout, MoveValue},
34 identifier::Identifier,
35 language_storage::StructTag,
36};
37use schemars::JsonSchema;
38use serde::{Deserialize, Serialize};
39use serde_json::Value;
40use serde_with::{DisplayFromStr, serde_as};
41
42use crate::{IotaMoveStruct, IotaMoveValue, Page};
43
44#[derive(Serialize, Deserialize, Debug, JsonSchema, Clone, PartialEq, Eq)]
45pub struct IotaObjectResponse {
46 #[serde(skip_serializing_if = "Option::is_none")]
47 pub data: Option<IotaObjectData>,
48 #[serde(skip_serializing_if = "Option::is_none")]
49 pub error: Option<IotaObjectResponseError>,
50}
51
52impl IotaObjectResponse {
53 pub fn new(data: Option<IotaObjectData>, error: Option<IotaObjectResponseError>) -> Self {
54 Self { data, error }
55 }
56
57 pub fn new_with_data(data: IotaObjectData) -> Self {
58 Self {
59 data: Some(data),
60 error: None,
61 }
62 }
63
64 pub fn new_with_error(error: IotaObjectResponseError) -> Self {
65 Self {
66 data: None,
67 error: Some(error),
68 }
69 }
70
71 pub fn try_from_object_read_and_options(
72 object_read: ObjectRead,
73 options: &IotaObjectDataOptions,
74 ) -> anyhow::Result<Self> {
75 match object_read {
76 ObjectRead::NotExists(id) => Ok(IotaObjectResponse::new_with_error(
77 IotaObjectResponseError::NotExists { object_id: id },
78 )),
79 ObjectRead::Exists(object_ref, o, layout) => Ok(IotaObjectResponse::new_with_data(
80 IotaObjectData::new(object_ref, o, layout, options, None)?,
81 )),
82 ObjectRead::Deleted((object_id, version, digest)) => Ok(
83 IotaObjectResponse::new_with_error(IotaObjectResponseError::Deleted {
84 object_id,
85 version,
86 digest,
87 }),
88 ),
89 }
90 }
91}
92
93impl Ord for IotaObjectResponse {
94 fn cmp(&self, other: &Self) -> Ordering {
95 match (&self.data, &other.data) {
96 (Some(data), Some(data_2)) => {
97 if data.object_id.cmp(&data_2.object_id).eq(&Ordering::Greater) {
98 return Ordering::Greater;
99 } else if data.object_id.cmp(&data_2.object_id).eq(&Ordering::Less) {
100 return Ordering::Less;
101 }
102 Ordering::Equal
103 }
104 (Some(_), None) => Ordering::Less,
107 (None, Some(_)) => Ordering::Greater,
108 _ => Ordering::Equal,
110 }
111 }
112}
113
114impl PartialOrd for IotaObjectResponse {
115 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
116 Some(self.cmp(other))
117 }
118}
119
120impl IotaObjectResponse {
121 pub fn move_object_bcs(&self) -> Option<&Vec<u8>> {
122 match &self.data {
123 Some(IotaObjectData {
124 bcs: Some(IotaRawData::MoveObject(obj)),
125 ..
126 }) => Some(&obj.bcs_bytes),
127 _ => None,
128 }
129 }
130
131 pub fn owner(&self) -> Option<Owner> {
132 if let Some(data) = &self.data {
133 return data.owner;
134 }
135 None
136 }
137
138 pub fn object_id(&self) -> Result<ObjectID, anyhow::Error> {
139 Ok(match (&self.data, &self.error) {
140 (Some(obj_data), None) => obj_data.object_id,
141 (None, Some(IotaObjectResponseError::NotExists { object_id })) => *object_id,
142 (
143 None,
144 Some(IotaObjectResponseError::Deleted {
145 object_id,
146 version: _,
147 digest: _,
148 }),
149 ) => *object_id,
150 _ => bail!(
151 "Could not get object_id, something went wrong with IotaObjectResponse construction."
152 ),
153 })
154 }
155
156 pub fn object_ref_if_exists(&self) -> Option<ObjectRef> {
157 match (&self.data, &self.error) {
158 (Some(obj_data), None) => Some(obj_data.object_ref()),
159 _ => None,
160 }
161 }
162}
163
164impl TryFrom<IotaObjectResponse> for ObjectInfo {
165 type Error = anyhow::Error;
166
167 fn try_from(value: IotaObjectResponse) -> Result<Self, Self::Error> {
168 let IotaObjectData {
169 object_id,
170 version,
171 digest,
172 type_,
173 owner,
174 previous_transaction,
175 ..
176 } = value.into_object()?;
177
178 Ok(ObjectInfo {
179 object_id,
180 version,
181 digest,
182 type_: type_.ok_or_else(|| anyhow!("Object type not found for object."))?,
183 owner: owner.ok_or_else(|| anyhow!("Owner not found for object."))?,
184 previous_transaction: previous_transaction
185 .ok_or_else(|| anyhow!("Transaction digest not found for object."))?,
186 })
187 }
188}
189
190#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, Eq, PartialEq)]
191pub struct DisplayFieldsResponse {
192 pub data: Option<BTreeMap<String, String>>,
193 pub error: Option<IotaObjectResponseError>,
194}
195
196#[serde_as]
197#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, Eq, PartialEq)]
198#[serde(rename_all = "camelCase", rename = "ObjectData")]
199pub struct IotaObjectData {
200 pub object_id: ObjectID,
201 #[schemars(with = "AsSequenceNumber")]
203 #[serde_as(as = "AsSequenceNumber")]
204 pub version: SequenceNumber,
205 pub digest: ObjectDigest,
207 #[schemars(with = "Option<String>")]
210 #[serde_as(as = "Option<DisplayFromStr>")]
211 #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
212 pub type_: Option<ObjectType>,
213 #[serde(skip_serializing_if = "Option::is_none")]
217 pub owner: Option<Owner>,
218 #[serde(skip_serializing_if = "Option::is_none")]
222 pub previous_transaction: Option<TransactionDigest>,
223 #[schemars(with = "Option<BigInt<u64>>")]
227 #[serde_as(as = "Option<BigInt<u64>>")]
228 #[serde(skip_serializing_if = "Option::is_none")]
229 pub storage_rebate: Option<u64>,
230 #[serde(skip_serializing_if = "Option::is_none")]
234 pub display: Option<DisplayFieldsResponse>,
235 #[serde(skip_serializing_if = "Option::is_none")]
238 pub content: Option<IotaParsedData>,
239 #[serde(skip_serializing_if = "Option::is_none")]
242 pub bcs: Option<IotaRawData>,
243}
244
245impl IotaObjectData {
246 pub fn new(
247 object_ref: ObjectRef,
248 obj: Object,
249 layout: impl Into<Option<MoveStructLayout>>,
250 options: &IotaObjectDataOptions,
251 display_fields: impl Into<Option<DisplayFieldsResponse>>,
252 ) -> anyhow::Result<Self> {
253 let layout = layout.into();
254 let display_fields = display_fields.into();
255 let show_display = options.show_display;
256 let IotaObjectDataOptions {
257 show_type,
258 show_owner,
259 show_previous_transaction,
260 show_content,
261 show_bcs,
262 show_storage_rebate,
263 ..
264 } = options;
265
266 let (object_id, version, digest) = object_ref;
267 let type_ = if *show_type {
268 Some(Into::<ObjectType>::into(&obj))
269 } else {
270 None
271 };
272
273 let bcs: Option<IotaRawData> = if *show_bcs {
274 let data = match obj.data.clone() {
275 Data::Move(m) => {
276 let layout = layout.clone().ok_or_else(|| {
277 anyhow!("Layout is required to convert Move object to json")
278 })?;
279 IotaRawData::try_from_object(m, layout)?
280 }
281 Data::Package(p) => IotaRawData::try_from_package(p)
282 .map_err(|e| anyhow!("Error getting raw data from package: {e:#?}"))?,
283 };
284 Some(data)
285 } else {
286 None
287 };
288
289 let obj = obj.into_inner();
290
291 let content: Option<IotaParsedData> = if *show_content {
292 let data = match obj.data {
293 Data::Move(m) => {
294 let layout = layout.ok_or_else(|| {
295 anyhow!("Layout is required to convert Move object to json")
296 })?;
297 IotaParsedData::try_from_object(m, layout)?
298 }
299 Data::Package(p) => IotaParsedData::try_from_package(p)?,
300 };
301 Some(data)
302 } else {
303 None
304 };
305
306 Ok(IotaObjectData {
307 object_id,
308 version,
309 digest,
310 type_,
311 owner: if *show_owner { Some(obj.owner) } else { None },
312 storage_rebate: if *show_storage_rebate {
313 Some(obj.storage_rebate)
314 } else {
315 None
316 },
317 previous_transaction: if *show_previous_transaction {
318 Some(obj.previous_transaction)
319 } else {
320 None
321 },
322 content,
323 bcs,
324 display: if show_display { display_fields } else { None },
325 })
326 }
327
328 pub fn object_ref(&self) -> ObjectRef {
329 (self.object_id, self.version, self.digest)
330 }
331
332 pub fn object_type(&self) -> anyhow::Result<ObjectType> {
333 self.type_
334 .as_ref()
335 .ok_or_else(|| anyhow!("type is missing for object {:?}", self.object_id))
336 .cloned()
337 }
338
339 pub fn is_gas_coin(&self) -> bool {
340 match self.type_.as_ref() {
341 Some(ObjectType::Struct(ty)) if ty.is_gas_coin() => true,
342 Some(_) => false,
343 None => false,
344 }
345 }
346}
347
348impl Display for IotaObjectData {
349 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
350 let type_ = if let Some(type_) = &self.type_ {
351 type_.to_string()
352 } else {
353 "Unknown Type".into()
354 };
355 let mut writer = String::new();
356 writeln!(
357 writer,
358 "{}",
359 format!("----- {type_} ({}[{}]) -----", self.object_id, self.version).bold()
360 )?;
361 if let Some(owner) = self.owner {
362 writeln!(writer, "{}: {owner}", "Owner".bold().bright_black())?;
363 }
364
365 writeln!(
366 writer,
367 "{}: {}",
368 "Version".bold().bright_black(),
369 self.version
370 )?;
371 if let Some(storage_rebate) = self.storage_rebate {
372 writeln!(
373 writer,
374 "{}: {storage_rebate}",
375 "Storage Rebate".bold().bright_black(),
376 )?;
377 }
378
379 if let Some(previous_transaction) = self.previous_transaction {
380 writeln!(
381 writer,
382 "{}: {previous_transaction:?}",
383 "Previous Transaction".bold().bright_black(),
384 )?;
385 }
386 if let Some(content) = self.content.as_ref() {
387 writeln!(writer, "{}", "----- Data -----".bold())?;
388 write!(writer, "{content}")?;
389 }
390
391 write!(f, "{writer}")
392 }
393}
394
395impl TryFrom<&IotaObjectData> for GasCoin {
396 type Error = anyhow::Error;
397 fn try_from(object: &IotaObjectData) -> Result<Self, Self::Error> {
398 match &object
399 .content
400 .as_ref()
401 .ok_or_else(|| anyhow!("Expect object content to not be empty"))?
402 {
403 IotaParsedData::MoveObject(o) => {
404 if GasCoin::type_() == o.type_ {
405 return GasCoin::try_from(&o.fields);
406 }
407 }
408 IotaParsedData::Package(_) => {}
409 }
410
411 bail!("Gas object type is not a gas coin: {:?}", object.type_)
412 }
413}
414
415impl TryFrom<&IotaMoveStruct> for GasCoin {
416 type Error = anyhow::Error;
417 fn try_from(move_struct: &IotaMoveStruct) -> Result<Self, Self::Error> {
418 match move_struct {
419 IotaMoveStruct::WithFields(fields) | IotaMoveStruct::WithTypes { type_: _, fields } => {
420 if let Some(IotaMoveValue::String(balance)) = fields.get("balance") {
421 if let Ok(balance) = balance.parse::<u64>() {
422 if let Some(IotaMoveValue::UID { id }) = fields.get("id") {
423 return Ok(GasCoin::new(*id, balance));
424 }
425 }
426 }
427 }
428 _ => {}
429 }
430 bail!("Struct is not a gas coin: {move_struct:?}")
431 }
432}
433
434#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, Eq, PartialEq, Default)]
435#[serde(rename_all = "camelCase", rename = "ObjectDataOptions", default)]
436pub struct IotaObjectDataOptions {
437 pub show_type: bool,
439 pub show_owner: bool,
441 pub show_previous_transaction: bool,
444 pub show_display: bool,
447 pub show_content: bool,
450 pub show_bcs: bool,
452 pub show_storage_rebate: bool,
454}
455
456impl IotaObjectDataOptions {
457 pub fn new() -> Self {
458 Self::default()
459 }
460
461 pub fn bcs_lossless() -> Self {
463 Self {
464 show_bcs: true,
465 show_type: true,
466 show_owner: true,
467 show_previous_transaction: true,
468 show_display: false,
469 show_content: false,
470 show_storage_rebate: true,
471 }
472 }
473
474 pub fn full_content() -> Self {
476 Self {
477 show_bcs: false,
478 show_type: true,
479 show_owner: true,
480 show_previous_transaction: true,
481 show_display: false,
482 show_content: true,
483 show_storage_rebate: true,
484 }
485 }
486
487 pub fn with_content(mut self) -> Self {
488 self.show_content = true;
489 self
490 }
491
492 pub fn with_owner(mut self) -> Self {
493 self.show_owner = true;
494 self
495 }
496
497 pub fn with_type(mut self) -> Self {
498 self.show_type = true;
499 self
500 }
501
502 pub fn with_display(mut self) -> Self {
503 self.show_display = true;
504 self
505 }
506
507 pub fn with_bcs(mut self) -> Self {
508 self.show_bcs = true;
509 self
510 }
511
512 pub fn with_previous_transaction(mut self) -> Self {
513 self.show_previous_transaction = true;
514 self
515 }
516
517 pub fn is_not_in_object_info(&self) -> bool {
518 self.show_bcs || self.show_content || self.show_display || self.show_storage_rebate
519 }
520}
521
522impl TryFrom<(ObjectRead, IotaObjectDataOptions)> for IotaObjectResponse {
523 type Error = anyhow::Error;
524
525 fn try_from(
526 (object_read, options): (ObjectRead, IotaObjectDataOptions),
527 ) -> Result<Self, Self::Error> {
528 Self::try_from_object_read_and_options(object_read, &options)
529 }
530}
531
532impl TryFrom<(ObjectInfo, IotaObjectDataOptions)> for IotaObjectResponse {
533 type Error = anyhow::Error;
534
535 fn try_from(
536 (object_info, options): (ObjectInfo, IotaObjectDataOptions),
537 ) -> Result<Self, Self::Error> {
538 let IotaObjectDataOptions {
539 show_type,
540 show_owner,
541 show_previous_transaction,
542 ..
543 } = options;
544
545 Ok(Self::new_with_data(IotaObjectData {
546 object_id: object_info.object_id,
547 version: object_info.version,
548 digest: object_info.digest,
549 type_: show_type.then_some(object_info.type_),
550 owner: show_owner.then_some(object_info.owner),
551 previous_transaction: show_previous_transaction
552 .then_some(object_info.previous_transaction),
553 storage_rebate: None,
554 display: None,
555 content: None,
556 bcs: None,
557 }))
558 }
559}
560
561impl IotaObjectResponse {
562 pub fn object(&self) -> Result<&IotaObjectData, IotaObjectResponseError> {
565 if let Some(data) = &self.data {
566 Ok(data)
567 } else if let Some(error) = &self.error {
568 Err(error.clone())
569 } else {
570 Err(IotaObjectResponseError::Unknown)
573 }
574 }
575
576 pub fn into_object(self) -> Result<IotaObjectData, IotaObjectResponseError> {
579 match self.object() {
580 Ok(data) => Ok(data.clone()),
581 Err(error) => Err(error),
582 }
583 }
584}
585
586impl TryInto<Object> for IotaObjectData {
587 type Error = anyhow::Error;
588
589 fn try_into(self) -> Result<Object, Self::Error> {
590 let protocol_config = ProtocolConfig::get_for_min_version();
591 let data = match self.bcs {
592 Some(IotaRawData::MoveObject(o)) => Data::Move({
593 MoveObject::new_from_execution(
594 o.type_().clone().into(),
595 o.version,
596 o.bcs_bytes,
597 &protocol_config,
598 )?
599 }),
600 Some(IotaRawData::Package(p)) => Data::Package(MovePackage::new(
601 p.id,
602 self.version,
603 p.module_map,
604 protocol_config.max_move_package_size(),
605 p.type_origin_table,
606 p.linkage_table,
607 )?),
608 _ => Err(anyhow!(
609 "BCS data is required to convert IotaObjectData to Object"
610 ))?,
611 };
612 Ok(ObjectInner {
613 data,
614 owner: self
615 .owner
616 .ok_or_else(|| anyhow!("Owner is required to convert IotaObjectData to Object"))?,
617 previous_transaction: self.previous_transaction.ok_or_else(|| {
618 anyhow!("previous_transaction is required to convert IotaObjectData to Object")
619 })?,
620 storage_rebate: self.storage_rebate.ok_or_else(|| {
621 anyhow!("storage_rebate is required to convert IotaObjectData to Object")
622 })?,
623 }
624 .into())
625 }
626}
627
628#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, Eq, PartialEq, Ord, PartialOrd)]
629#[serde(rename_all = "camelCase", rename = "ObjectRef")]
630pub struct IotaObjectRef {
631 pub object_id: ObjectID,
633 pub version: SequenceNumber,
635 pub digest: ObjectDigest,
637}
638
639impl IotaObjectRef {
640 pub fn to_object_ref(&self) -> ObjectRef {
641 (self.object_id, self.version, self.digest)
642 }
643}
644
645impl Display for IotaObjectRef {
646 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
647 write!(
648 f,
649 "Object ID: {}, version: {}, digest: {}",
650 self.object_id, self.version, self.digest
651 )
652 }
653}
654
655impl From<ObjectRef> for IotaObjectRef {
656 fn from(oref: ObjectRef) -> Self {
657 Self {
658 object_id: oref.0,
659 version: oref.1,
660 digest: oref.2,
661 }
662 }
663}
664
665pub trait IotaData: Sized {
666 type ObjectType;
667 type PackageType;
668 fn try_from_object(object: MoveObject, layout: MoveStructLayout)
669 -> Result<Self, anyhow::Error>;
670 fn try_from_package(package: MovePackage) -> Result<Self, anyhow::Error>;
671 fn try_as_move(&self) -> Option<&Self::ObjectType>;
672 fn try_into_move(self) -> Option<Self::ObjectType>;
673 fn try_as_package(&self) -> Option<&Self::PackageType>;
674 fn type_(&self) -> Option<&StructTag>;
675}
676
677#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, Eq, PartialEq)]
678#[serde(tag = "dataType", rename_all = "camelCase", rename = "RawData")]
679pub enum IotaRawData {
680 MoveObject(IotaRawMoveObject),
682 Package(IotaRawMovePackage),
683}
684
685impl IotaData for IotaRawData {
686 type ObjectType = IotaRawMoveObject;
687 type PackageType = IotaRawMovePackage;
688
689 fn try_from_object(object: MoveObject, _: MoveStructLayout) -> Result<Self, anyhow::Error> {
690 Ok(Self::MoveObject(object.into()))
691 }
692
693 fn try_from_package(package: MovePackage) -> Result<Self, anyhow::Error> {
694 Ok(Self::Package(package.into()))
695 }
696
697 fn try_as_move(&self) -> Option<&Self::ObjectType> {
698 match self {
699 Self::MoveObject(o) => Some(o),
700 Self::Package(_) => None,
701 }
702 }
703
704 fn try_into_move(self) -> Option<Self::ObjectType> {
705 match self {
706 Self::MoveObject(o) => Some(o),
707 Self::Package(_) => None,
708 }
709 }
710
711 fn try_as_package(&self) -> Option<&Self::PackageType> {
712 match self {
713 Self::MoveObject(_) => None,
714 Self::Package(p) => Some(p),
715 }
716 }
717
718 fn type_(&self) -> Option<&StructTag> {
719 match self {
720 Self::MoveObject(o) => Some(&o.type_),
721 Self::Package(_) => None,
722 }
723 }
724}
725
726#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, Eq, PartialEq)]
727#[serde(tag = "dataType", rename_all = "camelCase", rename = "Data")]
728pub enum IotaParsedData {
729 MoveObject(IotaParsedMoveObject),
731 Package(IotaMovePackage),
732}
733
734impl IotaData for IotaParsedData {
735 type ObjectType = IotaParsedMoveObject;
736 type PackageType = IotaMovePackage;
737
738 fn try_from_object(
739 object: MoveObject,
740 layout: MoveStructLayout,
741 ) -> Result<Self, anyhow::Error> {
742 Ok(Self::MoveObject(IotaParsedMoveObject::try_from_layout(
743 object, layout,
744 )?))
745 }
746
747 fn try_from_package(package: MovePackage) -> Result<Self, anyhow::Error> {
748 let mut disassembled = BTreeMap::new();
749 for bytecode in package.serialized_module_map().values() {
750 let module = move_binary_format::CompiledModule::deserialize_with_defaults(bytecode)
753 .map_err(|error| IotaError::ModuleDeserializationFailure {
754 error: error.to_string(),
755 })?;
756 let d = move_disassembler::disassembler::Disassembler::from_module(
757 &module,
758 move_ir_types::location::Spanned::unsafe_no_loc(()).loc,
759 )
760 .map_err(|e| IotaError::ObjectSerialization {
761 error: e.to_string(),
762 })?;
763 let bytecode_str = d
764 .disassemble()
765 .map_err(|e| IotaError::ObjectSerialization {
766 error: e.to_string(),
767 })?;
768 disassembled.insert(module.name().to_string(), Value::String(bytecode_str));
769 }
770
771 Ok(Self::Package(IotaMovePackage { disassembled }))
772 }
773
774 fn try_as_move(&self) -> Option<&Self::ObjectType> {
775 match self {
776 Self::MoveObject(o) => Some(o),
777 Self::Package(_) => None,
778 }
779 }
780
781 fn try_into_move(self) -> Option<Self::ObjectType> {
782 match self {
783 Self::MoveObject(o) => Some(o),
784 Self::Package(_) => None,
785 }
786 }
787
788 fn try_as_package(&self) -> Option<&Self::PackageType> {
789 match self {
790 Self::MoveObject(_) => None,
791 Self::Package(p) => Some(p),
792 }
793 }
794
795 fn type_(&self) -> Option<&StructTag> {
796 match self {
797 Self::MoveObject(o) => Some(&o.type_),
798 Self::Package(_) => None,
799 }
800 }
801}
802
803impl Display for IotaParsedData {
804 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
805 let mut writer = String::new();
806 match self {
807 IotaParsedData::MoveObject(o) => {
808 writeln!(writer, "{}: {}", "type".bold().bright_black(), o.type_)?;
809 write!(writer, "{}", &o.fields)?;
810 }
811 IotaParsedData::Package(p) => {
812 write!(
813 writer,
814 "{}: {:?}",
815 "Modules".bold().bright_black(),
816 p.disassembled.keys()
817 )?;
818 }
819 }
820 write!(f, "{writer}")
821 }
822}
823
824impl IotaParsedData {
825 pub fn try_from_object_read(object_read: ObjectRead) -> Result<Self, anyhow::Error> {
826 match object_read {
827 ObjectRead::NotExists(id) => Err(anyhow::anyhow!("Object {id} does not exist")),
828 ObjectRead::Exists(_object_ref, o, layout) => {
829 let data = match o.into_inner().data {
830 Data::Move(m) => {
831 let layout = layout.ok_or_else(|| {
832 anyhow!("Layout is required to convert Move object to json")
833 })?;
834 IotaParsedData::try_from_object(m, layout)?
835 }
836 Data::Package(p) => IotaParsedData::try_from_package(p)?,
837 };
838 Ok(data)
839 }
840 ObjectRead::Deleted((object_id, version, digest)) => Err(anyhow::anyhow!(
841 "Object {object_id} was deleted at version {version} with digest {digest}"
842 )),
843 }
844 }
845}
846
847pub trait IotaMoveObject: Sized {
848 fn try_from_layout(object: MoveObject, layout: MoveStructLayout)
849 -> Result<Self, anyhow::Error>;
850
851 fn try_from(o: MoveObject, resolver: &impl GetModule) -> Result<Self, anyhow::Error> {
852 let layout = o.get_layout(resolver)?;
853 Self::try_from_layout(o, layout)
854 }
855
856 fn type_(&self) -> &StructTag;
857}
858
859#[serde_as]
860#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, Eq, PartialEq)]
861#[serde(rename = "MoveObject", rename_all = "camelCase")]
862pub struct IotaParsedMoveObject {
863 #[serde(rename = "type")]
864 #[serde_as(as = "IotaStructTag")]
865 #[schemars(with = "String")]
866 pub type_: StructTag,
867 pub fields: IotaMoveStruct,
868}
869
870impl IotaMoveObject for IotaParsedMoveObject {
871 fn try_from_layout(
872 object: MoveObject,
873 layout: MoveStructLayout,
874 ) -> Result<Self, anyhow::Error> {
875 let move_struct = object.to_move_struct(&layout)?.into();
876
877 Ok(
878 if let IotaMoveStruct::WithTypes { type_, fields } = move_struct {
879 IotaParsedMoveObject {
880 type_,
881 fields: IotaMoveStruct::WithFields(fields),
882 }
883 } else {
884 IotaParsedMoveObject {
885 type_: object.type_().clone().into(),
886 fields: move_struct,
887 }
888 },
889 )
890 }
891
892 fn type_(&self) -> &StructTag {
893 &self.type_
894 }
895}
896
897impl IotaParsedMoveObject {
898 pub fn try_from_object_read(object_read: ObjectRead) -> Result<Self, anyhow::Error> {
899 let parsed_data = IotaParsedData::try_from_object_read(object_read)?;
900 match parsed_data {
901 IotaParsedData::MoveObject(o) => Ok(o),
902 IotaParsedData::Package(_) => Err(anyhow::anyhow!("Object is not a Move object")),
903 }
904 }
905
906 pub fn read_dynamic_field_value(&self, field_name: &str) -> Option<IotaMoveValue> {
907 match &self.fields {
908 IotaMoveStruct::WithFields(fields) => fields.get(field_name).cloned(),
909 IotaMoveStruct::WithTypes { fields, .. } => fields.get(field_name).cloned(),
910 _ => None,
911 }
912 }
913}
914
915pub fn type_and_fields_from_move_event_data(
916 event_data: MoveValue,
917) -> IotaResult<(StructTag, serde_json::Value)> {
918 match event_data.into() {
919 IotaMoveValue::Struct(move_struct) => match &move_struct {
920 IotaMoveStruct::WithTypes { type_, .. } => {
921 Ok((type_.clone(), move_struct.clone().to_json_value()))
922 }
923 _ => Err(IotaError::ObjectDeserialization {
924 error: "Found non-type IotaMoveStruct in MoveValue event".to_string(),
925 }),
926 },
927 IotaMoveValue::Variant(v) => Ok((v.type_.clone(), v.clone().to_json_value())),
928 IotaMoveValue::Vector(_)
929 | IotaMoveValue::Number(_)
930 | IotaMoveValue::Bool(_)
931 | IotaMoveValue::Address(_)
932 | IotaMoveValue::String(_)
933 | IotaMoveValue::UID { .. }
934 | IotaMoveValue::Option(_) => Err(IotaError::ObjectDeserialization {
935 error: "Invalid MoveValue event type -- this should not be possible".to_string(),
936 }),
937 }
938}
939
940#[serde_as]
941#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, Eq, PartialEq)]
942#[serde(rename = "RawMoveObject", rename_all = "camelCase")]
943pub struct IotaRawMoveObject {
944 #[schemars(with = "String")]
945 #[serde(rename = "type")]
946 #[serde_as(as = "IotaStructTag")]
947 pub type_: StructTag,
948 pub version: SequenceNumber,
949 #[serde_as(as = "Base64")]
950 #[schemars(with = "Base64")]
951 pub bcs_bytes: Vec<u8>,
952}
953
954impl From<MoveObject> for IotaRawMoveObject {
955 fn from(o: MoveObject) -> Self {
956 Self {
957 type_: o.type_().clone().into(),
958 version: o.version(),
959 bcs_bytes: o.into_contents(),
960 }
961 }
962}
963
964impl IotaMoveObject for IotaRawMoveObject {
965 fn try_from_layout(
966 object: MoveObject,
967 _layout: MoveStructLayout,
968 ) -> Result<Self, anyhow::Error> {
969 Ok(Self {
970 type_: object.type_().clone().into(),
971 version: object.version(),
972 bcs_bytes: object.into_contents(),
973 })
974 }
975
976 fn type_(&self) -> &StructTag {
977 &self.type_
978 }
979}
980
981impl IotaRawMoveObject {
982 pub fn deserialize<'a, T: Deserialize<'a>>(&'a self) -> Result<T, anyhow::Error> {
983 Ok(bcs::from_bytes(self.bcs_bytes.as_slice())?)
984 }
985}
986
987#[serde_as]
988#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, Eq, PartialEq)]
989#[serde(rename = "RawMovePackage", rename_all = "camelCase")]
990pub struct IotaRawMovePackage {
991 pub id: ObjectID,
992 pub version: SequenceNumber,
993 #[schemars(with = "BTreeMap<String, Base64>")]
994 #[serde_as(as = "BTreeMap<_, Base64>")]
995 pub module_map: BTreeMap<String, Vec<u8>>,
996 pub type_origin_table: Vec<TypeOrigin>,
997 pub linkage_table: BTreeMap<ObjectID, UpgradeInfo>,
998}
999
1000impl From<MovePackage> for IotaRawMovePackage {
1001 fn from(p: MovePackage) -> Self {
1002 Self {
1003 id: p.id(),
1004 version: p.version(),
1005 module_map: p.serialized_module_map().clone(),
1006 type_origin_table: p.type_origin_table().clone(),
1007 linkage_table: p.linkage_table().clone(),
1008 }
1009 }
1010}
1011
1012impl IotaRawMovePackage {
1013 pub fn to_move_package(
1014 &self,
1015 max_move_package_size: u64,
1016 ) -> Result<MovePackage, ExecutionError> {
1017 MovePackage::new(
1018 self.id,
1019 self.version,
1020 self.module_map.clone(),
1021 max_move_package_size,
1022 self.type_origin_table.clone(),
1023 self.linkage_table.clone(),
1024 )
1025 }
1026}
1027
1028#[derive(Serialize, Deserialize, Debug, JsonSchema, Clone, PartialEq, Eq)]
1029#[serde(tag = "status", content = "details", rename = "ObjectRead")]
1030#[expect(clippy::large_enum_variant)]
1031pub enum IotaPastObjectResponse {
1032 VersionFound(IotaObjectData),
1034 ObjectNotExists(ObjectID),
1036 ObjectDeleted(IotaObjectRef),
1038 VersionNotFound(ObjectID, SequenceNumber),
1040 VersionTooHigh {
1042 object_id: ObjectID,
1043 asked_version: SequenceNumber,
1044 latest_version: SequenceNumber,
1045 },
1046}
1047
1048impl IotaPastObjectResponse {
1049 pub fn object(&self) -> UserInputResult<&IotaObjectData> {
1051 match &self {
1052 Self::ObjectDeleted(oref) => Err(UserInputError::ObjectDeleted {
1053 object_ref: oref.to_object_ref(),
1054 }),
1055 Self::ObjectNotExists(id) => Err(UserInputError::ObjectNotFound {
1056 object_id: *id,
1057 version: None,
1058 }),
1059 Self::VersionFound(o) => Ok(o),
1060 Self::VersionNotFound(id, seq_num) => Err(UserInputError::ObjectNotFound {
1061 object_id: *id,
1062 version: Some(*seq_num),
1063 }),
1064 Self::VersionTooHigh {
1065 object_id,
1066 asked_version,
1067 latest_version,
1068 } => Err(UserInputError::ObjectSequenceNumberTooHigh {
1069 object_id: *object_id,
1070 asked_version: *asked_version,
1071 latest_version: *latest_version,
1072 }),
1073 }
1074 }
1075
1076 pub fn into_object(self) -> UserInputResult<IotaObjectData> {
1078 match self {
1079 Self::ObjectDeleted(oref) => Err(UserInputError::ObjectDeleted {
1080 object_ref: oref.to_object_ref(),
1081 }),
1082 Self::ObjectNotExists(id) => Err(UserInputError::ObjectNotFound {
1083 object_id: id,
1084 version: None,
1085 }),
1086 Self::VersionFound(o) => Ok(o),
1087 Self::VersionNotFound(object_id, version) => Err(UserInputError::ObjectNotFound {
1088 object_id,
1089 version: Some(version),
1090 }),
1091 Self::VersionTooHigh {
1092 object_id,
1093 asked_version,
1094 latest_version,
1095 } => Err(UserInputError::ObjectSequenceNumberTooHigh {
1096 object_id,
1097 asked_version,
1098 latest_version,
1099 }),
1100 }
1101 }
1102}
1103
1104#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, Eq, PartialEq)]
1105#[serde(rename = "MovePackage", rename_all = "camelCase")]
1106pub struct IotaMovePackage {
1107 pub disassembled: BTreeMap<String, Value>,
1108}
1109
1110pub type QueryObjectsPage = Page<IotaObjectResponse, CheckpointedObjectID>;
1111pub type ObjectsPage = Page<IotaObjectResponse, ObjectID>;
1112
1113#[serde_as]
1114#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, Copy, Eq, PartialEq)]
1115#[serde(rename_all = "camelCase")]
1116pub struct CheckpointedObjectID {
1117 pub object_id: ObjectID,
1118 #[schemars(with = "Option<BigInt<u64>>")]
1119 #[serde_as(as = "Option<BigInt<u64>>")]
1120 #[serde(skip_serializing_if = "Option::is_none")]
1121 pub at_checkpoint: Option<CheckpointSequenceNumber>,
1122}
1123
1124#[serde_as]
1125#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, Eq, PartialEq)]
1126#[serde(rename = "GetPastObjectRequest", rename_all = "camelCase")]
1127pub struct IotaGetPastObjectRequest {
1128 pub object_id: ObjectID,
1130 #[schemars(with = "AsSequenceNumber")]
1132 #[serde_as(as = "AsSequenceNumber")]
1133 pub version: SequenceNumber,
1134}
1135
1136#[serde_as]
1137#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
1138pub enum IotaObjectDataFilter {
1139 MatchAll(Vec<IotaObjectDataFilter>),
1140 MatchAny(Vec<IotaObjectDataFilter>),
1141 MatchNone(Vec<IotaObjectDataFilter>),
1142 Package(ObjectID),
1144 MoveModule {
1146 package: ObjectID,
1148 #[schemars(with = "String")]
1150 #[serde_as(as = "DisplayFromStr")]
1151 module: Identifier,
1152 },
1153 StructType(
1155 #[schemars(with = "String")]
1156 #[serde_as(as = "IotaStructTag")]
1157 StructTag,
1158 ),
1159 AddressOwner(IotaAddress),
1160 ObjectOwner(ObjectID),
1161 ObjectId(ObjectID),
1162 ObjectIds(Vec<ObjectID>),
1164 Version(
1165 #[schemars(with = "BigInt<u64>")]
1166 #[serde_as(as = "BigInt<u64>")]
1167 u64,
1168 ),
1169}
1170
1171impl IotaObjectDataFilter {
1172 pub fn gas_coin() -> Self {
1173 Self::StructType(GasCoin::type_())
1174 }
1175
1176 pub fn and(self, other: Self) -> Self {
1177 Self::MatchAll(vec![self, other])
1178 }
1179 pub fn or(self, other: Self) -> Self {
1180 Self::MatchAny(vec![self, other])
1181 }
1182 pub fn not(self, other: Self) -> Self {
1183 Self::MatchNone(vec![self, other])
1184 }
1185
1186 pub fn matches(&self, object: &ObjectInfo) -> bool {
1187 match self {
1188 IotaObjectDataFilter::MatchAll(filters) => !filters.iter().any(|f| !f.matches(object)),
1189 IotaObjectDataFilter::MatchAny(filters) => filters.iter().any(|f| f.matches(object)),
1190 IotaObjectDataFilter::MatchNone(filters) => !filters.iter().any(|f| f.matches(object)),
1191 IotaObjectDataFilter::StructType(s) => {
1192 let obj_tag: StructTag = match &object.type_ {
1193 ObjectType::Package => return false,
1194 ObjectType::Struct(s) => s.clone().into(),
1195 };
1196 if !s.type_params.is_empty() && s.type_params != obj_tag.type_params {
1199 false
1200 } else {
1201 obj_tag.address == s.address
1202 && obj_tag.module == s.module
1203 && obj_tag.name == s.name
1204 }
1205 }
1206 IotaObjectDataFilter::MoveModule { package, module } => {
1207 matches!(&object.type_, ObjectType::Struct(s) if &ObjectID::from(s.address()) == package
1208 && s.module() == module.as_ident_str())
1209 }
1210 IotaObjectDataFilter::Package(p) => {
1211 matches!(&object.type_, ObjectType::Struct(s) if &ObjectID::from(s.address()) == p)
1212 }
1213 IotaObjectDataFilter::AddressOwner(a) => {
1214 matches!(object.owner, Owner::AddressOwner(addr) if &addr == a)
1215 }
1216 IotaObjectDataFilter::ObjectOwner(o) => {
1217 matches!(object.owner, Owner::ObjectOwner(addr) if addr == IotaAddress::from(*o))
1218 }
1219 IotaObjectDataFilter::ObjectId(id) => &object.object_id == id,
1220 IotaObjectDataFilter::ObjectIds(ids) => ids.contains(&object.object_id),
1221 IotaObjectDataFilter::Version(v) => object.version.value() == *v,
1222 }
1223 }
1224}
1225
1226#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, Default)]
1227#[serde(rename_all = "camelCase", rename = "ObjectResponseQuery", default)]
1228pub struct IotaObjectResponseQuery {
1229 pub filter: Option<IotaObjectDataFilter>,
1231 pub options: Option<IotaObjectDataOptions>,
1234}
1235
1236impl IotaObjectResponseQuery {
1237 pub fn new(
1238 filter: Option<IotaObjectDataFilter>,
1239 options: Option<IotaObjectDataOptions>,
1240 ) -> Self {
1241 Self { filter, options }
1242 }
1243
1244 pub fn new_with_filter(filter: IotaObjectDataFilter) -> Self {
1245 Self {
1246 filter: Some(filter),
1247 options: None,
1248 }
1249 }
1250
1251 pub fn new_with_options(options: IotaObjectDataOptions) -> Self {
1252 Self {
1253 filter: None,
1254 options: Some(options),
1255 }
1256 }
1257}