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