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 config::DEFAULT_PAGE_SIZE,
14 connection::ScanConnection,
15 context_data::db_data_provider::PgManager,
16 data::Db,
17 error::Error,
18 types::{
19 balance::{self, Balance},
20 base64::Base64,
21 big_int::BigInt,
22 coin::Coin,
23 display::DisplayEntry,
24 dynamic_field::{DynamicField, DynamicFieldName},
25 iota_address::IotaAddress,
26 iota_names_registration::{NameFormat, NameRegistration},
27 move_object::{MoveObject, MoveObjectImpl},
28 move_value::MoveValue,
29 object::{self, Object, ObjectFilter, ObjectImpl, ObjectOwner, ObjectStatus},
30 owner::OwnerImpl,
31 stake::StakedIota,
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<NameFormat>,
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, NameRegistration>> {
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 {
168 ObjectImpl(&self.super_.super_).status().await
169 }
170
171 pub(crate) async fn digest(&self) -> Option<String> {
174 ObjectImpl(&self.super_.super_).digest().await
175 }
176
177 pub(crate) async fn owner(&self, ctx: &Context<'_>) -> Option<ObjectOwner> {
179 ObjectImpl(&self.super_.super_).owner(ctx).await
180 }
181
182 pub(crate) async fn previous_transaction_block(
184 &self,
185 ctx: &Context<'_>,
186 ) -> Result<Option<TransactionBlock>> {
187 ObjectImpl(&self.super_.super_)
188 .previous_transaction_block(ctx)
189 .await
190 }
191
192 pub(crate) async fn storage_rebate(&self) -> Option<BigInt> {
196 ObjectImpl(&self.super_.super_).storage_rebate().await
197 }
198
199 #[graphql(
225 complexity = "first.or(last).unwrap_or(DEFAULT_PAGE_SIZE as u64) as usize * child_complexity"
226 )]
227 pub(crate) async fn received_transaction_blocks(
228 &self,
229 ctx: &Context<'_>,
230 first: Option<u64>,
231 after: Option<transaction_block::Cursor>,
232 last: Option<u64>,
233 before: Option<transaction_block::Cursor>,
234 filter: Option<TransactionBlockFilter>,
235 scan_limit: Option<u64>,
236 ) -> Result<ScanConnection<String, TransactionBlock>> {
237 ObjectImpl(&self.super_.super_)
238 .received_transaction_blocks(ctx, first, after, last, before, filter, scan_limit)
239 .await
240 }
241
242 pub(crate) async fn bcs(&self) -> Result<Option<Base64>> {
244 ObjectImpl(&self.super_.super_).bcs().await
245 }
246
247 pub(crate) async fn contents(&self) -> Option<MoveValue> {
251 MoveObjectImpl(&self.super_).contents().await
252 }
253
254 pub(crate) async fn display(&self, ctx: &Context<'_>) -> Result<Option<Vec<DisplayEntry>>> {
258 ObjectImpl(&self.super_.super_).display(ctx).await
259 }
260
261 pub(crate) async fn dynamic_field(
268 &self,
269 ctx: &Context<'_>,
270 name: DynamicFieldName,
271 ) -> Result<Option<DynamicField>> {
272 OwnerImpl::from(&self.super_.super_)
273 .dynamic_field(ctx, name, Some(self.super_.root_version()))
274 .await
275 }
276
277 pub(crate) async fn dynamic_object_field(
286 &self,
287 ctx: &Context<'_>,
288 name: DynamicFieldName,
289 ) -> Result<Option<DynamicField>> {
290 OwnerImpl::from(&self.super_.super_)
291 .dynamic_object_field(ctx, name, Some(self.super_.root_version()))
292 .await
293 }
294
295 pub(crate) async fn dynamic_fields(
300 &self,
301 ctx: &Context<'_>,
302 first: Option<u64>,
303 after: Option<object::Cursor>,
304 last: Option<u64>,
305 before: Option<object::Cursor>,
306 ) -> Result<Connection<String, DynamicField>> {
307 OwnerImpl::from(&self.super_.super_)
308 .dynamic_fields(
309 ctx,
310 first,
311 after,
312 last,
313 before,
314 Some(self.super_.root_version()),
315 )
316 .await
317 }
318
319 async fn decimals(&self) -> Option<u8> {
321 Some(self.native.decimals)
322 }
323
324 async fn name(&self) -> Option<&str> {
326 Some(&self.native.name)
327 }
328
329 async fn symbol(&self) -> Option<&str> {
331 Some(&self.native.symbol)
332 }
333
334 async fn description(&self) -> Option<&str> {
336 Some(&self.native.description)
337 }
338
339 async fn icon_url(&self) -> Option<&str> {
340 self.native.icon_url.as_deref()
341 }
342
343 async fn supply(&self, ctx: &Context<'_>) -> Result<Option<BigInt>> {
345 let mut type_params = self.super_.native.type_().type_params();
346 let Some(coin_type) = type_params.pop() else {
347 return Ok(None);
348 };
349
350 let supply = CoinMetadata::query_total_supply(
351 ctx,
352 coin_type,
353 self.super_.super_.checkpoint_viewed_at,
354 )
355 .await
356 .extend()?;
357
358 Ok(supply.map(BigInt::from))
359 }
360}
361
362impl CoinMetadata {
363 pub(crate) async fn query(
366 db: &Db,
367 coin_type: TypeTag,
368 checkpoint_viewed_at: u64,
369 ) -> Result<Option<CoinMetadata>, Error> {
370 let TypeTag::Struct(coin_struct) = coin_type else {
371 return Ok(None);
374 };
375
376 let metadata_type = NativeCoinMetadata::type_(*coin_struct);
377 let Some(object) = Object::query_singleton(db, metadata_type, checkpoint_viewed_at).await?
378 else {
379 return Ok(None);
380 };
381
382 let move_object = MoveObject::try_from(&object).map_err(|_| {
383 Error::Internal(format!(
384 "Expected {} to be CoinMetadata, but it is not an object.",
385 object.address,
386 ))
387 })?;
388
389 let coin_metadata = CoinMetadata::try_from(&move_object).map_err(|_| {
390 Error::Internal(format!(
391 "Expected {} to be CoinMetadata, but it is not.",
392 object.address,
393 ))
394 })?;
395
396 Ok(Some(coin_metadata))
397 }
398
399 pub(crate) async fn query_total_supply(
400 ctx: &Context<'_>,
401 coin_type: TypeTag,
402 checkpoint_viewed_at: u64,
403 ) -> Result<Option<u64>, Error> {
404 let TypeTag::Struct(coin_struct) = coin_type else {
405 return Ok(None);
408 };
409
410 Ok(Some(if GAS::is_gas(coin_struct.as_ref()) {
411 let pg_manager = ctx.data_unchecked::<PgManager>();
412
413 let state = pg_manager.fetch_iota_system_state(None).await?;
414
415 state.iota_total_supply()
416 } else {
417 let cap_type = TreasuryCap::type_(*coin_struct);
418
419 let db = ctx.data_unchecked();
420
421 let Some(object) = Object::query_singleton(db, cap_type, checkpoint_viewed_at).await?
422 else {
423 return Ok(None);
424 };
425
426 let Some(native) = object.native_impl() else {
427 return Ok(None);
428 };
429
430 let treasury_cap = TreasuryCap::try_from(native.clone()).map_err(|e| {
431 Error::Internal(format!(
432 "Error while deserializing treasury cap {}: {e}",
433 object.address,
434 ))
435 })?;
436
437 treasury_cap.total_supply.value
438 }))
439 }
440}
441
442impl TryFrom<&MoveObject> for CoinMetadata {
443 type Error = CoinMetadataDowncastError;
444
445 fn try_from(move_object: &MoveObject) -> Result<Self, Self::Error> {
446 if !move_object.native.type_().is_coin_metadata() {
447 return Err(CoinMetadataDowncastError::NotCoinMetadata);
448 }
449
450 Ok(Self {
451 super_: move_object.clone(),
452 native: bcs::from_bytes(move_object.native.contents())
453 .map_err(CoinMetadataDowncastError::Bcs)?,
454 })
455 }
456}