1use crate::connection::ScanConnection;
5use crate::error::Error;
6use crate::{context_data::db_data_provider::PgManager, data::Db};
7
8use super::balance::{self, Balance};
9use super::base64::Base64;
10use super::coin::Coin;
11use super::cursor::Page;
12use super::display::DisplayEntry;
13use super::dynamic_field::{DynamicField, DynamicFieldName};
14use super::move_object::MoveObjectImpl;
15use super::move_value::MoveValue;
16use super::object::{Object, ObjectFilter, ObjectImpl, ObjectOwner, ObjectStatus};
17use super::owner::OwnerImpl;
18use super::suins_registration::{DomainFormat, SuinsRegistration};
19use super::transaction_block::{self, TransactionBlock, TransactionBlockFilter};
20use super::type_filter::ExactTypeFilter;
21use super::uint53::UInt53;
22use super::{
23 big_int::BigInt, epoch::Epoch, move_object::MoveObject, object, sui_address::SuiAddress,
24};
25use async_graphql::connection::Connection;
26use async_graphql::*;
27use move_core_types::language_storage::StructTag;
28use sui_json_rpc_types::{Stake as RpcStakedSui, StakeStatus as RpcStakeStatus};
29use sui_types::base_types::MoveObjectType;
30use sui_types::governance::StakedSui as NativeStakedSui;
31
32#[derive(Copy, Clone, Enum, PartialEq, Eq)]
33pub(crate) enum StakeStatus {
35 Active,
37 Pending,
39 Unstaked,
41}
42
43pub(crate) enum StakedSuiDowncastError {
44 NotAStakedSui,
45 Bcs(bcs::Error),
46}
47
48#[derive(Clone)]
49pub(crate) struct StakedSui {
50 pub super_: MoveObject,
52
53 pub native: NativeStakedSui,
56}
57
58#[Object]
60impl StakedSui {
61 pub(crate) async fn address(&self) -> SuiAddress {
62 OwnerImpl::from(&self.super_.super_).address().await
63 }
64
65 pub(crate) async fn objects(
67 &self,
68 ctx: &Context<'_>,
69 first: Option<u64>,
70 after: Option<object::Cursor>,
71 last: Option<u64>,
72 before: Option<object::Cursor>,
73 filter: Option<ObjectFilter>,
74 ) -> Result<Connection<String, MoveObject>> {
75 OwnerImpl::from(&self.super_.super_)
76 .objects(ctx, first, after, last, before, filter)
77 .await
78 }
79
80 pub(crate) async fn balance(
83 &self,
84 ctx: &Context<'_>,
85 type_: Option<ExactTypeFilter>,
86 ) -> Result<Option<Balance>> {
87 OwnerImpl::from(&self.super_.super_)
88 .balance(ctx, type_)
89 .await
90 }
91
92 pub(crate) async fn balances(
94 &self,
95 ctx: &Context<'_>,
96 first: Option<u64>,
97 after: Option<balance::Cursor>,
98 last: Option<u64>,
99 before: Option<balance::Cursor>,
100 ) -> Result<Connection<String, Balance>> {
101 OwnerImpl::from(&self.super_.super_)
102 .balances(ctx, first, after, last, before)
103 .await
104 }
105
106 pub(crate) async fn coins(
110 &self,
111 ctx: &Context<'_>,
112 first: Option<u64>,
113 after: Option<object::Cursor>,
114 last: Option<u64>,
115 before: Option<object::Cursor>,
116 type_: Option<ExactTypeFilter>,
117 ) -> Result<Connection<String, Coin>> {
118 OwnerImpl::from(&self.super_.super_)
119 .coins(ctx, first, after, last, before, type_)
120 .await
121 }
122
123 pub(crate) async fn staked_suis(
125 &self,
126 ctx: &Context<'_>,
127 first: Option<u64>,
128 after: Option<object::Cursor>,
129 last: Option<u64>,
130 before: Option<object::Cursor>,
131 ) -> Result<Connection<String, StakedSui>> {
132 OwnerImpl::from(&self.super_.super_)
133 .staked_suis(ctx, first, after, last, before)
134 .await
135 }
136
137 pub(crate) async fn default_suins_name(
139 &self,
140 ctx: &Context<'_>,
141 format: Option<DomainFormat>,
142 ) -> Result<Option<String>> {
143 OwnerImpl::from(&self.super_.super_)
144 .default_suins_name(ctx, format)
145 .await
146 }
147
148 pub(crate) async fn suins_registrations(
151 &self,
152 ctx: &Context<'_>,
153 first: Option<u64>,
154 after: Option<object::Cursor>,
155 last: Option<u64>,
156 before: Option<object::Cursor>,
157 ) -> Result<Connection<String, SuinsRegistration>> {
158 OwnerImpl::from(&self.super_.super_)
159 .suins_registrations(ctx, first, after, last, before)
160 .await
161 }
162
163 pub(crate) async fn version(&self) -> UInt53 {
164 ObjectImpl(&self.super_.super_).version().await
165 }
166
167 pub(crate) async fn status(&self) -> ObjectStatus {
175 ObjectImpl(&self.super_.super_).status().await
176 }
177
178 pub(crate) async fn digest(&self) -> Option<String> {
180 ObjectImpl(&self.super_.super_).digest().await
181 }
182
183 pub(crate) async fn owner(&self) -> Option<ObjectOwner> {
185 ObjectImpl(&self.super_.super_).owner().await
186 }
187
188 pub(crate) async fn previous_transaction_block(
190 &self,
191 ctx: &Context<'_>,
192 ) -> Result<Option<TransactionBlock>> {
193 ObjectImpl(&self.super_.super_)
194 .previous_transaction_block(ctx)
195 .await
196 }
197
198 pub(crate) async fn storage_rebate(&self) -> Option<BigInt> {
201 ObjectImpl(&self.super_.super_).storage_rebate().await
202 }
203
204 pub(crate) async fn received_transaction_blocks(
225 &self,
226 ctx: &Context<'_>,
227 first: Option<u64>,
228 after: Option<transaction_block::Cursor>,
229 last: Option<u64>,
230 before: Option<transaction_block::Cursor>,
231 filter: Option<TransactionBlockFilter>,
232 scan_limit: Option<u64>,
233 ) -> Result<ScanConnection<String, TransactionBlock>> {
234 ObjectImpl(&self.super_.super_)
235 .received_transaction_blocks(ctx, first, after, last, before, filter, scan_limit)
236 .await
237 }
238
239 pub(crate) async fn bcs(&self) -> Result<Option<Base64>> {
241 ObjectImpl(&self.super_.super_).bcs().await
242 }
243
244 pub(crate) async fn contents(&self) -> Option<MoveValue> {
248 MoveObjectImpl(&self.super_).contents().await
249 }
250
251 pub(crate) async fn has_public_transfer(&self, ctx: &Context<'_>) -> Result<bool> {
255 MoveObjectImpl(&self.super_).has_public_transfer(ctx).await
256 }
257
258 pub(crate) async fn display(&self, ctx: &Context<'_>) -> Result<Option<Vec<DisplayEntry>>> {
262 ObjectImpl(&self.super_.super_).display(ctx).await
263 }
264
265 pub(crate) async fn dynamic_field(
272 &self,
273 ctx: &Context<'_>,
274 name: DynamicFieldName,
275 ) -> Result<Option<DynamicField>> {
276 OwnerImpl::from(&self.super_.super_)
277 .dynamic_field(ctx, name, Some(self.super_.root_version()))
278 .await
279 }
280
281 pub(crate) async fn dynamic_object_field(
289 &self,
290 ctx: &Context<'_>,
291 name: DynamicFieldName,
292 ) -> Result<Option<DynamicField>> {
293 OwnerImpl::from(&self.super_.super_)
294 .dynamic_object_field(ctx, name, Some(self.super_.root_version()))
295 .await
296 }
297
298 pub(crate) async fn dynamic_fields(
303 &self,
304 ctx: &Context<'_>,
305 first: Option<u64>,
306 after: Option<object::Cursor>,
307 last: Option<u64>,
308 before: Option<object::Cursor>,
309 ) -> Result<Connection<String, DynamicField>> {
310 OwnerImpl::from(&self.super_.super_)
311 .dynamic_fields(
312 ctx,
313 first,
314 after,
315 last,
316 before,
317 Some(self.super_.root_version()),
318 )
319 .await
320 }
321
322 async fn stake_status(&self, ctx: &Context<'_>) -> Result<StakeStatus> {
324 Ok(match self.rpc_stake(ctx).await.extend()?.status {
325 RpcStakeStatus::Pending => StakeStatus::Pending,
326 RpcStakeStatus::Active { .. } => StakeStatus::Active,
327 RpcStakeStatus::Unstaked => StakeStatus::Unstaked,
328 })
329 }
330
331 async fn activated_epoch(&self, ctx: &Context<'_>) -> Result<Option<Epoch>> {
333 Epoch::query(
334 ctx,
335 Some(self.native.activation_epoch()),
336 self.super_.super_.checkpoint_viewed_at,
337 )
338 .await
339 .extend()
340 }
341
342 async fn requested_epoch(&self, ctx: &Context<'_>) -> Result<Option<Epoch>> {
344 Epoch::query(
345 ctx,
346 Some(self.native.request_epoch()),
347 self.super_.super_.checkpoint_viewed_at,
348 )
349 .await
350 .extend()
351 }
352
353 async fn pool_id(&self) -> Option<SuiAddress> {
355 Some(self.native.pool_id().into())
356 }
357
358 async fn principal(&self) -> Option<BigInt> {
360 Some(BigInt::from(self.native.principal()))
361 }
362
363 async fn estimated_reward(&self, ctx: &Context<'_>) -> Result<Option<BigInt>, Error> {
374 let RpcStakeStatus::Active { estimated_reward } = self.rpc_stake(ctx).await?.status else {
375 return Ok(None);
376 };
377
378 Ok(Some(BigInt::from(estimated_reward)))
379 }
380}
381
382impl StakedSui {
383 pub(crate) async fn paginate(
390 db: &Db,
391 page: Page<object::Cursor>,
392 owner: SuiAddress,
393 checkpoint_viewed_at: u64,
394 ) -> Result<Connection<String, StakedSui>, Error> {
395 let type_: StructTag = MoveObjectType::staked_sui().into();
396
397 let filter = ObjectFilter {
398 type_: Some(type_.into()),
399 owner: Some(owner),
400 ..Default::default()
401 };
402
403 Object::paginate_subtype(db, page, filter, checkpoint_viewed_at, |object| {
404 let address = object.address;
405 let move_object = MoveObject::try_from(&object).map_err(|_| {
406 Error::Internal(format!(
407 "Expected {address} to be a StakedSui, but it's not a Move Object.",
408 ))
409 })?;
410
411 StakedSui::try_from(&move_object).map_err(|_| {
412 Error::Internal(format!(
413 "Expected {address} to be a StakedSui, but it is not."
414 ))
415 })
416 })
417 .await
418 }
419
420 async fn rpc_stake(&self, ctx: &Context<'_>) -> Result<RpcStakedSui, Error> {
425 ctx.data_unchecked::<PgManager>()
426 .fetch_rpc_staked_sui(self.native.clone())
427 .await
428 }
429}
430
431impl TryFrom<&MoveObject> for StakedSui {
432 type Error = StakedSuiDowncastError;
433
434 fn try_from(move_object: &MoveObject) -> Result<Self, Self::Error> {
435 if !move_object.native.is_staked_sui() {
436 return Err(StakedSuiDowncastError::NotAStakedSui);
437 }
438
439 Ok(Self {
440 super_: move_object.clone(),
441 native: bcs::from_bytes(move_object.native.contents())
442 .map_err(StakedSuiDowncastError::Bcs)?,
443 })
444 }
445}