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