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