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