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::{DomainFormat, IotaNamesRegistration},
26 move_object::{MoveObject, MoveObjectImpl},
27 move_value::MoveValue,
28 object::{self, Object, ObjectFilter, ObjectImpl, ObjectOwner, ObjectStatus},
29 owner::OwnerImpl,
30 stake::StakedIota,
31 system_state_summary::SystemStateSummaryView,
32 transaction_block::{self, TransactionBlock, TransactionBlockFilter},
33 type_filter::ExactTypeFilter,
34 uint53::UInt53,
35 },
36};
37
38pub(crate) struct CoinMetadata {
39 pub super_: MoveObject,
40 pub native: NativeCoinMetadata,
41}
42
43pub(crate) enum CoinMetadataDowncastError {
44 NotCoinMetadata,
45 Bcs(bcs::Error),
46}
47
48#[Object]
50impl CoinMetadata {
51 pub(crate) async fn address(&self) -> IotaAddress {
52 OwnerImpl::from(&self.super_.super_).address().await
53 }
54
55 pub(crate) async fn objects(
57 &self,
58 ctx: &Context<'_>,
59 first: Option<u64>,
60 after: Option<object::Cursor>,
61 last: Option<u64>,
62 before: Option<object::Cursor>,
63 filter: Option<ObjectFilter>,
64 ) -> Result<Connection<String, MoveObject>> {
65 OwnerImpl::from(&self.super_.super_)
66 .objects(ctx, first, after, last, before, filter)
67 .await
68 }
69
70 pub(crate) async fn balance(
73 &self,
74 ctx: &Context<'_>,
75 type_: Option<ExactTypeFilter>,
76 ) -> Result<Option<Balance>> {
77 OwnerImpl::from(&self.super_.super_)
78 .balance(ctx, type_)
79 .await
80 }
81
82 pub(crate) async fn balances(
84 &self,
85 ctx: &Context<'_>,
86 first: Option<u64>,
87 after: Option<balance::Cursor>,
88 last: Option<u64>,
89 before: Option<balance::Cursor>,
90 ) -> Result<Connection<String, Balance>> {
91 OwnerImpl::from(&self.super_.super_)
92 .balances(ctx, first, after, last, before)
93 .await
94 }
95
96 pub(crate) async fn coins(
101 &self,
102 ctx: &Context<'_>,
103 first: Option<u64>,
104 after: Option<object::Cursor>,
105 last: Option<u64>,
106 before: Option<object::Cursor>,
107 type_: Option<ExactTypeFilter>,
108 ) -> Result<Connection<String, Coin>> {
109 OwnerImpl::from(&self.super_.super_)
110 .coins(ctx, first, after, last, before, type_)
111 .await
112 }
113
114 pub(crate) async fn staked_iotas(
116 &self,
117 ctx: &Context<'_>,
118 first: Option<u64>,
119 after: Option<object::Cursor>,
120 last: Option<u64>,
121 before: Option<object::Cursor>,
122 ) -> Result<Connection<String, StakedIota>> {
123 OwnerImpl::from(&self.super_.super_)
124 .staked_iotas(ctx, first, after, last, before)
125 .await
126 }
127
128 pub(crate) async fn iota_names_default_name(
131 &self,
132 ctx: &Context<'_>,
133 format: Option<DomainFormat>,
134 ) -> Result<Option<String>> {
135 OwnerImpl::from(&self.super_.super_)
136 .iota_names_default_name(ctx, format)
137 .await
138 }
139
140 pub(crate) async fn iota_names_registrations(
143 &self,
144 ctx: &Context<'_>,
145 first: Option<u64>,
146 after: Option<object::Cursor>,
147 last: Option<u64>,
148 before: Option<object::Cursor>,
149 ) -> Result<Connection<String, IotaNamesRegistration>> {
150 OwnerImpl::from(&self.super_.super_)
151 .iota_names_registrations(ctx, first, after, last, before)
152 .await
153 }
154
155 pub(crate) async fn version(&self) -> UInt53 {
156 ObjectImpl(&self.super_.super_).version().await
157 }
158
159 pub(crate) async fn status(&self) -> ObjectStatus {
169 ObjectImpl(&self.super_.super_).status().await
170 }
171
172 pub(crate) async fn digest(&self) -> Option<String> {
175 ObjectImpl(&self.super_.super_).digest().await
176 }
177
178 pub(crate) async fn owner(&self, ctx: &Context<'_>) -> Option<ObjectOwner> {
180 ObjectImpl(&self.super_.super_).owner(ctx).await
181 }
182
183 pub(crate) async fn previous_transaction_block(
185 &self,
186 ctx: &Context<'_>,
187 ) -> Result<Option<TransactionBlock>> {
188 ObjectImpl(&self.super_.super_)
189 .previous_transaction_block(ctx)
190 .await
191 }
192
193 pub(crate) async fn storage_rebate(&self) -> Option<BigInt> {
197 ObjectImpl(&self.super_.super_).storage_rebate().await
198 }
199
200 pub(crate) async fn received_transaction_blocks(
226 &self,
227 ctx: &Context<'_>,
228 first: Option<u64>,
229 after: Option<transaction_block::Cursor>,
230 last: Option<u64>,
231 before: Option<transaction_block::Cursor>,
232 filter: Option<TransactionBlockFilter>,
233 scan_limit: Option<u64>,
234 ) -> Result<ScanConnection<String, TransactionBlock>> {
235 ObjectImpl(&self.super_.super_)
236 .received_transaction_blocks(ctx, first, after, last, before, filter, scan_limit)
237 .await
238 }
239
240 pub(crate) async fn bcs(&self) -> Result<Option<Base64>> {
242 ObjectImpl(&self.super_.super_).bcs().await
243 }
244
245 pub(crate) async fn contents(&self) -> Option<MoveValue> {
249 MoveObjectImpl(&self.super_).contents().await
250 }
251
252 pub(crate) async fn display(&self, ctx: &Context<'_>) -> Result<Option<Vec<DisplayEntry>>> {
256 ObjectImpl(&self.super_.super_).display(ctx).await
257 }
258
259 pub(crate) async fn dynamic_field(
266 &self,
267 ctx: &Context<'_>,
268 name: DynamicFieldName,
269 ) -> Result<Option<DynamicField>> {
270 OwnerImpl::from(&self.super_.super_)
271 .dynamic_field(ctx, name, Some(self.super_.root_version()))
272 .await
273 }
274
275 pub(crate) async fn dynamic_object_field(
284 &self,
285 ctx: &Context<'_>,
286 name: DynamicFieldName,
287 ) -> Result<Option<DynamicField>> {
288 OwnerImpl::from(&self.super_.super_)
289 .dynamic_object_field(ctx, name, Some(self.super_.root_version()))
290 .await
291 }
292
293 pub(crate) async fn dynamic_fields(
298 &self,
299 ctx: &Context<'_>,
300 first: Option<u64>,
301 after: Option<object::Cursor>,
302 last: Option<u64>,
303 before: Option<object::Cursor>,
304 ) -> Result<Connection<String, DynamicField>> {
305 OwnerImpl::from(&self.super_.super_)
306 .dynamic_fields(
307 ctx,
308 first,
309 after,
310 last,
311 before,
312 Some(self.super_.root_version()),
313 )
314 .await
315 }
316
317 async fn decimals(&self) -> Option<u8> {
319 Some(self.native.decimals)
320 }
321
322 async fn name(&self) -> Option<&str> {
324 Some(&self.native.name)
325 }
326
327 async fn symbol(&self) -> Option<&str> {
329 Some(&self.native.symbol)
330 }
331
332 async fn description(&self) -> Option<&str> {
334 Some(&self.native.description)
335 }
336
337 async fn icon_url(&self) -> Option<&str> {
338 self.native.icon_url.as_deref()
339 }
340
341 async fn supply(&self, ctx: &Context<'_>) -> Result<Option<BigInt>> {
343 let mut type_params = self.super_.native.type_().type_params();
344 let Some(coin_type) = type_params.pop() else {
345 return Ok(None);
346 };
347
348 let supply = CoinMetadata::query_total_supply(
349 ctx,
350 coin_type,
351 self.super_.super_.checkpoint_viewed_at,
352 )
353 .await
354 .extend()?;
355
356 Ok(supply.map(BigInt::from))
357 }
358}
359
360impl CoinMetadata {
361 pub(crate) async fn query(
364 db: &Db,
365 coin_type: TypeTag,
366 checkpoint_viewed_at: u64,
367 ) -> Result<Option<CoinMetadata>, Error> {
368 let TypeTag::Struct(coin_struct) = coin_type else {
369 return Ok(None);
372 };
373
374 let metadata_type = NativeCoinMetadata::type_(*coin_struct);
375 let Some(object) = Object::query_singleton(db, metadata_type, checkpoint_viewed_at).await?
376 else {
377 return Ok(None);
378 };
379
380 let move_object = MoveObject::try_from(&object).map_err(|_| {
381 Error::Internal(format!(
382 "Expected {} to be CoinMetadata, but it is not an object.",
383 object.address,
384 ))
385 })?;
386
387 let coin_metadata = CoinMetadata::try_from(&move_object).map_err(|_| {
388 Error::Internal(format!(
389 "Expected {} to be CoinMetadata, but it is not.",
390 object.address,
391 ))
392 })?;
393
394 Ok(Some(coin_metadata))
395 }
396
397 pub(crate) async fn query_total_supply(
398 ctx: &Context<'_>,
399 coin_type: TypeTag,
400 checkpoint_viewed_at: u64,
401 ) -> Result<Option<u64>, Error> {
402 let TypeTag::Struct(coin_struct) = coin_type else {
403 return Ok(None);
406 };
407
408 Ok(Some(if GAS::is_gas(coin_struct.as_ref()) {
409 let pg_manager = ctx.data_unchecked::<PgManager>();
410
411 let state = pg_manager.fetch_iota_system_state(None).await?;
412
413 state.iota_total_supply()
414 } else {
415 let cap_type = TreasuryCap::type_(*coin_struct);
416
417 let db = ctx.data_unchecked();
418
419 let Some(object) = Object::query_singleton(db, cap_type, checkpoint_viewed_at).await?
420 else {
421 return Ok(None);
422 };
423
424 let Some(native) = object.native_impl() else {
425 return Ok(None);
426 };
427
428 let treasury_cap = TreasuryCap::try_from(native.clone()).map_err(|e| {
429 Error::Internal(format!(
430 "Error while deserializing treasury cap {}: {e}",
431 object.address,
432 ))
433 })?;
434
435 treasury_cap.total_supply.value
436 }))
437 }
438}
439
440impl TryFrom<&MoveObject> for CoinMetadata {
441 type Error = CoinMetadataDowncastError;
442
443 fn try_from(move_object: &MoveObject) -> Result<Self, Self::Error> {
444 if !move_object.native.type_().is_coin_metadata() {
445 return Err(CoinMetadataDowncastError::NotCoinMetadata);
446 }
447
448 Ok(Self {
449 super_: move_object.clone(),
450 native: bcs::from_bytes(move_object.native.contents())
451 .map_err(CoinMetadataDowncastError::Bcs)?,
452 })
453 }
454}