sui_json_rpc_types/
sui_object.rs

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