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