1use async_graphql::{connection::Connection, *};
6use iota_types::{
7 TypeTag,
8 coin::{CoinMetadata as NativeCoinMetadata, TreasuryCap},
9 gas_coin::GAS,
10};
11
12use crate::{
13 connection::ScanConnection,
14 context_data::db_data_provider::PgManager,
15 data::Db,
16 error::Error,
17 types::{
18 balance::{self, Balance},
19 base64::Base64,
20 big_int::BigInt,
21 coin::Coin,
22 display::DisplayEntry,
23 dynamic_field::{DynamicField, DynamicFieldName},
24 iota_address::IotaAddress,
25 iota_names_registration::{NameFormat, NameRegistration},
26 move_object::{MoveObject, MoveObjectImpl},
27 move_value::MoveValue,
28 object::{self, Object, ObjectFilter, ObjectImpl, ObjectOwner, ObjectStatus},
29 owner::OwnerImpl,
30 stake::StakedIota,
31 transaction_block::{self, TransactionBlock, TransactionBlockFilter},
32 type_filter::ExactTypeFilter,
33 uint53::UInt53,
34 },
35};
36
37pub(crate) struct CoinMetadata {
38 pub super_: MoveObject,
39 pub native: NativeCoinMetadata,
40}
41
42pub(crate) enum CoinMetadataDowncastError {
43 NotCoinMetadata,
44 Bcs(bcs::Error),
45}
46
47#[Object]
49impl CoinMetadata {
50 pub(crate) async fn address(&self) -> IotaAddress {
51 OwnerImpl::from(&self.super_.super_).address().await
52 }
53
54 pub(crate) async fn objects(
56 &self,
57 ctx: &Context<'_>,
58 first: Option<u64>,
59 after: Option<object::Cursor>,
60 last: Option<u64>,
61 before: Option<object::Cursor>,
62 filter: Option<ObjectFilter>,
63 ) -> Result<Connection<String, MoveObject>> {
64 OwnerImpl::from(&self.super_.super_)
65 .objects(ctx, first, after, last, before, filter)
66 .await
67 }
68
69 pub(crate) async fn balance(
72 &self,
73 ctx: &Context<'_>,
74 type_: Option<ExactTypeFilter>,
75 ) -> Result<Option<Balance>> {
76 OwnerImpl::from(&self.super_.super_)
77 .balance(ctx, type_)
78 .await
79 }
80
81 pub(crate) async fn balances(
83 &self,
84 ctx: &Context<'_>,
85 first: Option<u64>,
86 after: Option<balance::Cursor>,
87 last: Option<u64>,
88 before: Option<balance::Cursor>,
89 ) -> Result<Connection<String, Balance>> {
90 OwnerImpl::from(&self.super_.super_)
91 .balances(ctx, first, after, last, before)
92 .await
93 }
94
95 pub(crate) async fn coins(
100 &self,
101 ctx: &Context<'_>,
102 first: Option<u64>,
103 after: Option<object::Cursor>,
104 last: Option<u64>,
105 before: Option<object::Cursor>,
106 type_: Option<ExactTypeFilter>,
107 ) -> Result<Connection<String, Coin>> {
108 OwnerImpl::from(&self.super_.super_)
109 .coins(ctx, first, after, last, before, type_)
110 .await
111 }
112
113 pub(crate) async fn staked_iotas(
115 &self,
116 ctx: &Context<'_>,
117 first: Option<u64>,
118 after: Option<object::Cursor>,
119 last: Option<u64>,
120 before: Option<object::Cursor>,
121 ) -> Result<Connection<String, StakedIota>> {
122 OwnerImpl::from(&self.super_.super_)
123 .staked_iotas(ctx, first, after, last, before)
124 .await
125 }
126
127 pub(crate) async fn iota_names_default_name(
130 &self,
131 ctx: &Context<'_>,
132 format: Option<NameFormat>,
133 ) -> Result<Option<String>> {
134 OwnerImpl::from(&self.super_.super_)
135 .iota_names_default_name(ctx, format)
136 .await
137 }
138
139 pub(crate) async fn iota_names_registrations(
142 &self,
143 ctx: &Context<'_>,
144 first: Option<u64>,
145 after: Option<object::Cursor>,
146 last: Option<u64>,
147 before: Option<object::Cursor>,
148 ) -> Result<Connection<String, NameRegistration>> {
149 OwnerImpl::from(&self.super_.super_)
150 .iota_names_registrations(ctx, first, after, last, before)
151 .await
152 }
153
154 pub(crate) async fn version(&self) -> UInt53 {
155 ObjectImpl(&self.super_.super_).version().await
156 }
157
158 pub(crate) async fn status(&self) -> ObjectStatus {
167 ObjectImpl(&self.super_.super_).status().await
168 }
169
170 pub(crate) async fn digest(&self) -> Option<String> {
173 ObjectImpl(&self.super_.super_).digest().await
174 }
175
176 pub(crate) async fn owner(&self, ctx: &Context<'_>) -> Option<ObjectOwner> {
178 ObjectImpl(&self.super_.super_).owner(ctx).await
179 }
180
181 pub(crate) async fn previous_transaction_block(
183 &self,
184 ctx: &Context<'_>,
185 ) -> Result<Option<TransactionBlock>> {
186 ObjectImpl(&self.super_.super_)
187 .previous_transaction_block(ctx)
188 .await
189 }
190
191 pub(crate) async fn storage_rebate(&self) -> Option<BigInt> {
195 ObjectImpl(&self.super_.super_).storage_rebate().await
196 }
197
198 pub(crate) async fn received_transaction_blocks(
224 &self,
225 ctx: &Context<'_>,
226 first: Option<u64>,
227 after: Option<transaction_block::Cursor>,
228 last: Option<u64>,
229 before: Option<transaction_block::Cursor>,
230 filter: Option<TransactionBlockFilter>,
231 scan_limit: Option<u64>,
232 ) -> Result<ScanConnection<String, TransactionBlock>> {
233 ObjectImpl(&self.super_.super_)
234 .received_transaction_blocks(ctx, first, after, last, before, filter, scan_limit)
235 .await
236 }
237
238 pub(crate) async fn bcs(&self) -> Result<Option<Base64>> {
240 ObjectImpl(&self.super_.super_).bcs().await
241 }
242
243 pub(crate) async fn contents(&self) -> Option<MoveValue> {
247 MoveObjectImpl(&self.super_).contents().await
248 }
249
250 pub(crate) async fn display(&self, ctx: &Context<'_>) -> Result<Option<Vec<DisplayEntry>>> {
254 ObjectImpl(&self.super_.super_).display(ctx).await
255 }
256
257 pub(crate) async fn dynamic_field(
264 &self,
265 ctx: &Context<'_>,
266 name: DynamicFieldName,
267 ) -> Result<Option<DynamicField>> {
268 OwnerImpl::from(&self.super_.super_)
269 .dynamic_field(ctx, name, Some(self.super_.root_version()))
270 .await
271 }
272
273 pub(crate) async fn dynamic_object_field(
282 &self,
283 ctx: &Context<'_>,
284 name: DynamicFieldName,
285 ) -> Result<Option<DynamicField>> {
286 OwnerImpl::from(&self.super_.super_)
287 .dynamic_object_field(ctx, name, Some(self.super_.root_version()))
288 .await
289 }
290
291 pub(crate) async fn dynamic_fields(
296 &self,
297 ctx: &Context<'_>,
298 first: Option<u64>,
299 after: Option<object::Cursor>,
300 last: Option<u64>,
301 before: Option<object::Cursor>,
302 ) -> Result<Connection<String, DynamicField>> {
303 OwnerImpl::from(&self.super_.super_)
304 .dynamic_fields(
305 ctx,
306 first,
307 after,
308 last,
309 before,
310 Some(self.super_.root_version()),
311 )
312 .await
313 }
314
315 async fn decimals(&self) -> Option<u8> {
317 Some(self.native.decimals)
318 }
319
320 async fn name(&self) -> Option<&str> {
322 Some(&self.native.name)
323 }
324
325 async fn symbol(&self) -> Option<&str> {
327 Some(&self.native.symbol)
328 }
329
330 async fn description(&self) -> Option<&str> {
332 Some(&self.native.description)
333 }
334
335 async fn icon_url(&self) -> Option<&str> {
336 self.native.icon_url.as_deref()
337 }
338
339 async fn supply(&self, ctx: &Context<'_>) -> Result<Option<BigInt>> {
341 let mut type_params = self.super_.native.type_().type_params();
342 let Some(coin_type) = type_params.pop() else {
343 return Ok(None);
344 };
345
346 let supply = CoinMetadata::query_total_supply(
347 ctx,
348 coin_type,
349 self.super_.super_.checkpoint_viewed_at,
350 )
351 .await
352 .extend()?;
353
354 Ok(supply.map(BigInt::from))
355 }
356}
357
358impl CoinMetadata {
359 pub(crate) async fn query(
362 db: &Db,
363 coin_type: TypeTag,
364 checkpoint_viewed_at: u64,
365 ) -> Result<Option<CoinMetadata>, Error> {
366 let TypeTag::Struct(coin_struct) = coin_type else {
367 return Ok(None);
370 };
371
372 let metadata_type = NativeCoinMetadata::type_(*coin_struct);
373 let Some(object) = Object::query_singleton(db, metadata_type, checkpoint_viewed_at).await?
374 else {
375 return Ok(None);
376 };
377
378 let move_object = MoveObject::try_from(&object).map_err(|_| {
379 Error::Internal(format!(
380 "Expected {} to be CoinMetadata, but it is not an object.",
381 object.address,
382 ))
383 })?;
384
385 let coin_metadata = CoinMetadata::try_from(&move_object).map_err(|_| {
386 Error::Internal(format!(
387 "Expected {} to be CoinMetadata, but it is not.",
388 object.address,
389 ))
390 })?;
391
392 Ok(Some(coin_metadata))
393 }
394
395 pub(crate) async fn query_total_supply(
396 ctx: &Context<'_>,
397 coin_type: TypeTag,
398 checkpoint_viewed_at: u64,
399 ) -> Result<Option<u64>, Error> {
400 let TypeTag::Struct(coin_struct) = coin_type else {
401 return Ok(None);
404 };
405
406 Ok(Some(if GAS::is_gas(coin_struct.as_ref()) {
407 let pg_manager = ctx.data_unchecked::<PgManager>();
408
409 let state = pg_manager.fetch_iota_system_state(None).await?;
410
411 state.iota_total_supply()
412 } else {
413 let cap_type = TreasuryCap::type_(*coin_struct);
414
415 let db = ctx.data_unchecked();
416
417 let Some(object) = Object::query_singleton(db, cap_type, checkpoint_viewed_at).await?
418 else {
419 return Ok(None);
420 };
421
422 let Some(native) = object.native_impl() else {
423 return Ok(None);
424 };
425
426 let treasury_cap = TreasuryCap::try_from(native.clone()).map_err(|e| {
427 Error::Internal(format!(
428 "Error while deserializing treasury cap {}: {e}",
429 object.address,
430 ))
431 })?;
432
433 treasury_cap.total_supply.value
434 }))
435 }
436}
437
438impl TryFrom<&MoveObject> for CoinMetadata {
439 type Error = CoinMetadataDowncastError;
440
441 fn try_from(move_object: &MoveObject) -> Result<Self, Self::Error> {
442 if !move_object.native.type_().is_coin_metadata() {
443 return Err(CoinMetadataDowncastError::NotCoinMetadata);
444 }
445
446 Ok(Self {
447 super_: move_object.clone(),
448 native: bcs::from_bytes(move_object.native.contents())
449 .map_err(CoinMetadataDowncastError::Bcs)?,
450 })
451 }
452}