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(s: StructTag) -> Self {
558        Self(if s == StructTag::gas_coin() {
559            MoveObjectType_::GasCoin
560        } else if let Some(coin_type) = s.is_coin() {
561            MoveObjectType_::Coin(coin_type.clone())
562        } else if s == StructTag::staked_sui() {
563            MoveObjectType_::StakedSui
564        } else {
565            MoveObjectType_::Other(s)
566        })
567    }
568}
569
570impl From<MoveObjectType> for StructTag {
571    fn from(t: MoveObjectType) -> Self {
572        match t.0 {
573            MoveObjectType_::GasCoin => Self::gas_coin(),
574            MoveObjectType_::StakedSui => Self::staked_sui(),
575            MoveObjectType_::Coin(inner) => Self::coin(inner),
576            MoveObjectType_::Other(s) => s,
577        }
578    }
579}
580
581impl From<MoveObjectType> for TypeTag {
582    fn from(o: MoveObjectType) -> Self {
583        let s: StructTag = o.into();
584        Self::Struct(Box::new(s))
585    }
586}
587
588/// The internal representation for [`MoveObjectType`].
589///
590/// It's private to prevent incorrectly constructing an `Other` instead of one of the specialized
591/// variants, e.g. `Other(GasCoin::type_())` instead of `GasCoin`
592#[derive(Eq, PartialEq, PartialOrd, Ord, Debug, Clone, Deserialize, Serialize, Hash)]
593enum MoveObjectType_ {
594    /// A type that is not `0x2::coin::Coin<T>`
595    Other(StructTag),
596    /// A SUI coin (i.e., `0x2::coin::Coin<0x2::sui::SUI>`)
597    GasCoin,
598    /// A record of a staked SUI coin (i.e., `0x3::staking_pool::StakedSui`)
599    StakedSui,
600    /// A non-SUI coin type (i.e., `0x2::coin::Coin<T> where T != 0x2::sui::SUI`)
601    Coin(TypeTag),
602    // NOTE: if adding a new type here, and there are existing on-chain objects of that
603    // type with Other(_), that is ok, but you must hand-roll PartialEq/Eq/Ord/maybe Hash
604    // to make sure the new type and Other(_) are interpreted consistently.
605}
606
607// =============================================================================
608//  ObjectType
609// =============================================================================
610
611const PACKAGE: &str = "package";
612/// Type of a Sui object
613///
614/// Originally from `sui_types::base_types`.
615#[derive(Clone, Serialize, Deserialize, Ord, PartialOrd, Eq, PartialEq, Debug)]
616pub enum ObjectType {
617    /// Move package containing one or more bytecode modules
618    Package,
619    /// A Move struct of the given type
620    Struct(MoveObjectType),
621}
622
623impl TryFrom<ObjectType> for StructTag {
624    type Error = NotMoveStructError;
625
626    fn try_from(o: ObjectType) -> Result<Self, Self::Error> {
627        match o {
628            ObjectType::Package => Err(NotMoveStructError),
629            ObjectType::Struct(move_object_type) => Ok(move_object_type.into()),
630        }
631    }
632}
633
634#[derive(thiserror::Error, Clone, Debug, PartialEq, Eq)]
635#[error("Cannot create StructTag from Package")]
636pub struct NotMoveStructError;
637
638impl Display for ObjectType {
639    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
640        match self {
641            ObjectType::Package => write!(f, "{}", PACKAGE),
642            ObjectType::Struct(t) => write!(f, "{}", t),
643        }
644    }
645}
646
647impl FromStr for ObjectType {
648    type Err = <StructTag as FromStr>::Err;
649
650    fn from_str(s: &str) -> Result<Self, Self::Err> {
651        if s.to_lowercase() == PACKAGE {
652            Ok(ObjectType::Package)
653        } else {
654            let tag: StructTag = s.parse()?;
655            Ok(ObjectType::Struct(MoveObjectType::from(tag)))
656        }
657    }
658}
659
660// =============================================================================
661//  SuiObjectDataOptions
662// =============================================================================
663
664#[derive(Debug, Clone, Deserialize, Serialize, Eq, PartialEq, Default)]
665#[serde(rename_all = "camelCase", rename = "ObjectDataOptions", default)]
666pub struct SuiObjectDataOptions {
667    /// Whether to show the type of the object. Default to be False
668    pub show_type: bool,
669    /// Whether to show the owner of the object. Default to be False
670    pub show_owner: bool,
671    /// Whether to show the previous transaction digest of the object. Default to be False
672    pub show_previous_transaction: bool,
673    /// Whether to show the Display metadata of the object for frontend rendering. Default to be False
674    pub show_display: bool,
675    /// Whether to show the content(i.e., package content or Move struct content) of the object.
676    /// Default to be False
677    pub show_content: bool,
678    /// Whether to show the content in BCS format. Default to be False
679    pub show_bcs: bool,
680    /// Whether to show the storage rebate of the object. Default to be False
681    pub show_storage_rebate: bool,
682}
683
684impl SuiObjectDataOptions {
685    pub fn new() -> Self {
686        Self::default()
687    }
688
689    /// Just enough information to create an [`Object`].
690    pub fn full_object() -> Self {
691        Self {
692            show_bcs: true,
693            show_owner: true,
694            show_storage_rebate: true,
695            show_previous_transaction: true,
696            show_content: false,
697            show_display: false,
698            show_type: false,
699        }
700    }
701
702    /// return BCS data and all other metadata such as storage rebate
703    pub fn bcs_lossless() -> Self {
704        Self {
705            show_bcs: true,
706            show_type: true,
707            show_owner: true,
708            show_previous_transaction: true,
709            show_display: false,
710            show_content: false,
711            show_storage_rebate: true,
712        }
713    }
714
715    /// return full content except bcs
716    pub fn full_content() -> Self {
717        Self {
718            show_bcs: false,
719            show_type: true,
720            show_owner: true,
721            show_previous_transaction: true,
722            show_display: false,
723            show_content: true,
724            show_storage_rebate: true,
725        }
726    }
727
728    pub fn with_content(mut self) -> Self {
729        self.show_content = true;
730        self
731    }
732
733    pub fn with_owner(mut self) -> Self {
734        self.show_owner = true;
735        self
736    }
737
738    pub fn with_type(mut self) -> Self {
739        self.show_type = true;
740        self
741    }
742
743    pub fn with_display(mut self) -> Self {
744        self.show_display = true;
745        self
746    }
747
748    pub fn with_bcs(mut self) -> Self {
749        self.show_bcs = true;
750        self
751    }
752
753    pub fn with_previous_transaction(mut self) -> Self {
754        self.show_previous_transaction = true;
755        self
756    }
757
758    pub fn is_not_in_object_info(&self) -> bool {
759        self.show_bcs || self.show_content || self.show_display || self.show_storage_rebate
760    }
761}
762
763// =============================================================================
764//  SuiObjectRef
765// =============================================================================
766
767#[derive(Debug, Clone, Deserialize, Serialize, Eq, PartialEq, Ord, PartialOrd)]
768#[serde(rename_all = "camelCase", rename = "ObjectRef")]
769pub struct SuiObjectRef {
770    /// Hex code as string representing the object id
771    pub object_id: Address,
772    /// Object version.
773    pub version: Version,
774    /// Base64 string representing the object digest
775    pub digest: Digest,
776}
777
778impl SuiObjectRef {
779    pub fn to_object_ref(&self) -> (Address, Version, Digest) {
780        (self.object_id, self.version, self.digest)
781    }
782}
783
784impl Display for SuiObjectRef {
785    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
786        write!(
787            f,
788            "Object ID: {}, version: {}, digest: {}",
789            self.object_id, self.version, self.digest
790        )
791    }
792}
793
794impl From<(Address, Version, Digest)> for SuiObjectRef {
795    fn from(oref: (Address, Version, Digest)) -> Self {
796        Self {
797            object_id: oref.0,
798            version: oref.1,
799            digest: oref.2,
800        }
801    }
802}
803
804// =============================================================================
805//  SuiData
806// =============================================================================
807
808pub trait SuiData: Sized {
809    type ObjectType;
810    type PackageType;
811    fn try_as_move(&self) -> Option<&Self::ObjectType>;
812    fn try_into_move(self) -> Option<Self::ObjectType>;
813    fn try_as_package(&self) -> Option<&Self::PackageType>;
814    fn type_(&self) -> Option<&StructTag>;
815}
816
817#[derive(Debug, Deserialize, Serialize, Clone, Eq, PartialEq)]
818#[serde(tag = "dataType", rename_all = "camelCase", rename = "RawData")]
819pub enum SuiRawData {
820    // Manually handle generic schema generation
821    MoveObject(SuiRawMoveObject),
822    Package(SuiRawMovePackage),
823}
824
825impl SuiData for SuiRawData {
826    type ObjectType = SuiRawMoveObject;
827    type PackageType = SuiRawMovePackage;
828
829    fn try_as_move(&self) -> Option<&Self::ObjectType> {
830        match self {
831            Self::MoveObject(o) => Some(o),
832            Self::Package(_) => None,
833        }
834    }
835
836    fn try_into_move(self) -> Option<Self::ObjectType> {
837        match self {
838            Self::MoveObject(o) => Some(o),
839            Self::Package(_) => None,
840        }
841    }
842
843    fn try_as_package(&self) -> Option<&Self::PackageType> {
844        match self {
845            Self::MoveObject(_) => None,
846            Self::Package(p) => Some(p),
847        }
848    }
849
850    fn type_(&self) -> Option<&StructTag> {
851        match self {
852            Self::MoveObject(o) => Some(&o.type_),
853            Self::Package(_) => None,
854        }
855    }
856}
857
858#[allow(clippy::large_enum_variant)]
859#[derive(Debug, Deserialize, Serialize, Clone, Eq, PartialEq)]
860#[serde(tag = "dataType", rename_all = "camelCase", rename = "Data")]
861pub enum SuiParsedData {
862    // Manually handle generic schema generation
863    MoveObject(SuiParsedMoveObject),
864    Package(SuiMovePackage),
865}
866
867impl SuiData for SuiParsedData {
868    type ObjectType = SuiParsedMoveObject;
869    type PackageType = SuiMovePackage;
870
871    fn try_as_move(&self) -> Option<&Self::ObjectType> {
872        match self {
873            Self::MoveObject(o) => Some(o),
874            Self::Package(_) => None,
875        }
876    }
877
878    fn try_into_move(self) -> Option<Self::ObjectType> {
879        match self {
880            Self::MoveObject(o) => Some(o),
881            Self::Package(_) => None,
882        }
883    }
884
885    fn try_as_package(&self) -> Option<&Self::PackageType> {
886        match self {
887            Self::MoveObject(_) => None,
888            Self::Package(p) => Some(p),
889        }
890    }
891
892    fn type_(&self) -> Option<&StructTag> {
893        match self {
894            Self::MoveObject(o) => Some(&o.type_),
895            Self::Package(_) => None,
896        }
897    }
898}
899
900impl Display for SuiParsedData {
901    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
902        let mut writer = String::new();
903        match self {
904            SuiParsedData::MoveObject(o) => {
905                writeln!(writer, "{}: {}", "type".bold().bright_black(), o.type_)?;
906                write!(writer, "{}", &o.fields)?;
907            }
908            SuiParsedData::Package(p) => {
909                write!(
910                    writer,
911                    "{}: {:?}",
912                    "Modules".bold().bright_black(),
913                    p.disassembled.keys()
914                )?;
915            }
916        }
917        write!(f, "{}", writer)
918    }
919}
920
921pub trait SuiMoveObject: Sized {
922    fn type_(&self) -> &StructTag;
923}
924
925#[serde_as]
926#[derive(Debug, Deserialize, Serialize, Clone, Eq, PartialEq)]
927#[serde(rename = "MoveObject", rename_all = "camelCase")]
928pub struct SuiParsedMoveObject {
929    #[serde(rename = "type")]
930    // #[serde_as(as = "SuiStructTag")]
931    #[serde_as(as = "DisplayFromStr")]
932    pub type_: StructTag,
933    pub has_public_transfer: bool,
934    pub fields: SuiMoveStruct,
935}
936
937impl SuiMoveObject for SuiParsedMoveObject {
938    fn type_(&self) -> &StructTag {
939        &self.type_
940    }
941}
942
943impl SuiParsedMoveObject {
944    pub fn read_dynamic_field_value(&self, field_name: &str) -> Option<SuiMoveValue> {
945        match &self.fields {
946            SuiMoveStruct::WithFields(fields) => fields.get(field_name).cloned(),
947            SuiMoveStruct::WithTypes { fields, .. } => fields.get(field_name).cloned(),
948            _ => None,
949        }
950    }
951}
952
953#[serde_as]
954#[derive(Debug, Deserialize, Serialize, Clone, Eq, PartialEq)]
955#[serde(rename = "RawMoveObject", rename_all = "camelCase")]
956pub struct SuiRawMoveObject {
957    #[serde(rename = "type")]
958    // #[serde_as(as = "SuiStructTag")]
959    #[serde_as(as = "DisplayFromStr")]
960    pub type_: StructTag,
961    pub has_public_transfer: bool,
962    pub version: Version,
963    #[serde_as(as = "Base64")]
964    pub bcs_bytes: Vec<u8>,
965}
966
967impl SuiMoveObject for SuiRawMoveObject {
968    fn type_(&self) -> &StructTag {
969        &self.type_
970    }
971}
972
973impl SuiRawMoveObject {
974    pub fn deserialize<'a, T: Deserialize<'a>>(&'a self) -> Result<T, sui_sdk_types::bcs::Error> {
975        sui_sdk_types::bcs::FromBcs::from_bcs(&self.bcs_bytes)
976    }
977}
978
979#[serde_as]
980#[derive(Debug, Deserialize, Serialize, Clone, Eq, PartialEq)]
981#[serde(rename = "RawMovePackage", rename_all = "camelCase")]
982pub struct SuiRawMovePackage {
983    pub id: Address,
984    pub version: Version,
985    #[serde_as(as = "BTreeMap<_, Base64>")]
986    pub module_map: BTreeMap<String, Vec<u8>>,
987    pub type_origin_table: Vec<TypeOrigin>,
988    pub linkage_table: BTreeMap<Address, UpgradeInfo>,
989}
990
991/// Errors for [`SuiPastObjectResponse`].
992#[derive(thiserror::Error, Eq, PartialEq, Clone, Debug, Serialize, Deserialize, Hash)]
993pub enum SuiPastObjectResponseError {
994    #[error("Could not find the referenced object {object_id:?} at version {version:?}.")]
995    ObjectNotFound {
996        object_id: Address,
997        version: Option<Version>,
998    },
999
1000    #[error(
1001        "Could not find the referenced object {object_id:?} \
1002            as the asked version {asked_version:?} \
1003            is higher than the latest {latest_version:?}"
1004    )]
1005    ObjectSequenceNumberTooHigh {
1006        object_id: Address,
1007        asked_version: Version,
1008        latest_version: Version,
1009    },
1010
1011    #[error("Object deleted at reference {object_ref:?}.")]
1012    ObjectDeleted {
1013        object_ref: (Address, Version, Digest),
1014    },
1015}
1016
1017#[rustversion::attr(nightly, expect(clippy::large_enum_variant))]
1018#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
1019#[serde(tag = "status", content = "details", rename = "ObjectRead")]
1020pub enum SuiPastObjectResponse {
1021    /// The object exists and is found with this version
1022    VersionFound(SuiObjectData),
1023    /// The object does not exist
1024    ObjectNotExists(Address),
1025    /// The object is found to be deleted with this version
1026    ObjectDeleted(SuiObjectRef),
1027    /// The object exists but not found with this version
1028    VersionNotFound(Address, Version),
1029    /// The asked object version is higher than the latest
1030    VersionTooHigh {
1031        object_id: Address,
1032        asked_version: Version,
1033        latest_version: Version,
1034    },
1035}
1036
1037impl SuiPastObjectResponse {
1038    /// Returns a reference to the object if there is any, otherwise an Err
1039    pub fn object(&self) -> Result<&SuiObjectData, SuiPastObjectResponseError> {
1040        match &self {
1041            Self::ObjectDeleted(oref) => Err(SuiPastObjectResponseError::ObjectDeleted {
1042                object_ref: oref.to_object_ref(),
1043            }),
1044            Self::ObjectNotExists(id) => Err(SuiPastObjectResponseError::ObjectNotFound {
1045                object_id: *id,
1046                version: None,
1047            }),
1048            Self::VersionFound(o) => Ok(o),
1049            Self::VersionNotFound(id, seq_num) => Err(SuiPastObjectResponseError::ObjectNotFound {
1050                object_id: *id,
1051                version: Some(*seq_num),
1052            }),
1053            Self::VersionTooHigh {
1054                object_id,
1055                asked_version,
1056                latest_version,
1057            } => Err(SuiPastObjectResponseError::ObjectSequenceNumberTooHigh {
1058                object_id: *object_id,
1059                asked_version: *asked_version,
1060                latest_version: *latest_version,
1061            }),
1062        }
1063    }
1064
1065    /// Returns the object value if there is any, otherwise an Err
1066    pub fn into_object(self) -> Result<SuiObjectData, SuiPastObjectResponseError> {
1067        match self {
1068            Self::ObjectDeleted(oref) => Err(SuiPastObjectResponseError::ObjectDeleted {
1069                object_ref: oref.to_object_ref(),
1070            }),
1071            Self::ObjectNotExists(id) => Err(SuiPastObjectResponseError::ObjectNotFound {
1072                object_id: id,
1073                version: None,
1074            }),
1075            Self::VersionFound(o) => Ok(o),
1076            Self::VersionNotFound(object_id, version) => {
1077                Err(SuiPastObjectResponseError::ObjectNotFound {
1078                    object_id,
1079                    version: Some(version),
1080                })
1081            }
1082            Self::VersionTooHigh {
1083                object_id,
1084                asked_version,
1085                latest_version,
1086            } => Err(SuiPastObjectResponseError::ObjectSequenceNumberTooHigh {
1087                object_id,
1088                asked_version,
1089                latest_version,
1090            }),
1091        }
1092    }
1093}
1094
1095#[derive(Debug, Deserialize, Serialize, Clone, Eq, PartialEq)]
1096#[serde(rename = "MovePackage", rename_all = "camelCase")]
1097pub struct SuiMovePackage {
1098    pub disassembled: BTreeMap<String, Value>,
1099}
1100
1101pub type QueryObjectsPage = Page<SuiObjectResponse, CheckpointedObjectId>;
1102pub type ObjectsPage = Page<SuiObjectResponse, Address>;
1103
1104#[serde_as]
1105#[derive(Debug, Deserialize, Serialize, Clone, Copy, Eq, PartialEq)]
1106#[serde(rename_all = "camelCase")]
1107pub struct CheckpointedObjectId {
1108    pub object_id: Address,
1109    #[serde_as(as = "Option<BigInt<u64>>")]
1110    #[serde(skip_serializing_if = "Option::is_none")]
1111    pub at_checkpoint: Option<Version>,
1112}
1113
1114#[serde_as]
1115#[derive(Debug, Deserialize, Serialize, Clone, Eq, PartialEq)]
1116#[serde(rename = "GetPastObjectRequest", rename_all = "camelCase")]
1117pub struct SuiGetPastObjectRequest {
1118    /// the ID of the queried object
1119    pub object_id: Address,
1120    /// the version of the queried object.
1121    #[serde_as(as = "BigInt<u64>")]
1122    pub version: Version,
1123}
1124
1125#[serde_as]
1126#[derive(Clone, Debug, Serialize, Deserialize)]
1127pub enum SuiObjectDataFilter {
1128    MatchAll(Vec<SuiObjectDataFilter>),
1129    MatchAny(Vec<SuiObjectDataFilter>),
1130    MatchNone(Vec<SuiObjectDataFilter>),
1131    /// Query by type a specified Package.
1132    Package(Address),
1133    /// Query by type a specified Move module.
1134    MoveModule {
1135        /// the Move package ID
1136        package: Address,
1137        /// the module name
1138        #[serde_as(as = "DisplayFromStr")]
1139        module: Identifier,
1140    },
1141    /// Query by type
1142    // StructType(#[serde_as(as = "SuiStructTag")] StructTag),
1143    StructType(#[serde_as(as = "DisplayFromStr")] StructTag),
1144    AddressOwner(Address),
1145    ObjectOwner(Address),
1146    Address(Address),
1147    // allow querying for multiple object ids
1148    ObjectIds(Vec<Address>),
1149    Version(#[serde_as(as = "BigInt<u64>")] u64),
1150}
1151
1152impl SuiObjectDataFilter {
1153    pub fn gas_coin() -> Self {
1154        Self::StructType(StructTag::gas_coin())
1155    }
1156
1157    pub fn and(self, other: Self) -> Self {
1158        Self::MatchAll(vec![self, other])
1159    }
1160    pub fn or(self, other: Self) -> Self {
1161        Self::MatchAny(vec![self, other])
1162    }
1163    pub fn not(self, other: Self) -> Self {
1164        Self::MatchNone(vec![self, other])
1165    }
1166}
1167
1168#[derive(Debug, Clone, Deserialize, Serialize, Default)]
1169#[serde(rename_all = "camelCase", rename = "ObjectResponseQuery", default)]
1170pub struct SuiObjectResponseQuery {
1171    /// If None, no filter will be applied
1172    pub filter: Option<SuiObjectDataFilter>,
1173    /// config which fields to include in the response, by default only digest is included
1174    pub options: Option<SuiObjectDataOptions>,
1175}
1176
1177impl SuiObjectResponseQuery {
1178    pub fn new(filter: Option<SuiObjectDataFilter>, options: Option<SuiObjectDataOptions>) -> Self {
1179        Self { filter, options }
1180    }
1181
1182    pub fn new_with_filter(filter: SuiObjectDataFilter) -> Self {
1183        Self {
1184            filter: Some(filter),
1185            options: None,
1186        }
1187    }
1188
1189    pub fn new_with_options(options: SuiObjectDataOptions) -> Self {
1190        Self {
1191            filter: None,
1192            options: Some(options),
1193        }
1194    }
1195}