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