sui_jsonrpc/msgs/
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::{Display, Formatter, Write};
8use std::str::FromStr;
9
10use af_sui_types::{
11    Address as SuiAddress,
12    Identifier,
13    MoveObject,
14    MoveObjectType,
15    Object,
16    ObjectArg,
17    ObjectDigest,
18    ObjectId,
19    ObjectRef,
20    Owner,
21    StructTag,
22    TransactionDigest,
23    TypeOrigin,
24    UpgradeInfo,
25};
26use colored::Colorize;
27use serde::{Deserialize, Serialize};
28use serde_json::Value;
29use serde_with::base64::Base64;
30use serde_with::{DisplayFromStr, serde_as};
31use sui_sdk_types::Version;
32
33use super::{Page, SuiMoveStruct, SuiMoveValue};
34use crate::serde::BigInt;
35
36// =============================================================================
37//  SuiObjectResponse
38// =============================================================================
39
40#[derive(thiserror::Error, Clone, Debug, PartialEq, Eq)]
41#[error("Could not get object_id, something went wrong with SuiObjectResponse construction.")]
42pub struct MissingObjectIdError;
43
44#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
45pub struct SuiObjectResponse {
46    #[serde(skip_serializing_if = "Option::is_none")]
47    pub data: Option<SuiObjectData>,
48    #[serde(skip_serializing_if = "Option::is_none")]
49    pub error: Option<SuiObjectResponseError>,
50}
51
52impl SuiObjectResponse {
53    pub fn new(data: Option<SuiObjectData>, error: Option<SuiObjectResponseError>) -> Self {
54        Self { data, error }
55    }
56
57    pub fn new_with_data(data: SuiObjectData) -> Self {
58        Self {
59            data: Some(data),
60            error: None,
61        }
62    }
63
64    pub fn new_with_error(error: SuiObjectResponseError) -> Self {
65        Self {
66            data: None,
67            error: Some(error),
68        }
69    }
70
71    /// Returns a reference to the object if there is any, otherwise an Err if
72    /// the object does not exist or is deleted.
73    pub fn object(&self) -> Result<&SuiObjectData, SuiObjectResponseError> {
74        if let Some(data) = &self.data {
75            Ok(data)
76        } else if let Some(error) = &self.error {
77            Err(error.clone())
78        } else {
79            // We really shouldn't reach this code block since either data, or error field should always be filled.
80            Err(SuiObjectResponseError::Unknown)
81        }
82    }
83
84    /// Returns the object value if there is any, otherwise an Err if
85    /// the object does not exist or is deleted.
86    pub fn into_object(self) -> Result<SuiObjectData, SuiObjectResponseError> {
87        match self.object() {
88            Ok(data) => Ok(data.clone()),
89            Err(error) => Err(error),
90        }
91    }
92
93    pub fn move_object_bcs(&self) -> Option<&Vec<u8>> {
94        match &self.data {
95            Some(SuiObjectData {
96                bcs: Some(SuiRawData::MoveObject(obj)),
97                ..
98            }) => Some(&obj.bcs_bytes),
99            _ => None,
100        }
101    }
102
103    pub fn owner(&self) -> Option<Owner> {
104        if let Some(data) = &self.data {
105            return data.owner.clone();
106        }
107        None
108    }
109
110    pub fn object_id(&self) -> Result<ObjectId, MissingObjectIdError> {
111        match (&self.data, &self.error) {
112            (Some(obj_data), None) => Ok(obj_data.object_id),
113            (None, Some(SuiObjectResponseError::NotExists { object_id })) => Ok(*object_id),
114            (
115                None,
116                Some(SuiObjectResponseError::Deleted {
117                    object_id,
118                    version: _,
119                    digest: _,
120                }),
121            ) => Ok(*object_id),
122            _ => Err(MissingObjectIdError),
123        }
124    }
125
126    pub fn object_ref_if_exists(&self) -> Option<ObjectRef> {
127        match (&self.data, &self.error) {
128            (Some(obj_data), None) => Some(obj_data.object_ref()),
129            _ => None,
130        }
131    }
132}
133
134impl Ord for SuiObjectResponse {
135    fn cmp(&self, other: &Self) -> Ordering {
136        match (&self.data, &other.data) {
137            (Some(data), Some(data_2)) => {
138                if data.object_id.cmp(&data_2.object_id).eq(&Ordering::Greater) {
139                    return Ordering::Greater;
140                } else if data.object_id.cmp(&data_2.object_id).eq(&Ordering::Less) {
141                    return Ordering::Less;
142                }
143                Ordering::Equal
144            }
145            // In this ordering those with data will come before SuiObjectResponses that are errors.
146            (Some(_), None) => Ordering::Less,
147            (None, Some(_)) => Ordering::Greater,
148            // SuiObjectResponses that are errors are just considered equal.
149            _ => Ordering::Equal,
150        }
151    }
152}
153
154impl PartialOrd for SuiObjectResponse {
155    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
156        Some(self.cmp(other))
157    }
158}
159
160/// Originally from `sui_types::error`.
161#[derive(thiserror::Error, Eq, PartialEq, Clone, Debug, Serialize, Deserialize, Hash)]
162#[serde(tag = "code", rename = "ObjectResponseError", rename_all = "camelCase")]
163pub enum SuiObjectResponseError {
164    #[error("Object {:?} does not exist.", object_id)]
165    NotExists { object_id: ObjectId },
166    #[error("Cannot find dynamic field for parent object {:?}.", parent_object_id)]
167    DynamicFieldNotFound { parent_object_id: ObjectId },
168    #[error(
169        "Object has been deleted object_id: {:?} at version: {:?} in digest {:?}",
170        object_id,
171        version,
172        digest
173    )]
174    Deleted {
175        object_id: ObjectId,
176        /// Object version.
177        version: Version,
178        /// Base64 string representing the object digest
179        digest: ObjectDigest,
180    },
181    #[error("Unknown Error.")]
182    Unknown,
183    #[error("Display Error: {:?}", error)]
184    DisplayError { error: String },
185}
186
187#[derive(Debug, Clone, Deserialize, Serialize, Eq, PartialEq)]
188pub struct DisplayFieldsResponse {
189    pub data: Option<BTreeMap<String, String>>,
190    pub error: Option<SuiObjectResponseError>,
191}
192
193// =============================================================================
194//  SuiObjectData
195// =============================================================================
196
197#[derive(thiserror::Error, Debug)]
198pub enum SuiObjectDataError {
199    #[error("Missing object type")]
200    MissingObjectType,
201    #[error("Missing BCS encoding")]
202    MissingBcs,
203    #[error("Missing object owner")]
204    MissingOwner,
205    #[error("Not a Move object")]
206    NotMoveObject,
207    #[error("Not an immutable or owned object")]
208    NotImmOrOwned,
209    #[error("Not a shared object")]
210    NotShared,
211    #[error(transparent)]
212    ObjectType(#[from] NotMoveStructError),
213}
214
215/// Error for [`SuiObjectData::into_full_object`].
216#[derive(thiserror::Error, Debug)]
217#[non_exhaustive]
218pub enum FullObjectDataError {
219    #[error("Missing BCS encoding")]
220    MissingBcs,
221    #[error("Missing object owner")]
222    MissingOwner,
223    #[error("Missing previous transaction digest")]
224    MissingPreviousTransaction,
225    #[error("Missing storage rebate")]
226    MissingStorageRebate,
227    #[error("MoveObject BCS doesn't start with ObjectId")]
228    InvalidBcs,
229    #[error("Invalid identifier: {ident}\nReason: {source}")]
230    InvalidIdentifier {
231        ident: Box<str>,
232        #[source]
233        source: af_sui_types::TypeParseError,
234    },
235}
236
237#[serde_as]
238#[derive(Debug, Clone, Deserialize, Serialize, Eq, PartialEq)]
239#[serde(rename_all = "camelCase", rename = "ObjectData")]
240pub struct SuiObjectData {
241    pub object_id: ObjectId,
242    /// Object version.
243    #[serde_as(as = "BigInt<u64>")]
244    pub version: Version,
245    /// Base64 string representing the object digest
246    pub digest: ObjectDigest,
247    /// The type of the object. Default to be None unless SuiObjectDataOptions.showType is set to true
248    #[serde_as(as = "Option<DisplayFromStr>")]
249    #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
250    pub type_: Option<ObjectType>,
251    // Default to be None because otherwise it will be repeated for the getOwnedObjects endpoint
252    /// The owner of this object. Default to be None unless SuiObjectDataOptions.showOwner is set to true
253    #[serde(skip_serializing_if = "Option::is_none")]
254    pub owner: Option<Owner>,
255    /// The digest of the transaction that created or last mutated this object. Default to be None unless
256    /// SuiObjectDataOptions.showPreviousTransaction is set to true
257    #[serde(skip_serializing_if = "Option::is_none")]
258    pub previous_transaction: Option<TransactionDigest>,
259    /// The amount of SUI we would rebate if this object gets deleted.
260    /// This number is re-calculated each time the object is mutated based on
261    /// the present storage gas price.
262    #[serde_as(as = "Option<BigInt<u64>>")]
263    #[serde(skip_serializing_if = "Option::is_none")]
264    pub storage_rebate: Option<u64>,
265    /// The Display metadata for frontend UI rendering, default to be None unless SuiObjectDataOptions.showContent is set to true
266    /// This can also be None if the struct type does not have Display defined
267    /// See more details in <https://forums.sui.io/t/nft-object-display-proposal/4872>
268    #[serde(skip_serializing_if = "Option::is_none")]
269    pub display: Option<DisplayFieldsResponse>,
270    /// Move object content or package content, default to be None unless SuiObjectDataOptions.showContent is set to true
271    #[serde(skip_serializing_if = "Option::is_none")]
272    pub content: Option<SuiParsedData>,
273    /// Move object content or package content in BCS, default to be None unless SuiObjectDataOptions.showBcs is set to true
274    #[serde(skip_serializing_if = "Option::is_none")]
275    pub bcs: Option<SuiRawData>,
276}
277
278impl SuiObjectData {
279    pub fn object_ref(&self) -> ObjectRef {
280        (self.object_id, self.version, self.digest)
281    }
282
283    pub fn object_type(&self) -> Result<ObjectType, SuiObjectDataError> {
284        self.type_
285            .as_ref()
286            .ok_or(SuiObjectDataError::MissingObjectType)
287            .cloned()
288    }
289
290    pub fn is_gas_coin(&self) -> bool {
291        match self.type_.as_ref() {
292            Some(ObjectType::Struct(ty)) if ty.is_gas_coin() => true,
293            Some(_) => false,
294            None => false,
295        }
296    }
297
298    pub fn struct_tag(&self) -> Result<StructTag, SuiObjectDataError> {
299        Ok(self
300            .type_
301            .clone()
302            .ok_or(SuiObjectDataError::MissingObjectType)?
303            .try_into()?)
304    }
305
306    pub fn take_object_type(&mut self) -> Result<ObjectType, SuiObjectDataError> {
307        self.type_
308            .take()
309            .ok_or(SuiObjectDataError::MissingObjectType)
310    }
311
312    pub fn take_raw_object(&mut self) -> Result<SuiRawMoveObject, SuiObjectDataError> {
313        self.take_raw_data()?
314            .try_into_move()
315            .ok_or(SuiObjectDataError::NotMoveObject)
316    }
317
318    pub fn take_raw_data(&mut self) -> Result<SuiRawData, SuiObjectDataError> {
319        self.bcs.take().ok_or(SuiObjectDataError::MissingBcs)
320    }
321
322    pub fn shared_object_arg(&self, mutable: bool) -> Result<ObjectArg, SuiObjectDataError> {
323        let Owner::Shared {
324            initial_shared_version,
325        } = self.owner()?
326        else {
327            return Err(SuiObjectDataError::NotShared);
328        };
329        Ok(ObjectArg::SharedObject {
330            id: self.object_id,
331            initial_shared_version,
332            mutable,
333        })
334    }
335
336    pub fn imm_or_owned_object_arg(&self) -> Result<ObjectArg, SuiObjectDataError> {
337        use Owner::*;
338        if !matches!(self.owner()?, AddressOwner(_) | ObjectOwner(_) | Immutable) {
339            return Err(SuiObjectDataError::NotImmOrOwned);
340        };
341        let (i, v, d) = self.object_ref();
342        Ok(ObjectArg::ImmOrOwnedObject((i, v, d)))
343    }
344
345    #[cfg(feature = "client")]
346    pub(crate) fn object_arg(&self, mutable: bool) -> Result<ObjectArg, SuiObjectDataError> {
347        use Owner as O;
348        Ok(match self.owner()? {
349            O::AddressOwner(_) | O::ObjectOwner(_) | O::Immutable => {
350                ObjectArg::ImmOrOwnedObject(self.object_ref())
351            }
352            O::Shared {
353                initial_shared_version,
354            }
355            | O::ConsensusV2 {
356                start_version: initial_shared_version,
357                ..
358            } => ObjectArg::SharedObject {
359                id: self.object_id,
360                initial_shared_version,
361                mutable,
362            },
363        })
364    }
365
366    pub fn owner(&self) -> Result<Owner, SuiObjectDataError> {
367        self.owner.clone().ok_or(SuiObjectDataError::MissingOwner)
368    }
369
370    /// Create a standard Sui [`Object`] if there's enough information.
371    pub fn into_full_object(self) -> Result<Object, FullObjectDataError> {
372        use itertools::Itertools as _;
373        let Self {
374            owner,
375            previous_transaction,
376            storage_rebate,
377            bcs,
378            ..
379        } = self;
380        let owner = owner.ok_or(FullObjectDataError::MissingOwner)?;
381        let previous_transaction =
382            previous_transaction.ok_or(FullObjectDataError::MissingPreviousTransaction)?;
383        let storage_rebate = storage_rebate.ok_or(FullObjectDataError::MissingStorageRebate)?;
384
385        match bcs.ok_or(FullObjectDataError::MissingBcs)? {
386            SuiRawData::Package(p) => {
387                let modules = p
388                    .module_map
389                    .into_iter()
390                    .map(|(s, bytes)| {
391                        Ok((
392                            s.parse()
393                                .map_err(|e| FullObjectDataError::InvalidIdentifier {
394                                    ident: s.into(),
395                                    source: e,
396                                })?,
397                            bytes,
398                        ))
399                    })
400                    .try_collect()?;
401                let inner = af_sui_types::MovePackage {
402                    id: p.id,
403                    version: p.version,
404                    modules,
405                    type_origin_table: p.type_origin_table,
406                    linkage_table: p.linkage_table,
407                };
408                Ok(Object::new_package(
409                    inner,
410                    owner,
411                    previous_transaction,
412                    storage_rebate,
413                ))
414            }
415            SuiRawData::MoveObject(raw_struct) => {
416                let struct_ = MoveObject::new(
417                    raw_struct.type_,
418                    raw_struct.has_public_transfer,
419                    raw_struct.version,
420                    raw_struct.bcs_bytes,
421                )
422                .ok_or(FullObjectDataError::InvalidBcs)?;
423                Ok(Object::new_struct(
424                    struct_,
425                    owner,
426                    previous_transaction,
427                    storage_rebate,
428                ))
429            }
430        }
431    }
432}
433
434impl Display for SuiObjectData {
435    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
436        let type_ = if let Some(type_) = &self.type_ {
437            type_.to_string()
438        } else {
439            "Unknown Type".into()
440        };
441        let mut writer = String::new();
442        writeln!(
443            writer,
444            "{}",
445            format!("----- {type_} ({}[{}]) -----", self.object_id, self.version).bold()
446        )?;
447        if let Some(ref owner) = self.owner {
448            writeln!(writer, "{}: {}", "Owner".bold().bright_black(), owner)?;
449        }
450
451        writeln!(
452            writer,
453            "{}: {}",
454            "Version".bold().bright_black(),
455            self.version
456        )?;
457        if let Some(storage_rebate) = self.storage_rebate {
458            writeln!(
459                writer,
460                "{}: {}",
461                "Storage Rebate".bold().bright_black(),
462                storage_rebate
463            )?;
464        }
465
466        if let Some(previous_transaction) = self.previous_transaction {
467            writeln!(
468                writer,
469                "{}: {:?}",
470                "Previous Transaction".bold().bright_black(),
471                previous_transaction
472            )?;
473        }
474        if let Some(content) = self.content.as_ref() {
475            writeln!(writer, "{}", "----- Data -----".bold())?;
476            write!(writer, "{}", content)?;
477        }
478
479        write!(f, "{}", writer)
480    }
481}
482
483// =============================================================================
484//  ObjectType
485// =============================================================================
486
487const PACKAGE: &str = "package";
488/// Type of a Sui object
489///
490/// Originally from `sui_types::base_types`.
491#[derive(Clone, Serialize, Deserialize, Ord, PartialOrd, Eq, PartialEq, Debug)]
492pub enum ObjectType {
493    /// Move package containing one or more bytecode modules
494    Package,
495    /// A Move struct of the given type
496    Struct(MoveObjectType),
497}
498
499impl TryFrom<ObjectType> for StructTag {
500    type Error = NotMoveStructError;
501
502    fn try_from(o: ObjectType) -> Result<Self, Self::Error> {
503        match o {
504            ObjectType::Package => Err(NotMoveStructError),
505            ObjectType::Struct(move_object_type) => Ok(move_object_type.into()),
506        }
507    }
508}
509
510#[derive(thiserror::Error, Clone, Debug, PartialEq, Eq)]
511#[error("Cannot create StructTag from Package")]
512pub struct NotMoveStructError;
513
514impl Display for ObjectType {
515    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
516        match self {
517            ObjectType::Package => write!(f, "{}", PACKAGE),
518            ObjectType::Struct(t) => write!(f, "{}", t),
519        }
520    }
521}
522
523impl FromStr for ObjectType {
524    type Err = <StructTag as FromStr>::Err;
525
526    fn from_str(s: &str) -> Result<Self, Self::Err> {
527        if s.to_lowercase() == PACKAGE {
528            Ok(ObjectType::Package)
529        } else {
530            let tag: StructTag = s.parse()?;
531            Ok(ObjectType::Struct(MoveObjectType::from(tag)))
532        }
533    }
534}
535
536// =============================================================================
537//  SuiObjectDataOptions
538// =============================================================================
539
540#[derive(Debug, Clone, Deserialize, Serialize, Eq, PartialEq, Default)]
541#[serde(rename_all = "camelCase", rename = "ObjectDataOptions", default)]
542pub struct SuiObjectDataOptions {
543    /// Whether to show the type of the object. Default to be False
544    pub show_type: bool,
545    /// Whether to show the owner of the object. Default to be False
546    pub show_owner: bool,
547    /// Whether to show the previous transaction digest of the object. Default to be False
548    pub show_previous_transaction: bool,
549    /// Whether to show the Display metadata of the object for frontend rendering. Default to be False
550    pub show_display: bool,
551    /// Whether to show the content(i.e., package content or Move struct content) of the object.
552    /// Default to be False
553    pub show_content: bool,
554    /// Whether to show the content in BCS format. Default to be False
555    pub show_bcs: bool,
556    /// Whether to show the storage rebate of the object. Default to be False
557    pub show_storage_rebate: bool,
558}
559
560impl SuiObjectDataOptions {
561    pub fn new() -> Self {
562        Self::default()
563    }
564
565    /// Just enough information to create an [`Object`].
566    pub fn full_object() -> Self {
567        Self {
568            show_bcs: true,
569            show_owner: true,
570            show_storage_rebate: true,
571            show_previous_transaction: true,
572            show_content: false,
573            show_display: false,
574            show_type: false,
575        }
576    }
577
578    /// return BCS data and all other metadata such as storage rebate
579    pub fn bcs_lossless() -> Self {
580        Self {
581            show_bcs: true,
582            show_type: true,
583            show_owner: true,
584            show_previous_transaction: true,
585            show_display: false,
586            show_content: false,
587            show_storage_rebate: true,
588        }
589    }
590
591    /// return full content except bcs
592    pub fn full_content() -> Self {
593        Self {
594            show_bcs: false,
595            show_type: true,
596            show_owner: true,
597            show_previous_transaction: true,
598            show_display: false,
599            show_content: true,
600            show_storage_rebate: true,
601        }
602    }
603
604    pub fn with_content(mut self) -> Self {
605        self.show_content = true;
606        self
607    }
608
609    pub fn with_owner(mut self) -> Self {
610        self.show_owner = true;
611        self
612    }
613
614    pub fn with_type(mut self) -> Self {
615        self.show_type = true;
616        self
617    }
618
619    pub fn with_display(mut self) -> Self {
620        self.show_display = true;
621        self
622    }
623
624    pub fn with_bcs(mut self) -> Self {
625        self.show_bcs = true;
626        self
627    }
628
629    pub fn with_previous_transaction(mut self) -> Self {
630        self.show_previous_transaction = true;
631        self
632    }
633
634    pub fn is_not_in_object_info(&self) -> bool {
635        self.show_bcs || self.show_content || self.show_display || self.show_storage_rebate
636    }
637}
638
639// =============================================================================
640//  SuiObjectRef
641// =============================================================================
642
643#[derive(Debug, Clone, Deserialize, Serialize, Eq, PartialEq, Ord, PartialOrd)]
644#[serde(rename_all = "camelCase", rename = "ObjectRef")]
645pub struct SuiObjectRef {
646    /// Hex code as string representing the object id
647    pub object_id: ObjectId,
648    /// Object version.
649    pub version: Version,
650    /// Base64 string representing the object digest
651    pub digest: ObjectDigest,
652}
653
654impl SuiObjectRef {
655    pub fn to_object_ref(&self) -> ObjectRef {
656        (self.object_id, self.version, self.digest)
657    }
658}
659
660impl Display for SuiObjectRef {
661    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
662        write!(
663            f,
664            "Object ID: {}, version: {}, digest: {}",
665            self.object_id, self.version, self.digest
666        )
667    }
668}
669
670impl From<ObjectRef> for SuiObjectRef {
671    fn from(oref: ObjectRef) -> Self {
672        Self {
673            object_id: oref.0,
674            version: oref.1,
675            digest: oref.2,
676        }
677    }
678}
679
680// =============================================================================
681//  SuiData
682// =============================================================================
683
684pub trait SuiData: Sized {
685    type ObjectType;
686    type PackageType;
687    fn try_as_move(&self) -> Option<&Self::ObjectType>;
688    fn try_into_move(self) -> Option<Self::ObjectType>;
689    fn try_as_package(&self) -> Option<&Self::PackageType>;
690    fn type_(&self) -> Option<&StructTag>;
691}
692
693#[derive(Debug, Deserialize, Serialize, Clone, Eq, PartialEq)]
694#[serde(tag = "dataType", rename_all = "camelCase", rename = "RawData")]
695pub enum SuiRawData {
696    // Manually handle generic schema generation
697    MoveObject(SuiRawMoveObject),
698    Package(SuiRawMovePackage),
699}
700
701impl SuiData for SuiRawData {
702    type ObjectType = SuiRawMoveObject;
703    type PackageType = SuiRawMovePackage;
704
705    fn try_as_move(&self) -> Option<&Self::ObjectType> {
706        match self {
707            Self::MoveObject(o) => Some(o),
708            Self::Package(_) => None,
709        }
710    }
711
712    fn try_into_move(self) -> Option<Self::ObjectType> {
713        match self {
714            Self::MoveObject(o) => Some(o),
715            Self::Package(_) => None,
716        }
717    }
718
719    fn try_as_package(&self) -> Option<&Self::PackageType> {
720        match self {
721            Self::MoveObject(_) => None,
722            Self::Package(p) => Some(p),
723        }
724    }
725
726    fn type_(&self) -> Option<&StructTag> {
727        match self {
728            Self::MoveObject(o) => Some(&o.type_),
729            Self::Package(_) => None,
730        }
731    }
732}
733
734#[derive(Debug, Deserialize, Serialize, Clone, Eq, PartialEq)]
735#[serde(tag = "dataType", rename_all = "camelCase", rename = "Data")]
736pub enum SuiParsedData {
737    // Manually handle generic schema generation
738    MoveObject(SuiParsedMoveObject),
739    Package(SuiMovePackage),
740}
741
742impl SuiData for SuiParsedData {
743    type ObjectType = SuiParsedMoveObject;
744    type PackageType = SuiMovePackage;
745
746    fn try_as_move(&self) -> Option<&Self::ObjectType> {
747        match self {
748            Self::MoveObject(o) => Some(o),
749            Self::Package(_) => None,
750        }
751    }
752
753    fn try_into_move(self) -> Option<Self::ObjectType> {
754        match self {
755            Self::MoveObject(o) => Some(o),
756            Self::Package(_) => None,
757        }
758    }
759
760    fn try_as_package(&self) -> Option<&Self::PackageType> {
761        match self {
762            Self::MoveObject(_) => None,
763            Self::Package(p) => Some(p),
764        }
765    }
766
767    fn type_(&self) -> Option<&StructTag> {
768        match self {
769            Self::MoveObject(o) => Some(&o.type_),
770            Self::Package(_) => None,
771        }
772    }
773}
774
775impl Display for SuiParsedData {
776    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
777        let mut writer = String::new();
778        match self {
779            SuiParsedData::MoveObject(o) => {
780                writeln!(writer, "{}: {}", "type".bold().bright_black(), o.type_)?;
781                write!(writer, "{}", &o.fields)?;
782            }
783            SuiParsedData::Package(p) => {
784                write!(
785                    writer,
786                    "{}: {:?}",
787                    "Modules".bold().bright_black(),
788                    p.disassembled.keys()
789                )?;
790            }
791        }
792        write!(f, "{}", writer)
793    }
794}
795
796pub trait SuiMoveObject: Sized {
797    fn type_(&self) -> &StructTag;
798}
799
800#[serde_as]
801#[derive(Debug, Deserialize, Serialize, Clone, Eq, PartialEq)]
802#[serde(rename = "MoveObject", rename_all = "camelCase")]
803pub struct SuiParsedMoveObject {
804    #[serde(rename = "type")]
805    // #[serde_as(as = "SuiStructTag")]
806    #[serde_as(as = "DisplayFromStr")]
807    pub type_: StructTag,
808    pub has_public_transfer: bool,
809    pub fields: SuiMoveStruct,
810}
811
812impl SuiMoveObject for SuiParsedMoveObject {
813    fn type_(&self) -> &StructTag {
814        &self.type_
815    }
816}
817
818impl SuiParsedMoveObject {
819    pub fn read_dynamic_field_value(&self, field_name: &str) -> Option<SuiMoveValue> {
820        match &self.fields {
821            SuiMoveStruct::WithFields(fields) => fields.get(field_name).cloned(),
822            SuiMoveStruct::WithTypes { fields, .. } => fields.get(field_name).cloned(),
823            _ => None,
824        }
825    }
826}
827
828#[serde_as]
829#[derive(Debug, Deserialize, Serialize, Clone, Eq, PartialEq)]
830#[serde(rename = "RawMoveObject", rename_all = "camelCase")]
831pub struct SuiRawMoveObject {
832    #[serde(rename = "type")]
833    // #[serde_as(as = "SuiStructTag")]
834    #[serde_as(as = "DisplayFromStr")]
835    pub type_: StructTag,
836    pub has_public_transfer: bool,
837    pub version: Version,
838    #[serde_as(as = "Base64")]
839    pub bcs_bytes: Vec<u8>,
840}
841
842impl SuiMoveObject for SuiRawMoveObject {
843    fn type_(&self) -> &StructTag {
844        &self.type_
845    }
846}
847
848impl SuiRawMoveObject {
849    pub fn deserialize<'a, T: Deserialize<'a>>(&'a self) -> Result<T, bcs::Error> {
850        bcs::from_bytes(self.bcs_bytes.as_slice())
851    }
852}
853
854#[serde_as]
855#[derive(Debug, Deserialize, Serialize, Clone, Eq, PartialEq)]
856#[serde(rename = "RawMovePackage", rename_all = "camelCase")]
857pub struct SuiRawMovePackage {
858    pub id: ObjectId,
859    pub version: Version,
860    #[serde_as(as = "BTreeMap<_, Base64>")]
861    pub module_map: BTreeMap<String, Vec<u8>>,
862    pub type_origin_table: Vec<TypeOrigin>,
863    pub linkage_table: BTreeMap<ObjectId, UpgradeInfo>,
864}
865
866/// Errors for [`SuiPastObjectResponse`].
867#[derive(thiserror::Error, Eq, PartialEq, Clone, Debug, Serialize, Deserialize, Hash)]
868pub enum SuiPastObjectResponseError {
869    #[error("Could not find the referenced object {object_id:?} at version {version:?}.")]
870    ObjectNotFound {
871        object_id: ObjectId,
872        version: Option<Version>,
873    },
874
875    #[error(
876        "Could not find the referenced object {object_id:?} \
877            as the asked version {asked_version:?} \
878            is higher than the latest {latest_version:?}"
879    )]
880    ObjectSequenceNumberTooHigh {
881        object_id: ObjectId,
882        asked_version: Version,
883        latest_version: Version,
884    },
885
886    #[error("Object deleted at reference {object_ref:?}.")]
887    ObjectDeleted { object_ref: ObjectRef },
888}
889
890#[rustversion::attr(nightly, expect(clippy::large_enum_variant))]
891#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
892#[serde(tag = "status", content = "details", rename = "ObjectRead")]
893pub enum SuiPastObjectResponse {
894    /// The object exists and is found with this version
895    VersionFound(SuiObjectData),
896    /// The object does not exist
897    ObjectNotExists(ObjectId),
898    /// The object is found to be deleted with this version
899    ObjectDeleted(SuiObjectRef),
900    /// The object exists but not found with this version
901    VersionNotFound(ObjectId, Version),
902    /// The asked object version is higher than the latest
903    VersionTooHigh {
904        object_id: ObjectId,
905        asked_version: Version,
906        latest_version: Version,
907    },
908}
909
910impl SuiPastObjectResponse {
911    /// Returns a reference to the object if there is any, otherwise an Err
912    pub fn object(&self) -> Result<&SuiObjectData, SuiPastObjectResponseError> {
913        match &self {
914            Self::ObjectDeleted(oref) => Err(SuiPastObjectResponseError::ObjectDeleted {
915                object_ref: oref.to_object_ref(),
916            }),
917            Self::ObjectNotExists(id) => Err(SuiPastObjectResponseError::ObjectNotFound {
918                object_id: *id,
919                version: None,
920            }),
921            Self::VersionFound(o) => Ok(o),
922            Self::VersionNotFound(id, seq_num) => Err(SuiPastObjectResponseError::ObjectNotFound {
923                object_id: *id,
924                version: Some(*seq_num),
925            }),
926            Self::VersionTooHigh {
927                object_id,
928                asked_version,
929                latest_version,
930            } => Err(SuiPastObjectResponseError::ObjectSequenceNumberTooHigh {
931                object_id: *object_id,
932                asked_version: *asked_version,
933                latest_version: *latest_version,
934            }),
935        }
936    }
937
938    /// Returns the object value if there is any, otherwise an Err
939    pub fn into_object(self) -> Result<SuiObjectData, SuiPastObjectResponseError> {
940        match self {
941            Self::ObjectDeleted(oref) => Err(SuiPastObjectResponseError::ObjectDeleted {
942                object_ref: oref.to_object_ref(),
943            }),
944            Self::ObjectNotExists(id) => Err(SuiPastObjectResponseError::ObjectNotFound {
945                object_id: id,
946                version: None,
947            }),
948            Self::VersionFound(o) => Ok(o),
949            Self::VersionNotFound(object_id, version) => {
950                Err(SuiPastObjectResponseError::ObjectNotFound {
951                    object_id,
952                    version: Some(version),
953                })
954            }
955            Self::VersionTooHigh {
956                object_id,
957                asked_version,
958                latest_version,
959            } => Err(SuiPastObjectResponseError::ObjectSequenceNumberTooHigh {
960                object_id,
961                asked_version,
962                latest_version,
963            }),
964        }
965    }
966}
967
968#[derive(Debug, Deserialize, Serialize, Clone, Eq, PartialEq)]
969#[serde(rename = "MovePackage", rename_all = "camelCase")]
970pub struct SuiMovePackage {
971    pub disassembled: BTreeMap<String, Value>,
972}
973
974pub type QueryObjectsPage = Page<SuiObjectResponse, CheckpointedObjectId>;
975pub type ObjectsPage = Page<SuiObjectResponse, ObjectId>;
976
977#[serde_as]
978#[derive(Debug, Deserialize, Serialize, Clone, Copy, Eq, PartialEq)]
979#[serde(rename_all = "camelCase")]
980pub struct CheckpointedObjectId {
981    pub object_id: ObjectId,
982    #[serde_as(as = "Option<BigInt<u64>>")]
983    #[serde(skip_serializing_if = "Option::is_none")]
984    pub at_checkpoint: Option<Version>,
985}
986
987#[serde_as]
988#[derive(Debug, Deserialize, Serialize, Clone, Eq, PartialEq)]
989#[serde(rename = "GetPastObjectRequest", rename_all = "camelCase")]
990pub struct SuiGetPastObjectRequest {
991    /// the ID of the queried object
992    pub object_id: ObjectId,
993    /// the version of the queried object.
994    #[serde_as(as = "BigInt<u64>")]
995    pub version: Version,
996}
997
998#[serde_as]
999#[derive(Clone, Debug, Serialize, Deserialize)]
1000pub enum SuiObjectDataFilter {
1001    MatchAll(Vec<SuiObjectDataFilter>),
1002    MatchAny(Vec<SuiObjectDataFilter>),
1003    MatchNone(Vec<SuiObjectDataFilter>),
1004    /// Query by type a specified Package.
1005    Package(ObjectId),
1006    /// Query by type a specified Move module.
1007    MoveModule {
1008        /// the Move package ID
1009        package: ObjectId,
1010        /// the module name
1011        #[serde_as(as = "DisplayFromStr")]
1012        module: Identifier,
1013    },
1014    /// Query by type
1015    // StructType(#[serde_as(as = "SuiStructTag")] StructTag),
1016    StructType(#[serde_as(as = "DisplayFromStr")] StructTag),
1017    AddressOwner(SuiAddress),
1018    ObjectOwner(ObjectId),
1019    ObjectId(ObjectId),
1020    // allow querying for multiple object ids
1021    ObjectIds(Vec<ObjectId>),
1022    Version(#[serde_as(as = "BigInt<u64>")] u64),
1023}
1024
1025impl SuiObjectDataFilter {
1026    pub fn gas_coin() -> Self {
1027        Self::StructType(StructTag::gas_coin())
1028    }
1029
1030    pub fn and(self, other: Self) -> Self {
1031        Self::MatchAll(vec![self, other])
1032    }
1033    pub fn or(self, other: Self) -> Self {
1034        Self::MatchAny(vec![self, other])
1035    }
1036    pub fn not(self, other: Self) -> Self {
1037        Self::MatchNone(vec![self, other])
1038    }
1039}
1040
1041#[derive(Debug, Clone, Deserialize, Serialize, Default)]
1042#[serde(rename_all = "camelCase", rename = "ObjectResponseQuery", default)]
1043pub struct SuiObjectResponseQuery {
1044    /// If None, no filter will be applied
1045    pub filter: Option<SuiObjectDataFilter>,
1046    /// config which fields to include in the response, by default only digest is included
1047    pub options: Option<SuiObjectDataOptions>,
1048}
1049
1050impl SuiObjectResponseQuery {
1051    pub fn new(filter: Option<SuiObjectDataFilter>, options: Option<SuiObjectDataOptions>) -> Self {
1052        Self { filter, options }
1053    }
1054
1055    pub fn new_with_filter(filter: SuiObjectDataFilter) -> Self {
1056        Self {
1057            filter: Some(filter),
1058            options: None,
1059        }
1060    }
1061
1062    pub fn new_with_options(options: SuiObjectDataOptions) -> Self {
1063        Self {
1064            filter: None,
1065            options: Some(options),
1066        }
1067    }
1068}