iota_types/
coin_manager.rs

1// Copyright (c) 2024 IOTA Stiftung
2// SPDX-License-Identifier: Apache-2.0
3
4use move_core_types::{ident_str, identifier::IdentStr, language_storage::TypeTag};
5use schemars::JsonSchema;
6use serde::{Deserialize, Serialize};
7
8use crate::{
9    IOTA_FRAMEWORK_ADDRESS, StructTag,
10    coin::{CoinMetadata, TreasuryCap},
11    error::IotaError,
12    id::UID,
13    object::{Data, Object},
14};
15
16pub const COIN_MANAGER_MODULE_NAME: &IdentStr = ident_str!("coin_manager");
17pub const COIN_MANAGER_STRUCT_NAME: &IdentStr = ident_str!("CoinManager");
18pub const COIN_MANAGER_TREASURY_CAP_STRUCT_NAME: &IdentStr = ident_str!("CoinManagerTreasuryCap");
19
20/// The purpose of a CoinManager is to allow access to all
21/// properties of a Coin on-chain from within a single shared object
22/// This includes access to the total supply and metadata
23/// In addition a optional maximum supply can be set and a custom
24/// additional Metadata field can be added.
25/// Holds all related objects to a Coin in a convenient shared function.
26#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq, JsonSchema)]
27pub struct CoinManager {
28    /// The unique identifier of the object.
29    pub id: UID,
30    /// The original TreasuryCap object as returned by `create_currency`
31    pub treasury_cap: TreasuryCap,
32    /// Metadata object, original one from the `coin` module, if available
33    pub metadata: Option<CoinMetadata>,
34    /// Immutable Metadata object, only to be used as a last resort if the
35    /// original metadata is frozen
36    pub immutable_metadata: Option<ImmutableCoinMetadata>,
37    /// Optional maximum supply, if set you can't mint more as this number - can
38    /// only be set once
39    pub maximum_supply: Option<u64>,
40    /// Flag indicating if the supply is considered immutable (TreasuryCap is
41    /// exchanged for this)
42    pub supply_immutable: bool,
43    /// Flag indicating if the metadata is considered immutable (MetadataCap is
44    /// exchanged for this)
45    pub metadata_immutable: bool,
46}
47
48impl CoinManager {
49    pub fn is_coin_manager(object_type: &StructTag) -> bool {
50        object_type.address == IOTA_FRAMEWORK_ADDRESS
51            && object_type.module.as_ident_str() == COIN_MANAGER_MODULE_NAME
52            && object_type.name.as_ident_str() == COIN_MANAGER_STRUCT_NAME
53    }
54
55    pub fn from_bcs_bytes(content: &[u8]) -> Result<Self, IotaError> {
56        bcs::from_bytes(content).map_err(|err| IotaError::ObjectDeserialization {
57            error: format!("Unable to deserialize CoinManager object: {err}"),
58        })
59    }
60
61    pub fn type_(type_param: StructTag) -> StructTag {
62        StructTag {
63            address: IOTA_FRAMEWORK_ADDRESS,
64            name: COIN_MANAGER_STRUCT_NAME.to_owned(),
65            module: COIN_MANAGER_MODULE_NAME.to_owned(),
66            type_params: vec![TypeTag::Struct(Box::new(type_param))],
67        }
68    }
69}
70
71/// The immutable version of CoinMetadata, used in case of migrating from frozen
72/// objects to a `CoinManager` holding the metadata.
73#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq, JsonSchema)]
74pub struct ImmutableCoinMetadata {
75    /// Number of decimal places the coin uses.
76    pub decimals: u8,
77    /// Name for the token
78    pub name: String,
79    /// Symbol for the token
80    pub symbol: String,
81    /// Description of the token
82    pub description: String,
83    /// URL for the token logo
84    pub icon_url: Option<String>,
85}
86
87/// Like `TreasuryCap`, but for dealing with `TreasuryCap` inside `CoinManager`
88/// objects
89#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq, JsonSchema)]
90pub struct CoinManagerTreasuryCap {
91    /// The unique identifier of the object.
92    pub id: UID,
93}
94
95impl CoinManagerTreasuryCap {
96    pub fn is_coin_manager_treasury_cap(object_type: &StructTag) -> bool {
97        object_type.address == IOTA_FRAMEWORK_ADDRESS
98            && object_type.module.as_ident_str() == COIN_MANAGER_MODULE_NAME
99            && object_type.name.as_ident_str() == COIN_MANAGER_TREASURY_CAP_STRUCT_NAME
100    }
101}
102
103impl TryFrom<Object> for CoinManager {
104    type Error = IotaError;
105    fn try_from(object: Object) -> Result<Self, Self::Error> {
106        TryFrom::try_from(&object)
107    }
108}
109
110impl TryFrom<&Object> for CoinManager {
111    type Error = IotaError;
112    fn try_from(object: &Object) -> Result<Self, Self::Error> {
113        if let Data::Move(o) = &object.data {
114            if o.type_().is_coin_manager() {
115                return CoinManager::from_bcs_bytes(o.contents());
116            }
117        }
118
119        Err(IotaError::Type {
120            error: format!("Object type is not a CoinManager: {object:?}"),
121        })
122    }
123}