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