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