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, MovePackage, MoveStruct, ObjectData, ObjectDigest, ObjectId,
18 ObjectReference, Owner, StructTag, TransactionDigest, TypeOrigin, UpgradeInfo, Version,
19};
20use iota_types::{
21 base_types::{ObjectInfo, ObjectType},
22 error::{IotaError, IotaResult, UserInputError, UserInputResult},
23 gas_coin::GasCoin,
24 messages_checkpoint::CheckpointSequenceNumber,
25 object::{MoveStructExt, 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 object_type,
174 owner,
175 previous_transaction,
176 ..
177 } = value.into_object()?;
178
179 Ok(ObjectInfo {
180 object_id,
181 version,
182 digest,
183 object_type: object_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 object_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 object_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 _ => bail!("unknown ObjectData variant"),
297 };
298 Some(data)
299 } else {
300 None
301 };
302
303 let obj = obj.into_inner();
304
305 let content: Option<IotaParsedData> = if *show_content {
306 let data = match obj.data {
307 ObjectData::Struct(m) => {
308 let layout = layout.ok_or_else(|| {
309 anyhow!("Layout is required to convert Move object to json")
310 })?;
311 IotaParsedData::try_from_object(m, layout)?
312 }
313 ObjectData::Package(p) => IotaParsedData::try_from_package(p)?,
314 _ => bail!("unknown ObjectData variant"),
315 };
316 Some(data)
317 } else {
318 None
319 };
320
321 Ok(IotaObjectData {
322 object_id,
323 version,
324 digest,
325 object_type,
326 owner: if *show_owner { Some(obj.owner) } else { None },
327 storage_rebate: if *show_storage_rebate {
328 Some(obj.storage_rebate)
329 } else {
330 None
331 },
332 previous_transaction: if *show_previous_transaction {
333 Some(obj.previous_transaction)
334 } else {
335 None
336 },
337 content,
338 bcs,
339 display: if show_display { display_fields } else { None },
340 })
341 }
342
343 pub fn object_ref(&self) -> ObjectReference {
344 ObjectReference::new(self.object_id, self.version, self.digest)
345 }
346
347 pub fn object_type(&self) -> anyhow::Result<ObjectType> {
348 self.object_type
349 .as_ref()
350 .ok_or_else(|| anyhow!("type is missing for object {}", self.object_id))
351 .cloned()
352 }
353
354 pub fn is_gas_coin(&self) -> bool {
355 match self.object_type.as_ref() {
356 Some(ObjectType::Struct(ty)) if ty.is_gas_coin() => true,
357 Some(_) => false,
358 None => false,
359 }
360 }
361}
362
363impl Display for IotaObjectData {
364 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
365 let object_type = if let Some(tag) = &self.object_type {
366 tag.to_string()
367 } else {
368 "Unknown Type".into()
369 };
370 let mut writer = String::new();
371 writeln!(
372 writer,
373 "{}",
374 format!(
375 "----- {object_type} ({}[{}]) -----",
376 self.object_id, self.version
377 )
378 .bold()
379 )?;
380 if let Some(owner) = self.owner {
381 writeln!(writer, "{}: {owner}", "Owner".bold().bright_black())?;
382 }
383
384 writeln!(
385 writer,
386 "{}: {}",
387 "Version".bold().bright_black(),
388 self.version
389 )?;
390 if let Some(storage_rebate) = self.storage_rebate {
391 writeln!(
392 writer,
393 "{}: {storage_rebate}",
394 "Storage Rebate".bold().bright_black(),
395 )?;
396 }
397
398 if let Some(previous_transaction) = self.previous_transaction {
399 writeln!(
400 writer,
401 "{}: {previous_transaction:?}",
402 "Previous Transaction".bold().bright_black(),
403 )?;
404 }
405 if let Some(content) = self.content.as_ref() {
406 writeln!(writer, "{}", "----- Data -----".bold())?;
407 write!(writer, "{content}")?;
408 }
409
410 write!(f, "{writer}")
411 }
412}
413
414impl TryFrom<&IotaObjectData> for GasCoin {
415 type Error = anyhow::Error;
416 fn try_from(object: &IotaObjectData) -> Result<Self, Self::Error> {
417 match &object
418 .content
419 .as_ref()
420 .ok_or_else(|| anyhow!("Expect object content to not be empty"))?
421 {
422 IotaParsedData::MoveObject(o) => {
423 if o.struct_tag.is_gas_coin() {
424 return GasCoin::try_from(&o.fields);
425 }
426 }
427 IotaParsedData::Package(_) => {}
428 }
429
430 bail!(
431 "Gas object type is not a gas coin: {:?}",
432 object.object_type
433 )
434 }
435}
436
437impl TryFrom<&IotaMoveStruct> for GasCoin {
438 type Error = anyhow::Error;
439 fn try_from(move_struct: &IotaMoveStruct) -> Result<Self, Self::Error> {
440 match move_struct {
441 IotaMoveStruct::WithFields(fields)
442 | IotaMoveStruct::WithTypes {
443 struct_tag: _,
444 fields,
445 } => {
446 if let Some(IotaMoveValue::String(balance)) = fields.get("balance") {
447 if let Ok(balance) = balance.parse::<u64>() {
448 if let Some(IotaMoveValue::UID { id }) = fields.get("id") {
449 return Ok(GasCoin::new(*id, balance));
450 }
451 }
452 }
453 }
454 _ => {}
455 }
456 bail!("Struct is not a gas coin: {move_struct:?}")
457 }
458}
459
460#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, Eq, PartialEq, Default)]
461#[serde(rename_all = "camelCase", rename = "ObjectDataOptions", default)]
462pub struct IotaObjectDataOptions {
463 pub show_type: bool,
465 pub show_owner: bool,
467 pub show_previous_transaction: bool,
470 pub show_display: bool,
473 pub show_content: bool,
476 pub show_bcs: bool,
478 pub show_storage_rebate: bool,
480}
481
482impl IotaObjectDataOptions {
483 pub fn new() -> Self {
484 Self::default()
485 }
486
487 pub fn bcs_lossless() -> Self {
489 Self {
490 show_bcs: true,
491 show_type: true,
492 show_owner: true,
493 show_previous_transaction: true,
494 show_display: false,
495 show_content: false,
496 show_storage_rebate: true,
497 }
498 }
499
500 pub fn full_content() -> Self {
502 Self {
503 show_bcs: false,
504 show_type: true,
505 show_owner: true,
506 show_previous_transaction: true,
507 show_display: false,
508 show_content: true,
509 show_storage_rebate: true,
510 }
511 }
512
513 pub fn with_content(mut self) -> Self {
514 self.show_content = true;
515 self
516 }
517
518 pub fn with_owner(mut self) -> Self {
519 self.show_owner = true;
520 self
521 }
522
523 pub fn with_type(mut self) -> Self {
524 self.show_type = true;
525 self
526 }
527
528 pub fn with_display(mut self) -> Self {
529 self.show_display = true;
530 self
531 }
532
533 pub fn with_bcs(mut self) -> Self {
534 self.show_bcs = true;
535 self
536 }
537
538 pub fn with_previous_transaction(mut self) -> Self {
539 self.show_previous_transaction = true;
540 self
541 }
542
543 pub fn is_not_in_object_info(&self) -> bool {
544 self.show_bcs || self.show_content || self.show_display || self.show_storage_rebate
545 }
546}
547
548impl TryFrom<(ObjectRead, IotaObjectDataOptions)> for IotaObjectResponse {
549 type Error = anyhow::Error;
550
551 fn try_from(
552 (object_read, options): (ObjectRead, IotaObjectDataOptions),
553 ) -> Result<Self, Self::Error> {
554 Self::try_from_object_read_and_options(object_read, &options)
555 }
556}
557
558impl TryFrom<(ObjectInfo, IotaObjectDataOptions)> for IotaObjectResponse {
559 type Error = anyhow::Error;
560
561 fn try_from(
562 (object_info, options): (ObjectInfo, IotaObjectDataOptions),
563 ) -> Result<Self, Self::Error> {
564 let IotaObjectDataOptions {
565 show_type,
566 show_owner,
567 show_previous_transaction,
568 ..
569 } = options;
570
571 Ok(Self::new_with_data(IotaObjectData {
572 object_id: object_info.object_id,
573 version: object_info.version,
574 digest: object_info.digest,
575 object_type: show_type.then_some(object_info.object_type),
576 owner: show_owner.then_some(object_info.owner),
577 previous_transaction: show_previous_transaction
578 .then_some(object_info.previous_transaction),
579 storage_rebate: None,
580 display: None,
581 content: None,
582 bcs: None,
583 }))
584 }
585}
586
587impl IotaObjectResponse {
588 pub fn object(&self) -> Result<&IotaObjectData, IotaObjectResponseError> {
591 if let Some(data) = &self.data {
592 Ok(data)
593 } else if let Some(error) = &self.error {
594 Err(error.clone())
595 } else {
596 Err(IotaObjectResponseError::Unknown)
599 }
600 }
601
602 pub fn into_object(self) -> Result<IotaObjectData, IotaObjectResponseError> {
605 match self.object() {
606 Ok(data) => Ok(data.clone()),
607 Err(error) => Err(error),
608 }
609 }
610}
611
612impl TryInto<Object> for IotaObjectData {
613 type Error = anyhow::Error;
614
615 fn try_into(self) -> Result<Object, Self::Error> {
616 let protocol_config = ProtocolConfig::get_for_min_version();
617 let data = match self.bcs {
618 Some(IotaRawData::MoveObject(o)) => ObjectData::Struct({
619 MoveStruct::new_from_execution(
620 o.struct_tag().clone(),
621 o.version.into(),
622 o.bcs_bytes,
623 &protocol_config,
624 false,
626 )?
627 }),
628 Some(IotaRawData::Package(p)) => ObjectData::Package(MovePackage::new(
631 p.id,
632 self.version,
633 p.module_map
634 .iter()
635 .map(|(k, v)| (Identifier::new_unchecked(k), v.clone()))
636 .collect(),
637 p.type_origin_table.into_iter().collect(),
638 p.linkage_table
639 .into_iter()
640 .map(|(k, v)| (k, v.into()))
641 .collect(),
642 )),
643 _ => Err(anyhow!(
644 "BCS data is required to convert IotaObjectData to Object"
645 ))?,
646 };
647 Ok(ObjectInner {
648 data,
649 owner: self
650 .owner
651 .ok_or_else(|| anyhow!("Owner is required to convert IotaObjectData to Object"))?,
652 previous_transaction: self.previous_transaction.ok_or_else(|| {
653 anyhow!("previous_transaction is required to convert IotaObjectData to Object")
654 })?,
655 storage_rebate: self.storage_rebate.ok_or_else(|| {
656 anyhow!("storage_rebate is required to convert IotaObjectData to Object")
657 })?,
658 }
659 .into())
660 }
661}
662
663#[serde_as]
664#[derive(Deserialize, Serialize, JsonSchema)]
665#[serde(rename_all = "camelCase", rename = "ObjectRef")]
666pub struct ObjectRefSchema {
667 #[serde_as(as = "ObjectIdSchema")]
669 #[schemars(with = "ObjectIdSchema")]
670 pub object_id: ObjectId,
671 pub version: SequenceNumberU64,
673 #[serde_as(as = "Base58Schema")]
675 #[schemars(with = "Base58Schema")]
676 pub digest: ObjectDigest,
677}
678
679impl SerializeAs<ObjectReference> for ObjectRefSchema {
680 fn serialize_as<S>(source: &ObjectReference, serializer: S) -> Result<S::Ok, S::Error>
681 where
682 S: serde::Serializer,
683 {
684 let iota_object_ref: ObjectRefSchema = (*source).into();
685 iota_object_ref.serialize(serializer)
686 }
687}
688
689impl<'de> DeserializeAs<'de, ObjectReference> for ObjectRefSchema {
690 fn deserialize_as<D>(deserializer: D) -> Result<ObjectReference, D::Error>
691 where
692 D: serde::Deserializer<'de>,
693 {
694 let iota_object_ref = ObjectRefSchema::deserialize(deserializer)?;
695 Ok(iota_object_ref.into())
696 }
697}
698
699impl From<ObjectReference> for ObjectRefSchema {
700 fn from(oref: ObjectReference) -> Self {
701 Self {
702 object_id: oref.object_id,
703 version: oref.version.into(),
704 digest: oref.digest,
705 }
706 }
707}
708
709impl From<ObjectRefSchema> for ObjectReference {
710 fn from(oref: ObjectRefSchema) -> Self {
711 ObjectReference::new(oref.object_id, oref.version.into(), oref.digest)
712 }
713}
714
715pub trait IotaData: Sized {
716 type ObjectType;
717 type PackageType;
718 fn try_from_object(object: MoveStruct, layout: MoveStructLayout)
719 -> Result<Self, anyhow::Error>;
720 fn try_from_package(package: MovePackage) -> Result<Self, anyhow::Error>;
721 fn try_as_move(&self) -> Option<&Self::ObjectType>;
722 fn try_into_move(self) -> Option<Self::ObjectType>;
723 fn try_as_package(&self) -> Option<&Self::PackageType>;
724 fn struct_tag(&self) -> Option<&StructTag>;
725}
726
727#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, Eq, PartialEq)]
728#[serde(tag = "dataType", rename_all = "camelCase", rename = "RawData")]
729pub enum IotaRawData {
730 MoveObject(IotaRawMoveObject),
732 Package(IotaRawMovePackage),
733}
734
735impl IotaData for IotaRawData {
736 type ObjectType = IotaRawMoveObject;
737 type PackageType = IotaRawMovePackage;
738
739 fn try_from_object(object: MoveStruct, _: MoveStructLayout) -> Result<Self, anyhow::Error> {
740 Ok(Self::MoveObject(object.into()))
741 }
742
743 fn try_from_package(package: MovePackage) -> Result<Self, anyhow::Error> {
744 Ok(Self::Package(package.into()))
745 }
746
747 fn try_as_move(&self) -> Option<&Self::ObjectType> {
748 match self {
749 Self::MoveObject(o) => Some(o),
750 Self::Package(_) => None,
751 }
752 }
753
754 fn try_into_move(self) -> Option<Self::ObjectType> {
755 match self {
756 Self::MoveObject(o) => Some(o),
757 Self::Package(_) => None,
758 }
759 }
760
761 fn try_as_package(&self) -> Option<&Self::PackageType> {
762 match self {
763 Self::MoveObject(_) => None,
764 Self::Package(p) => Some(p),
765 }
766 }
767
768 fn struct_tag(&self) -> Option<&StructTag> {
769 match self {
770 Self::MoveObject(o) => Some(&o.struct_tag),
771 Self::Package(_) => None,
772 }
773 }
774}
775
776#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, Eq, PartialEq)]
777#[serde(tag = "dataType", rename_all = "camelCase", rename = "Data")]
778pub enum IotaParsedData {
779 MoveObject(Box<IotaParsedMoveObject>),
781 Package(IotaMovePackage),
782}
783
784impl IotaData for IotaParsedData {
785 type ObjectType = IotaParsedMoveObject;
786 type PackageType = IotaMovePackage;
787
788 fn try_from_object(
789 object: MoveStruct,
790 layout: MoveStructLayout,
791 ) -> Result<Self, anyhow::Error> {
792 Ok(Self::MoveObject(Box::new(
793 IotaParsedMoveObject::try_from_layout(object, layout)?,
794 )))
795 }
796
797 fn try_from_package(package: MovePackage) -> Result<Self, anyhow::Error> {
798 let mut disassembled = BTreeMap::new();
799 for bytecode in package.serialized_module_map().values() {
800 let module = move_binary_format::CompiledModule::deserialize_with_defaults(bytecode)
803 .map_err(|error| IotaError::ModuleDeserializationFailure {
804 error: error.to_string(),
805 })?;
806 let d = move_disassembler::disassembler::Disassembler::from_module(
807 &module,
808 move_ir_types::location::Spanned::unsafe_no_loc(()).loc,
809 )
810 .map_err(|e| IotaError::ObjectSerialization {
811 error: e.to_string(),
812 })?;
813 let bytecode_str = d
814 .disassemble()
815 .map_err(|e| IotaError::ObjectSerialization {
816 error: e.to_string(),
817 })?;
818 disassembled.insert(module.name().to_string(), Value::String(bytecode_str));
819 }
820
821 Ok(Self::Package(IotaMovePackage { disassembled }))
822 }
823
824 fn try_as_move(&self) -> Option<&Self::ObjectType> {
825 match self {
826 Self::MoveObject(o) => Some(o),
827 Self::Package(_) => None,
828 }
829 }
830
831 fn try_into_move(self) -> Option<Self::ObjectType> {
832 match self {
833 Self::MoveObject(o) => Some(*o),
834 Self::Package(_) => None,
835 }
836 }
837
838 fn try_as_package(&self) -> Option<&Self::PackageType> {
839 match self {
840 Self::MoveObject(_) => None,
841 Self::Package(p) => Some(p),
842 }
843 }
844
845 fn struct_tag(&self) -> Option<&StructTag> {
846 match self {
847 Self::MoveObject(o) => Some(&o.struct_tag),
848 Self::Package(_) => None,
849 }
850 }
851}
852
853impl Display for IotaParsedData {
854 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
855 let mut writer = String::new();
856 match self {
857 IotaParsedData::MoveObject(o) => {
858 writeln!(writer, "{}: {}", "type".bold().bright_black(), o.struct_tag)?;
859 write!(writer, "{}", o.fields)?;
860 }
861 IotaParsedData::Package(p) => {
862 write!(
863 writer,
864 "{}: {:?}",
865 "Modules".bold().bright_black(),
866 p.disassembled.keys()
867 )?;
868 }
869 }
870 write!(f, "{writer}")
871 }
872}
873
874impl IotaParsedData {
875 pub fn try_from_object_read(object_read: ObjectRead) -> Result<Self, anyhow::Error> {
876 match object_read {
877 ObjectRead::NotExists(id) => Err(anyhow::anyhow!("Object {id} does not exist")),
878 ObjectRead::Exists(_object_ref, o, layout) => {
879 let data = match o.into_inner().data {
880 ObjectData::Struct(m) => {
881 let layout = layout.ok_or_else(|| {
882 anyhow!("Layout is required to convert Move object to json")
883 })?;
884 IotaParsedData::try_from_object(m, layout)?
885 }
886 ObjectData::Package(p) => IotaParsedData::try_from_package(p)?,
887 _ => bail!("unknown ObjectData variant"),
888 };
889 Ok(data)
890 }
891 ObjectRead::Deleted(object_ref) => Err(anyhow::anyhow!(
892 "Object {} was deleted at version {} with digest {}",
893 object_ref.object_id,
894 object_ref.version,
895 object_ref.digest
896 )),
897 }
898 }
899}
900
901pub trait IotaMoveObject: Sized {
902 fn try_from_layout(object: MoveStruct, layout: MoveStructLayout)
903 -> Result<Self, anyhow::Error>;
904
905 fn try_from(o: MoveStruct, resolver: &impl GetModule) -> Result<Self, anyhow::Error> {
906 let layout = o.get_layout(resolver)?;
907 Self::try_from_layout(o, layout)
908 }
909
910 fn struct_tag(&self) -> &StructTag;
911}
912
913#[serde_as]
914#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, Eq, PartialEq)]
915#[serde(rename = "MoveObject", rename_all = "camelCase")]
916pub struct IotaParsedMoveObject {
917 #[serde(rename = "type")]
918 #[schemars(with = "StructTagSchema")]
919 #[serde_as(as = "StructTagSchema")]
920 pub struct_tag: StructTag,
921 pub fields: IotaMoveStruct,
922}
923
924impl IotaMoveObject for IotaParsedMoveObject {
925 fn try_from_layout(
926 object: MoveStruct,
927 layout: MoveStructLayout,
928 ) -> Result<Self, anyhow::Error> {
929 let move_struct = object.to_move_struct(&layout)?.into();
930
931 Ok(
932 if let IotaMoveStruct::WithTypes {
933 struct_tag: tag,
934 fields,
935 } = move_struct
936 {
937 IotaParsedMoveObject {
938 struct_tag: tag,
939 fields: IotaMoveStruct::WithFields(fields),
940 }
941 } else {
942 IotaParsedMoveObject {
943 struct_tag: object.struct_tag().clone(),
944 fields: move_struct,
945 }
946 },
947 )
948 }
949
950 fn struct_tag(&self) -> &StructTag {
951 &self.struct_tag
952 }
953}
954
955impl IotaParsedMoveObject {
956 pub fn try_from_object_read(object_read: ObjectRead) -> Result<Self, anyhow::Error> {
957 let parsed_data = IotaParsedData::try_from_object_read(object_read)?;
958 match parsed_data {
959 IotaParsedData::MoveObject(o) => Ok(*o),
960 IotaParsedData::Package(_) => Err(anyhow::anyhow!("Object is not a Move object")),
961 }
962 }
963
964 pub fn read_dynamic_field_value(&self, field_name: &str) -> Option<IotaMoveValue> {
965 match &self.fields {
966 IotaMoveStruct::WithFields(fields) => fields.get(field_name).cloned(),
967 IotaMoveStruct::WithTypes { fields, .. } => fields.get(field_name).cloned(),
968 _ => None,
969 }
970 }
971}
972
973pub fn type_and_fields_from_move_event_data(
974 event_data: MoveValue,
975) -> IotaResult<(StructTag, serde_json::Value)> {
976 match event_data.into() {
977 IotaMoveValue::Struct(move_struct) => match &move_struct {
978 IotaMoveStruct::WithTypes {
979 struct_tag: tag, ..
980 } => Ok((tag.clone(), move_struct.clone().to_json_value())),
981 _ => Err(IotaError::ObjectDeserialization {
982 error: "Found non-type IotaMoveStruct in MoveValue event".to_string(),
983 }),
984 },
985 IotaMoveValue::Variant(v) => Ok((v.struct_tag.clone(), v.to_json_value())),
986 IotaMoveValue::Vector(_)
987 | IotaMoveValue::Number(_)
988 | IotaMoveValue::Bool(_)
989 | IotaMoveValue::Address(_)
990 | IotaMoveValue::String(_)
991 | IotaMoveValue::UID { .. }
992 | IotaMoveValue::Option(_) => Err(IotaError::ObjectDeserialization {
993 error: "Invalid MoveValue event type -- this should not be possible".to_string(),
994 }),
995 }
996}
997
998#[serde_as]
999#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, Eq, PartialEq)]
1000#[serde(rename = "RawMoveObject", rename_all = "camelCase")]
1001pub struct IotaRawMoveObject {
1002 #[serde(rename = "type")]
1003 #[schemars(with = "StructTagSchema")]
1004 #[serde_as(as = "StructTagSchema")]
1005 pub struct_tag: StructTag,
1006 pub version: SequenceNumberU64,
1007 #[serde_as(as = "Base64")]
1008 #[schemars(with = "Base64Schema")]
1009 pub bcs_bytes: Vec<u8>,
1010}
1011
1012impl From<MoveStruct> for IotaRawMoveObject {
1013 fn from(o: MoveStruct) -> Self {
1014 Self {
1015 struct_tag: o.struct_tag().clone(),
1016 version: o.version().into(),
1017 bcs_bytes: o.into_contents(),
1018 }
1019 }
1020}
1021
1022impl IotaMoveObject for IotaRawMoveObject {
1023 fn try_from_layout(
1024 object: MoveStruct,
1025 _layout: MoveStructLayout,
1026 ) -> Result<Self, anyhow::Error> {
1027 Ok(Self {
1028 struct_tag: object.struct_tag().clone(),
1029 version: object.version().into(),
1030 bcs_bytes: object.into_contents(),
1031 })
1032 }
1033
1034 fn struct_tag(&self) -> &StructTag {
1035 &self.struct_tag
1036 }
1037}
1038
1039impl IotaRawMoveObject {
1040 pub fn deserialize<'a, T: Deserialize<'a>>(&'a self) -> Result<T, anyhow::Error> {
1041 Ok(bcs::from_bytes(self.bcs_bytes.as_slice())?)
1042 }
1043}
1044
1045#[serde_as]
1056#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, JsonSchema)]
1057#[schemars(rename = "TypeOrigin")]
1058pub struct IotaTypeOrigin {
1059 #[schemars(with = "IdentifierSchema")]
1061 pub module_name: Identifier,
1062 #[serde(alias = "struct_name")]
1067 #[schemars(with = "IdentifierSchema")]
1068 pub datatype_name: Identifier,
1069 #[schemars(with = "ObjectIdSchema")]
1071 pub package: ObjectId,
1072}
1073
1074impl From<TypeOrigin> for IotaTypeOrigin {
1075 fn from(origin: TypeOrigin) -> Self {
1076 Self {
1077 module_name: origin.module_name,
1078 datatype_name: origin.datatype_name,
1079 package: origin.package,
1080 }
1081 }
1082}
1083
1084impl From<IotaTypeOrigin> for TypeOrigin {
1085 fn from(origin: IotaTypeOrigin) -> Self {
1086 Self {
1087 module_name: origin.module_name,
1088 datatype_name: origin.datatype_name,
1089 package: origin.package,
1090 }
1091 }
1092}
1093
1094#[serde_as]
1101#[derive(JsonSchema, Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
1102#[schemars(rename = "UpgradeInfo")]
1103pub struct IotaUpgradeInfo {
1104 #[schemars(with = "ObjectIdSchema")]
1106 pub upgraded_id: ObjectId,
1107 pub upgraded_version: SequenceNumberU64,
1109}
1110
1111impl From<UpgradeInfo> for IotaUpgradeInfo {
1112 fn from(info: UpgradeInfo) -> Self {
1113 Self {
1114 upgraded_id: info.upgraded_id,
1115 upgraded_version: info.upgraded_version.into(),
1116 }
1117 }
1118}
1119
1120impl From<IotaUpgradeInfo> for UpgradeInfo {
1121 fn from(info: IotaUpgradeInfo) -> Self {
1122 Self {
1123 upgraded_id: info.upgraded_id,
1124 upgraded_version: info.upgraded_version.into(),
1125 }
1126 }
1127}
1128
1129#[serde_as]
1130#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, Eq, PartialEq)]
1131#[serde(rename = "RawMovePackage", rename_all = "camelCase")]
1132pub struct IotaRawMovePackage {
1133 #[serde_as(as = "ObjectIdSchema")]
1134 #[schemars(with = "ObjectIdSchema")]
1135 pub id: ObjectId,
1136 pub version: SequenceNumberU64,
1137 #[schemars(with = "BTreeMap<String, Base64Schema>")]
1138 #[serde_as(as = "BTreeMap<_, Base64>")]
1139 pub module_map: BTreeMap<String, Vec<u8>>,
1140 #[schemars(with = "Vec<IotaTypeOrigin>")]
1141 pub type_origin_table: Vec<TypeOrigin>,
1142 #[serde_as(as = "BTreeMap<ObjectIdSchema, _>")]
1143 #[schemars(with = "BTreeMap<ObjectIdSchema, IotaUpgradeInfo>")]
1144 pub linkage_table: BTreeMap<ObjectId, IotaUpgradeInfo>,
1145}
1146
1147impl From<MovePackage> for IotaRawMovePackage {
1148 fn from(p: MovePackage) -> Self {
1149 Self {
1150 id: p.id(),
1151 version: p.version().into(),
1152 module_map: p
1153 .modules
1154 .into_iter()
1155 .map(|(k, v)| (k.to_string(), v))
1156 .collect(),
1157 type_origin_table: p.type_origin_table,
1158 linkage_table: p
1159 .linkage_table
1160 .into_iter()
1161 .map(|(k, v)| (k, v.into()))
1162 .collect(),
1163 }
1164 }
1165}
1166
1167impl IotaRawMovePackage {
1168 pub fn to_move_package(&self) -> MovePackage {
1171 MovePackage::new(
1172 self.id,
1173 self.version.into(),
1174 self.module_map
1175 .iter()
1176 .map(|(k, v)| (Identifier::new_unchecked(k), v.clone()))
1177 .collect(),
1178 self.type_origin_table.clone(),
1179 self.linkage_table
1180 .clone()
1181 .into_iter()
1182 .map(|(k, v)| (k, v.into()))
1183 .collect(),
1184 )
1185 }
1186}
1187
1188#[serde_as]
1189#[derive(Serialize, Deserialize, Debug, JsonSchema, Clone, PartialEq, Eq)]
1190#[serde(tag = "status", content = "details", rename = "ObjectRead")]
1191#[expect(clippy::large_enum_variant)]
1192pub enum IotaPastObjectResponse {
1193 VersionFound(IotaObjectData),
1195 ObjectNotExists(
1197 #[serde_as(as = "ObjectIdSchema")]
1198 #[schemars(with = "ObjectIdSchema")]
1199 ObjectId,
1200 ),
1201 ObjectDeleted(
1203 #[schemars(with = "ObjectRefSchema")]
1204 #[serde_as(as = "ObjectRefSchema")]
1205 ObjectReference,
1206 ),
1207 VersionNotFound(
1209 #[serde_as(as = "ObjectIdSchema")]
1210 #[schemars(with = "ObjectIdSchema")]
1211 ObjectId,
1212 SequenceNumberU64,
1213 ),
1214 VersionTooHigh {
1216 #[serde_as(as = "ObjectIdSchema")]
1217 #[schemars(with = "ObjectIdSchema")]
1218 object_id: ObjectId,
1219 asked_version: SequenceNumberU64,
1220 latest_version: SequenceNumberU64,
1221 },
1222}
1223
1224impl IotaPastObjectResponse {
1225 pub fn object(&self) -> UserInputResult<&IotaObjectData> {
1227 match &self {
1228 Self::ObjectDeleted(oref) => Err(UserInputError::ObjectDeleted { object_ref: *oref }),
1229 Self::ObjectNotExists(id) => Err(UserInputError::ObjectNotFound {
1230 object_id: *id,
1231 version: None,
1232 }),
1233 Self::VersionFound(o) => Ok(o),
1234 Self::VersionNotFound(id, seq_num) => Err(UserInputError::ObjectNotFound {
1235 object_id: *id,
1236 version: Some((*seq_num).into()),
1237 }),
1238 Self::VersionTooHigh {
1239 object_id,
1240 asked_version,
1241 latest_version,
1242 } => Err(UserInputError::ObjectSequenceNumberTooHigh {
1243 object_id: *object_id,
1244 asked_version: (*asked_version).into(),
1245 latest_version: (*latest_version).into(),
1246 }),
1247 }
1248 }
1249
1250 pub fn into_object(self) -> UserInputResult<IotaObjectData> {
1252 match self {
1253 Self::ObjectDeleted(oref) => Err(UserInputError::ObjectDeleted { object_ref: oref }),
1254 Self::ObjectNotExists(id) => Err(UserInputError::ObjectNotFound {
1255 object_id: id,
1256 version: None,
1257 }),
1258 Self::VersionFound(o) => Ok(o),
1259 Self::VersionNotFound(object_id, version) => Err(UserInputError::ObjectNotFound {
1260 object_id,
1261 version: Some(version.into()),
1262 }),
1263 Self::VersionTooHigh {
1264 object_id,
1265 asked_version,
1266 latest_version,
1267 } => Err(UserInputError::ObjectSequenceNumberTooHigh {
1268 object_id,
1269 asked_version: asked_version.into(),
1270 latest_version: latest_version.into(),
1271 }),
1272 }
1273 }
1274}
1275
1276#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, Eq, PartialEq)]
1277#[serde(rename = "MovePackage", rename_all = "camelCase")]
1278pub struct IotaMovePackage {
1279 pub disassembled: BTreeMap<String, Value>,
1280}
1281
1282pub type QueryObjectsPage = Page<IotaObjectResponse, CheckpointedObjectID>;
1283pub type ObjectsPage = Page<IotaObjectResponse, ObjectId>;
1284
1285#[serde_as]
1286#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, Copy, Eq, PartialEq)]
1287#[serde(rename_all = "camelCase")]
1288pub struct CheckpointedObjectID {
1289 #[serde_as(as = "ObjectIdSchema")]
1290 #[schemars(with = "ObjectIdSchema")]
1291 pub object_id: ObjectId,
1292 #[schemars(with = "Option<String>")]
1293 #[serde_as(as = "Option<DisplayFromStr>")]
1294 #[serde(skip_serializing_if = "Option::is_none")]
1295 pub at_checkpoint: Option<CheckpointSequenceNumber>,
1296}
1297
1298#[serde_as]
1299#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, Eq, PartialEq)]
1300#[serde(rename = "GetPastObjectRequest", rename_all = "camelCase")]
1301pub struct IotaGetPastObjectRequest {
1302 #[serde_as(as = "ObjectIdSchema")]
1304 #[schemars(with = "ObjectIdSchema")]
1305 pub object_id: ObjectId,
1306 #[schemars(with = "SequenceNumberStringSchema")]
1308 #[serde_as(as = "SequenceNumberStringSchema")]
1309 pub version: Version,
1310}
1311
1312#[serde_as]
1313#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
1314pub enum IotaObjectDataFilter {
1315 MatchAll(Vec<IotaObjectDataFilter>),
1316 MatchAny(Vec<IotaObjectDataFilter>),
1317 MatchNone(Vec<IotaObjectDataFilter>),
1318 Package(
1320 #[serde_as(as = "ObjectIdSchema")]
1321 #[schemars(with = "ObjectIdSchema")]
1322 ObjectId,
1323 ),
1324 MoveModule {
1326 #[serde_as(as = "ObjectIdSchema")]
1328 #[schemars(with = "ObjectIdSchema")]
1329 package: ObjectId,
1330 #[serde_as(as = "IdentifierSchema")]
1332 #[schemars(with = "IdentifierSchema")]
1333 module: Identifier,
1334 },
1335 StructType(
1337 #[schemars(with = "StructTagSchema")]
1338 #[serde_as(as = "StructTagSchema")]
1339 StructTag,
1340 ),
1341 AddressOwner(
1342 #[serde_as(as = "AddressSchema")]
1343 #[schemars(with = "AddressSchema")]
1344 Address,
1345 ),
1346 ObjectOwner(
1347 #[serde_as(as = "ObjectIdSchema")]
1348 #[schemars(with = "ObjectIdSchema")]
1349 ObjectId,
1350 ),
1351 ObjectId(
1352 #[serde_as(as = "ObjectIdSchema")]
1353 #[schemars(with = "ObjectIdSchema")]
1354 ObjectId,
1355 ),
1356 ObjectIds(
1358 #[serde_as(as = "Vec<ObjectIdSchema>")]
1359 #[schemars(with = "Vec<ObjectIdSchema>")]
1360 Vec<ObjectId>,
1361 ),
1362 Version(
1363 #[serde_as(as = "DisplayFromStr")]
1364 #[schemars(with = "String")]
1365 u64,
1366 ),
1367}
1368
1369impl IotaObjectDataFilter {
1370 pub fn gas_coin() -> Self {
1371 Self::StructType(StructTag::new_gas_coin())
1372 }
1373
1374 pub fn and(self, other: Self) -> Self {
1375 Self::MatchAll(vec![self, other])
1376 }
1377 pub fn or(self, other: Self) -> Self {
1378 Self::MatchAny(vec![self, other])
1379 }
1380 pub fn not(self, other: Self) -> Self {
1381 Self::MatchNone(vec![self, other])
1382 }
1383
1384 pub fn matches(&self, object: &ObjectInfo) -> bool {
1385 match self {
1386 IotaObjectDataFilter::MatchAll(filters) => !filters.iter().any(|f| !f.matches(object)),
1387 IotaObjectDataFilter::MatchAny(filters) => filters.iter().any(|f| f.matches(object)),
1388 IotaObjectDataFilter::MatchNone(filters) => !filters.iter().any(|f| f.matches(object)),
1389 IotaObjectDataFilter::StructType(s) => {
1390 let obj_tag: StructTag = match &object.object_type {
1391 ObjectType::Package => return false,
1392 ObjectType::Struct(s) => s.clone().into(),
1393 };
1394 if !s.type_params().is_empty() && s.type_params() != obj_tag.type_params() {
1397 false
1398 } else {
1399 obj_tag.address() == s.address()
1400 && obj_tag.module() == s.module()
1401 && obj_tag.name() == s.name()
1402 }
1403 }
1404 IotaObjectDataFilter::MoveModule { package, module } => {
1405 matches!(&object.object_type, ObjectType::Struct(s) if &ObjectId::from(s.address()) == package
1406 && s.module() == module)
1407 }
1408 IotaObjectDataFilter::Package(p) => {
1409 matches!(&object.object_type, ObjectType::Struct(s) if &ObjectId::from(s.address()) == p)
1410 }
1411 IotaObjectDataFilter::AddressOwner(a) => {
1412 matches!(object.owner, Owner::Address(addr) if &addr == a)
1413 }
1414 IotaObjectDataFilter::ObjectOwner(o) => {
1415 matches!(object.owner, Owner::Object(addr) if &addr == o)
1416 }
1417 IotaObjectDataFilter::ObjectId(id) => &object.object_id == id,
1418 IotaObjectDataFilter::ObjectIds(ids) => ids.contains(&object.object_id),
1419 IotaObjectDataFilter::Version(v) => object.version == *v,
1420 }
1421 }
1422}
1423
1424#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, Default)]
1425#[serde(rename_all = "camelCase", rename = "ObjectResponseQuery", default)]
1426pub struct IotaObjectResponseQuery {
1427 pub filter: Option<IotaObjectDataFilter>,
1429 pub options: Option<IotaObjectDataOptions>,
1432}
1433
1434impl IotaObjectResponseQuery {
1435 pub fn new(
1436 filter: Option<IotaObjectDataFilter>,
1437 options: Option<IotaObjectDataOptions>,
1438 ) -> Self {
1439 Self { filter, options }
1440 }
1441
1442 pub fn new_with_filter(filter: IotaObjectDataFilter) -> Self {
1443 Self {
1444 filter: Some(filter),
1445 options: None,
1446 }
1447 }
1448
1449 pub fn new_with_options(options: IotaObjectDataOptions) -> Self {
1450 Self {
1451 filter: None,
1452 options: Some(options),
1453 }
1454 }
1455}