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