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