sui_gql_client/queries/model/
fragments.rs

1use af_sui_types::{
2    Address as SuiAddress,
3    Object as ObjectSdk,
4    Transaction as TransactionSdk,
5    TypeTag,
6    Version,
7};
8use cynic::QueryFragment;
9use enum_as_inner::EnumAsInner;
10use sui_gql_schema::scalars::{self, BigInt};
11use sui_gql_schema::schema;
12
13// ====================================================================================================
14//  Query Input Fragments
15// ====================================================================================================
16
17/// This is only used in `Query.multiGetObjects` currently
18#[derive(cynic::InputObject, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
19pub struct ObjectKey {
20    pub address: SuiAddress,
21    pub version: Option<Version>,
22    pub root_version: Option<Version>,
23    pub at_checkpoint: Option<u64>,
24}
25
26#[derive(cynic::InputObject, Clone, Debug, Default)]
27#[cynic(graphql_type = "ObjectFilter")]
28pub(crate) struct ObjectFilter {
29    /// Filter objects by their type's `package`, `package::module`, or their fully qualified type
30    /// name.
31    ///
32    /// Generic types can be queried by either the generic type name, e.g. `0x2::coin::Coin`, or by
33    /// the full type name, such as `0x2::coin::Coin<0x2::sui::SUI>`.
34    #[cynic(rename = "type")]
35    pub(crate) type_: Option<String>,
36    pub(crate) owner: Option<SuiAddress>,
37    pub(crate) owner_kind: Option<OwnerKind>,
38}
39
40#[derive(Clone, Debug, cynic::Enum)]
41pub enum OwnerKind {
42    Address,
43    Object,
44    Shared,
45    Immutable,
46}
47
48#[derive(cynic::InputObject, Clone, Debug)]
49pub struct DynamicFieldName {
50    /// The string type of the DynamicField's 'name' field.
51    /// A string representation of a Move primitive like 'u64', or a struct type like '0x2::kiosk::Listing'
52    #[cynic(rename = "type")]
53    pub type_: scalars::TypeTag,
54    /// The Base64 encoded bcs serialization of the DynamicField's 'name' field.
55    pub bcs: scalars::Base64<Vec<u8>>,
56}
57
58impl<T: af_move_type::MoveType> TryFrom<af_move_type::MoveInstance<T>> for DynamicFieldName {
59    type Error = bcs::Error;
60
61    fn try_from(value: af_move_type::MoveInstance<T>) -> Result<Self, Self::Error> {
62        let af_move_type::MoveInstance { type_, value } = value;
63        Ok(Self {
64            type_: scalars::TypeTag(type_.into()),
65            bcs: scalars::Base64::new(bcs::to_bytes(&value)?),
66        })
67    }
68}
69
70#[derive(cynic::InputObject, Clone, Debug, Default)]
71pub struct TransactionFilter {
72    pub function: Option<String>,
73    pub kind: Option<TransactionKindInput>,
74    pub after_checkpoint: Option<Version>,
75    pub at_checkpoint: Option<Version>,
76    pub before_checkpoint: Option<Version>,
77    pub affected_address: Option<SuiAddress>,
78    pub sent_address: Option<SuiAddress>,
79    pub affected_object: Option<SuiAddress>,
80}
81
82#[derive(cynic::Enum, Clone, Debug)]
83pub enum TransactionKindInput {
84    SystemTx,
85    ProgrammableTx,
86}
87
88// ====================================================================================================
89//  PageInfo Fragments
90// ====================================================================================================
91
92#[derive(cynic::QueryFragment, Clone, Debug, Default)]
93pub struct PageInfo {
94    pub has_next_page: bool,
95    pub end_cursor: Option<String>,
96    pub has_previous_page: bool,
97    pub start_cursor: Option<String>,
98}
99
100impl From<PageInfoForward> for PageInfo {
101    fn from(
102        PageInfoForward {
103            has_next_page,
104            end_cursor,
105        }: PageInfoForward,
106    ) -> Self {
107        Self {
108            has_next_page,
109            end_cursor,
110            ..Default::default()
111        }
112    }
113}
114
115impl From<PageInfoBackward> for PageInfo {
116    fn from(
117        PageInfoBackward {
118            has_previous_page,
119            start_cursor,
120        }: PageInfoBackward,
121    ) -> Self {
122        Self {
123            has_previous_page,
124            start_cursor,
125            ..Default::default()
126        }
127    }
128}
129
130#[derive(cynic::QueryFragment, Clone, Debug)]
131#[cynic(graphql_type = "PageInfo")]
132pub struct PageInfoForward {
133    pub has_next_page: bool,
134    pub end_cursor: Option<String>,
135}
136
137#[derive(cynic::QueryFragment, Clone, Debug)]
138#[cynic(graphql_type = "PageInfo")]
139pub struct PageInfoBackward {
140    pub has_previous_page: bool,
141    pub start_cursor: Option<String>,
142}
143
144// =============================================================================
145//  Inner Fragments
146// =============================================================================
147
148#[derive(cynic::QueryFragment, Clone, Debug)]
149#[cynic(graphql_type = "MoveValue")]
150pub struct MoveValueGql {
151    #[cynic(rename = "type")]
152    pub type_: Option<MoveTypeGql>,
153    pub bcs: Option<scalars::Base64<Vec<u8>>>,
154}
155
156/// `ObjectConnection` where the `Object` fragment does take any parameters.
157#[derive(cynic::QueryFragment, Clone, Debug)]
158pub struct ObjectConnection {
159    pub nodes: Vec<ObjectGql>,
160    pub page_info: PageInfoForward,
161}
162
163#[derive(cynic::QueryFragment, Debug, Clone)]
164#[cynic(graphql_type = "Object")]
165pub struct ObjectGql {
166    #[cynic(rename = "address")]
167    pub id: SuiAddress,
168    #[cynic(rename = "objectBcs")]
169    pub object: Option<scalars::Base64Bcs<ObjectSdk>>,
170}
171
172impl TryFrom<MoveValueGql> for super::outputs::RawMoveValue {
173    type Error = TryFromMoveValue;
174    fn try_from(MoveValueGql { type_, bcs }: MoveValueGql) -> Result<Self, Self::Error> {
175        let (Some(type_), Some(bcs)) = (type_, bcs) else {
176            return Err(TryFromMoveValue::MissingData);
177        };
178
179        Ok(Self {
180            type_: type_.into(),
181            bcs: bcs.into_inner(),
182        })
183    }
184}
185
186impl TryFrom<MoveValueGql> for super::outputs::RawMoveStruct {
187    type Error = TryFromMoveValue;
188    fn try_from(MoveValueGql { type_, bcs }: MoveValueGql) -> Result<Self, Self::Error> {
189        let (Some(type_), Some(bcs)) = (type_, bcs) else {
190            return Err(TryFromMoveValue::MissingData);
191        };
192        let tag: TypeTag = type_.into();
193        let TypeTag::Struct(stag) = tag else {
194            return Err(TryFromMoveValue::NotMoveStructError);
195        };
196
197        Ok(Self {
198            type_: *stag,
199            bcs: bcs.into_inner(),
200        })
201    }
202}
203
204impl<T> TryFrom<MoveValueGql> for af_move_type::MoveInstance<T>
205where
206    T: af_move_type::MoveType,
207{
208    type Error = ToMoveInstanceError;
209    fn try_from(MoveValueGql { bcs, type_ }: MoveValueGql) -> Result<Self, Self::Error> {
210        let (Some(type_), Some(bcs)) = (type_, bcs) else {
211            return Err(TryFromMoveValue::MissingData.into());
212        };
213        // Fail early if type tag is not expected
214        let type_ = TypeTag::from(type_).try_into()?;
215        let value = bcs::from_bytes(bcs.as_ref())?;
216        Ok(Self { type_, value })
217    }
218}
219
220/// Helper to extract a strongly typed [`TypeTag`] from the `MoveType` GQL type.
221#[derive(cynic::QueryFragment, Clone)]
222#[cynic(graphql_type = "MoveType")]
223pub struct MoveTypeGql {
224    /// Keep this private so that we can change where we get the [TypeTag] from.
225    repr: scalars::TypeTag,
226}
227
228impl std::fmt::Debug for MoveTypeGql {
229    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
230        write!(f, "MoveTypeTag({})", self.repr.0)
231    }
232}
233
234impl From<MoveTypeGql> for TypeTag {
235    fn from(value: MoveTypeGql) -> Self {
236        value.repr.0
237    }
238}
239
240#[derive(cynic::QueryFragment, Debug)]
241#[cynic(graphql_type = "DynamicField")]
242pub struct DynamicFieldByName {
243    pub value: Option<DynamicFieldValue>,
244}
245
246#[derive(cynic::QueryFragment, Debug)]
247pub struct DynamicFieldConnection {
248    pub nodes: Vec<DynamicField>,
249    pub page_info: PageInfo,
250}
251
252#[derive(cynic::QueryFragment, Debug)]
253pub struct DynamicField {
254    pub name: Option<MoveValueGql>,
255    pub value: Option<DynamicFieldValue>,
256}
257
258#[derive(cynic::InlineFragments, Debug, EnumAsInner)]
259pub enum DynamicFieldValue {
260    MoveObject(MoveObject),
261    MoveValue(MoveValueGql),
262    #[cynic(fallback)]
263    Unknown,
264}
265
266#[derive(cynic::QueryFragment, Debug)]
267pub struct MoveObject {
268    pub address: SuiAddress,
269    pub version: Option<Version>,
270    pub contents: Option<MoveValueGql>,
271}
272
273#[derive(cynic::QueryFragment, Debug, Clone)]
274pub struct Checkpoint {
275    pub sequence_number: af_sui_types::Version,
276}
277
278#[derive(QueryFragment, Clone, Debug)]
279pub struct Epoch {
280    pub epoch_id: Version,
281    pub reference_gas_price: Option<BigInt<u64>>,
282}
283
284#[derive(cynic::QueryFragment, Debug, Clone)]
285#[cynic(graphql_type = "Transaction")]
286pub struct TransactionGql {
287    pub digest: String,
288    #[cynic(rename = "transactionBcs")]
289    pub bcs: Option<scalars::Base64Bcs<TransactionSdk>>,
290    pub effects: Option<TransactionEffects>,
291}
292
293#[derive(cynic::QueryFragment, Debug, Clone)]
294pub struct TransactionEffects {
295    pub status: Option<ExecutionStatus>,
296}
297
298#[derive(cynic::Enum, Clone, Copy, Debug)]
299pub enum ExecutionStatus {
300    Success,
301    Failure,
302}
303
304impl From<ExecutionStatus> for bool {
305    fn from(value: ExecutionStatus) -> Self {
306        match value {
307            ExecutionStatus::Success => true,
308            ExecutionStatus::Failure => false,
309        }
310    }
311}
312
313// ====================================================================================================
314//  Events
315// ====================================================================================================
316
317#[derive(cynic::InputObject, Debug, Clone)]
318pub struct EventFilter {
319    pub sender: Option<SuiAddress>,
320    pub after_checkpoint: Option<u64>,
321    pub before_checkpoint: Option<u64>,
322    pub at_checkpoint: Option<u64>,
323    #[cynic(rename = "type")]
324    pub type_: Option<String>,
325    pub module: Option<String>,
326}
327
328#[derive(cynic::QueryFragment, Debug, Clone)]
329pub struct EventEdge {
330    pub node: Event,
331    pub cursor: String,
332}
333
334#[derive(cynic::QueryFragment, Debug, Clone)]
335pub struct Event {
336    pub timestamp: Option<scalars::DateTime>,
337    pub contents: Option<MoveValueGql>,
338}
339
340// ====================================================================================================
341//  Packages
342// ====================================================================================================
343
344#[derive(cynic::QueryFragment, Debug)]
345pub struct MovePackageConnection {
346    pub nodes: Vec<MovePackage>,
347    pub page_info: PageInfoForward,
348}
349
350#[derive(cynic::QueryFragment, Debug)]
351pub struct MovePackage {
352    pub address: SuiAddress,
353    pub version: Option<Version>,
354}
355
356// ====================================================================================================
357//  Errors
358// ====================================================================================================
359
360#[derive(thiserror::Error, Debug)]
361pub enum ToMoveInstanceError {
362    #[error("Mismatched types: {0}")]
363    TypeTag(#[from] af_move_type::TypeTagError),
364    #[error("Deserializing value: {0}")]
365    Bcs(#[from] bcs::Error),
366    #[error("Deserializing value: {0}")]
367    TryFromMoveValue(#[from] TryFromMoveValue),
368}
369
370#[derive(thiserror::Error, Debug)]
371pub enum TryFromMoveValue {
372    #[error("Either type or bcs are missing")]
373    MissingData,
374
375    #[error("TypeTag is not a Struct variant")]
376    NotMoveStructError,
377}