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