1use async_graphql::{connection::Connection, *};
6use iota_json_rpc_types::{Stake as RpcStakedIota, StakeStatus as RpcStakeStatus};
7use iota_types::{base_types::MoveObjectType, governance::StakedIota as NativeStakedIota};
8use move_core_types::language_storage::StructTag;
9
10use crate::{
11 connection::ScanConnection,
12 context_data::db_data_provider::PgManager,
13 data::Db,
14 error::Error,
15 types::{
16 balance::{self, Balance},
17 base64::Base64,
18 big_int::BigInt,
19 coin::Coin,
20 cursor::Page,
21 display::DisplayEntry,
22 dynamic_field::{DynamicField, DynamicFieldName},
23 epoch::Epoch,
24 iota_address::IotaAddress,
25 iota_names_registration::{DomainFormat, IotaNamesRegistration},
26 move_object::{MoveObject, MoveObjectImpl},
27 move_value::MoveValue,
28 object,
29 object::{Object, ObjectFilter, ObjectImpl, ObjectOwner, ObjectStatus},
30 owner::OwnerImpl,
31 transaction_block::{self, TransactionBlock, TransactionBlockFilter},
32 type_filter::ExactTypeFilter,
33 uint53::UInt53,
34 },
35};
36
37#[derive(Copy, Clone, Enum, PartialEq, Eq)]
38pub(crate) enum StakeStatus {
40 Active,
43 Pending,
45 Unstaked,
47}
48
49pub(crate) enum StakedIotaDowncastError {
50 NotAStakedIota,
51 Bcs(bcs::Error),
52}
53
54#[derive(Clone)]
55pub(crate) struct StakedIota {
56 pub super_: MoveObject,
58
59 pub native: NativeStakedIota,
62}
63
64#[Object]
66impl StakedIota {
67 pub(crate) async fn address(&self) -> IotaAddress {
68 OwnerImpl::from(&self.super_.super_).address().await
69 }
70
71 pub(crate) async fn objects(
73 &self,
74 ctx: &Context<'_>,
75 first: Option<u64>,
76 after: Option<object::Cursor>,
77 last: Option<u64>,
78 before: Option<object::Cursor>,
79 filter: Option<ObjectFilter>,
80 ) -> Result<Connection<String, MoveObject>> {
81 OwnerImpl::from(&self.super_.super_)
82 .objects(ctx, first, after, last, before, filter)
83 .await
84 }
85
86 pub(crate) async fn balance(
89 &self,
90 ctx: &Context<'_>,
91 type_: Option<ExactTypeFilter>,
92 ) -> Result<Option<Balance>> {
93 OwnerImpl::from(&self.super_.super_)
94 .balance(ctx, type_)
95 .await
96 }
97
98 pub(crate) async fn balances(
100 &self,
101 ctx: &Context<'_>,
102 first: Option<u64>,
103 after: Option<balance::Cursor>,
104 last: Option<u64>,
105 before: Option<balance::Cursor>,
106 ) -> Result<Connection<String, Balance>> {
107 OwnerImpl::from(&self.super_.super_)
108 .balances(ctx, first, after, last, before)
109 .await
110 }
111
112 pub(crate) async fn coins(
117 &self,
118 ctx: &Context<'_>,
119 first: Option<u64>,
120 after: Option<object::Cursor>,
121 last: Option<u64>,
122 before: Option<object::Cursor>,
123 type_: Option<ExactTypeFilter>,
124 ) -> Result<Connection<String, Coin>> {
125 OwnerImpl::from(&self.super_.super_)
126 .coins(ctx, first, after, last, before, type_)
127 .await
128 }
129
130 pub(crate) async fn staked_iotas(
132 &self,
133 ctx: &Context<'_>,
134 first: Option<u64>,
135 after: Option<object::Cursor>,
136 last: Option<u64>,
137 before: Option<object::Cursor>,
138 ) -> Result<Connection<String, StakedIota>> {
139 OwnerImpl::from(&self.super_.super_)
140 .staked_iotas(ctx, first, after, last, before)
141 .await
142 }
143
144 pub(crate) async fn iota_names_default_name(
147 &self,
148 ctx: &Context<'_>,
149 format: Option<DomainFormat>,
150 ) -> Result<Option<String>> {
151 OwnerImpl::from(&self.super_.super_)
152 .iota_names_default_name(ctx, format)
153 .await
154 }
155
156 pub(crate) async fn iota_names_registrations(
159 &self,
160 ctx: &Context<'_>,
161 first: Option<u64>,
162 after: Option<object::Cursor>,
163 last: Option<u64>,
164 before: Option<object::Cursor>,
165 ) -> Result<Connection<String, IotaNamesRegistration>> {
166 OwnerImpl::from(&self.super_.super_)
167 .iota_names_registrations(ctx, first, after, last, before)
168 .await
169 }
170
171 pub(crate) async fn version(&self) -> UInt53 {
172 ObjectImpl(&self.super_.super_).version().await
173 }
174
175 pub(crate) async fn status(&self) -> ObjectStatus {
185 ObjectImpl(&self.super_.super_).status().await
186 }
187
188 pub(crate) async fn digest(&self) -> Option<String> {
191 ObjectImpl(&self.super_.super_).digest().await
192 }
193
194 pub(crate) async fn owner(&self, ctx: &Context<'_>) -> Option<ObjectOwner> {
196 ObjectImpl(&self.super_.super_).owner(ctx).await
197 }
198
199 pub(crate) async fn previous_transaction_block(
201 &self,
202 ctx: &Context<'_>,
203 ) -> Result<Option<TransactionBlock>> {
204 ObjectImpl(&self.super_.super_)
205 .previous_transaction_block(ctx)
206 .await
207 }
208
209 pub(crate) async fn storage_rebate(&self) -> Option<BigInt> {
213 ObjectImpl(&self.super_.super_).storage_rebate().await
214 }
215
216 pub(crate) async fn received_transaction_blocks(
242 &self,
243 ctx: &Context<'_>,
244 first: Option<u64>,
245 after: Option<transaction_block::Cursor>,
246 last: Option<u64>,
247 before: Option<transaction_block::Cursor>,
248 filter: Option<TransactionBlockFilter>,
249 scan_limit: Option<u64>,
250 ) -> Result<ScanConnection<String, TransactionBlock>> {
251 ObjectImpl(&self.super_.super_)
252 .received_transaction_blocks(ctx, first, after, last, before, filter, scan_limit)
253 .await
254 }
255
256 pub(crate) async fn bcs(&self) -> Result<Option<Base64>> {
258 ObjectImpl(&self.super_.super_).bcs().await
259 }
260
261 pub(crate) async fn contents(&self) -> Option<MoveValue> {
265 MoveObjectImpl(&self.super_).contents().await
266 }
267
268 pub(crate) async fn display(&self, ctx: &Context<'_>) -> Result<Option<Vec<DisplayEntry>>> {
272 ObjectImpl(&self.super_.super_).display(ctx).await
273 }
274
275 pub(crate) async fn dynamic_field(
282 &self,
283 ctx: &Context<'_>,
284 name: DynamicFieldName,
285 ) -> Result<Option<DynamicField>> {
286 OwnerImpl::from(&self.super_.super_)
287 .dynamic_field(ctx, name, Some(self.super_.root_version()))
288 .await
289 }
290
291 pub(crate) async fn dynamic_object_field(
300 &self,
301 ctx: &Context<'_>,
302 name: DynamicFieldName,
303 ) -> Result<Option<DynamicField>> {
304 OwnerImpl::from(&self.super_.super_)
305 .dynamic_object_field(ctx, name, Some(self.super_.root_version()))
306 .await
307 }
308
309 pub(crate) async fn dynamic_fields(
314 &self,
315 ctx: &Context<'_>,
316 first: Option<u64>,
317 after: Option<object::Cursor>,
318 last: Option<u64>,
319 before: Option<object::Cursor>,
320 ) -> Result<Connection<String, DynamicField>> {
321 OwnerImpl::from(&self.super_.super_)
322 .dynamic_fields(
323 ctx,
324 first,
325 after,
326 last,
327 before,
328 Some(self.super_.root_version()),
329 )
330 .await
331 }
332
333 async fn stake_status(&self, ctx: &Context<'_>) -> Result<StakeStatus> {
335 Ok(match self.rpc_stake(ctx).await.extend()?.status {
336 RpcStakeStatus::Pending => StakeStatus::Pending,
337 RpcStakeStatus::Active { .. } => StakeStatus::Active,
338 RpcStakeStatus::Unstaked => StakeStatus::Unstaked,
339 })
340 }
341
342 async fn activated_epoch(&self, ctx: &Context<'_>) -> Result<Option<Epoch>> {
344 Epoch::query(
345 ctx,
346 Some(self.native.activation_epoch()),
347 self.super_.super_.checkpoint_viewed_at,
348 )
349 .await
350 .extend()
351 }
352
353 async fn requested_epoch(&self, ctx: &Context<'_>) -> Result<Option<Epoch>> {
355 Epoch::query(
356 ctx,
357 Some(self.native.request_epoch()),
358 self.super_.super_.checkpoint_viewed_at,
359 )
360 .await
361 .extend()
362 }
363
364 async fn pool_id(&self) -> Option<IotaAddress> {
366 Some(self.native.pool_id().into())
367 }
368
369 async fn principal(&self) -> Option<BigInt> {
371 Some(BigInt::from(self.native.principal()))
372 }
373
374 async fn estimated_reward(&self, ctx: &Context<'_>) -> Result<Option<BigInt>, Error> {
386 let RpcStakeStatus::Active { estimated_reward } = self.rpc_stake(ctx).await?.status else {
387 return Ok(None);
388 };
389
390 Ok(Some(BigInt::from(estimated_reward)))
391 }
392}
393
394impl StakedIota {
395 pub(crate) async fn paginate(
404 db: &Db,
405 page: Page<object::Cursor>,
406 owner: IotaAddress,
407 checkpoint_viewed_at: u64,
408 ) -> Result<Connection<String, StakedIota>, Error> {
409 let type_: StructTag = MoveObjectType::staked_iota().into();
410
411 let filter = ObjectFilter {
412 type_: Some(type_.into()),
413 owner: Some(owner),
414 ..Default::default()
415 };
416
417 Object::paginate_subtype(db, page, filter, checkpoint_viewed_at, |object| {
418 let address = object.address;
419 let move_object = MoveObject::try_from(&object).map_err(|_| {
420 Error::Internal(format!(
421 "Expected {address} to be a StakedIota, but it's not a Move Object.",
422 ))
423 })?;
424
425 StakedIota::try_from(&move_object).map_err(|_| {
426 Error::Internal(format!(
427 "Expected {address} to be a StakedIota, but it is not."
428 ))
429 })
430 })
431 .await
432 }
433
434 async fn rpc_stake(&self, ctx: &Context<'_>) -> Result<RpcStakedIota, Error> {
439 ctx.data_unchecked::<PgManager>()
440 .fetch_rpc_staked_iota(self.native.clone())
441 .await
442 }
443}
444
445impl TryFrom<&MoveObject> for StakedIota {
446 type Error = StakedIotaDowncastError;
447
448 fn try_from(move_object: &MoveObject) -> Result<Self, Self::Error> {
449 if !move_object.native.is_staked_iota() {
450 return Err(StakedIotaDowncastError::NotAStakedIota);
451 }
452
453 Ok(Self {
454 super_: move_object.clone(),
455 native: bcs::from_bytes(move_object.native.contents())
456 .map_err(StakedIotaDowncastError::Bcs)?,
457 })
458 }
459}