1use iota_sdk_types::{Identifier, StructTag, TypeTag};
6use move_core_types::{
7 annotated_value::{MoveFieldLayout, MoveStructLayout, MoveTypeLayout},
8 ident_str,
9};
10use serde::{Deserialize, Serialize};
11
12use crate::{
13 balance::{Balance, Supply},
14 base_types::ObjectID,
15 error::{ExecutionError, ExecutionErrorKind, IotaError},
16 id::UID,
17 iota_sdk_types_conversions::struct_tag_sdk_to_core,
18 object::{Data, Object},
19};
20
21pub const COIN_JOIN_FUNC_NAME: Identifier = Identifier::from_static("join");
22
23pub const PAY_SPLIT_N_FUNC_NAME: Identifier = Identifier::from_static("divide_and_keep");
24pub const PAY_SPLIT_VEC_FUNC_NAME: Identifier = Identifier::from_static("split_vec");
25
26#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)]
28pub struct Coin {
29 pub id: UID,
30 pub balance: Balance,
31}
32
33impl Coin {
34 pub fn new(id: ObjectID, value: u64) -> Self {
35 Self {
36 id: UID::new(id),
37 balance: Balance::new(value),
38 }
39 }
40
41 pub fn from_bcs_bytes(content: &[u8]) -> Result<Self, bcs::Error> {
43 bcs::from_bytes(content)
44 }
45
46 pub fn extract_balance_if_coin(object: &Object) -> Result<Option<u64>, bcs::Error> {
51 let Data::Struct(obj) = &object.data else {
52 return Ok(None);
53 };
54 let Some(_) = obj.struct_tag().coin_type_opt() else {
55 return Ok(None);
56 };
57
58 let coin = Self::from_bcs_bytes(obj.contents())?;
59 Ok(Some(coin.value()))
60 }
61
62 pub fn id(&self) -> &ObjectID {
63 self.id.object_id()
64 }
65
66 pub fn value(&self) -> u64 {
67 self.balance.value()
68 }
69
70 pub fn to_bcs_bytes(&self) -> Vec<u8> {
71 bcs::to_bytes(&self).unwrap()
72 }
73
74 pub fn layout(type_param: TypeTag) -> MoveStructLayout {
75 MoveStructLayout {
76 type_: struct_tag_sdk_to_core(&StructTag::new_coin(type_param.clone())),
77 fields: vec![
78 MoveFieldLayout::new(
79 ident_str!("id").to_owned(),
80 MoveTypeLayout::Struct(Box::new(UID::layout())),
81 ),
82 MoveFieldLayout::new(
83 ident_str!("balance").to_owned(),
84 MoveTypeLayout::Struct(Box::new(Balance::layout(type_param))),
85 ),
86 ],
87 }
88 }
89
90 pub fn add(&mut self, balance: Balance) -> Result<(), ExecutionError> {
93 let Some(new_value) = self.value().checked_add(balance.value()) else {
94 return Err(ExecutionError::from_kind(
95 ExecutionErrorKind::CoinBalanceOverflow,
96 ));
97 };
98 self.balance = Balance::new(new_value);
99 Ok(())
100 }
101
102 pub fn split(&mut self, amount: u64, new_coin_id: ObjectID) -> Result<Coin, ExecutionError> {
107 self.balance.withdraw(amount)?;
108 Ok(Coin::new(new_coin_id, amount))
109 }
110}
111
112#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)]
114pub struct TreasuryCap {
115 pub id: UID,
116 pub total_supply: Supply,
117}
118
119impl TreasuryCap {
120 pub fn from_bcs_bytes(content: &[u8]) -> Result<Self, IotaError> {
122 bcs::from_bytes(content).map_err(|err| IotaError::ObjectDeserialization {
123 error: format!("Unable to deserialize TreasuryCap object: {err}"),
124 })
125 }
126
127 pub fn is_treasury_with_coin_type(other: &StructTag) -> Option<&StructTag> {
130 if other.is_treasury_cap() {
131 match other.type_params().first() {
132 Some(TypeTag::Struct(coin_type)) => Some(coin_type),
133 _ => None,
134 }
135 } else {
136 None
137 }
138 }
139}
140
141impl TryFrom<Object> for TreasuryCap {
142 type Error = IotaError;
143 fn try_from(object: Object) -> Result<Self, Self::Error> {
144 match &object.data {
145 Data::Struct(o) => {
146 if o.struct_tag().is_treasury_cap() {
147 return TreasuryCap::from_bcs_bytes(o.contents());
148 }
149 }
150 Data::Package(_) => {}
151 }
152
153 Err(IotaError::Type {
154 error: format!("Object type is not a TreasuryCap: {object:?}"),
155 })
156 }
157}
158
159#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)]
161pub struct CoinMetadata {
162 pub id: UID,
163 pub decimals: u8,
165 pub name: String,
167 pub symbol: String,
169 pub description: String,
171 pub icon_url: Option<String>,
173}
174
175impl CoinMetadata {
176 pub fn from_bcs_bytes(content: &[u8]) -> Result<Self, IotaError> {
178 bcs::from_bytes(content).map_err(|err| IotaError::ObjectDeserialization {
179 error: format!("Unable to deserialize CoinMetadata object: {err}"),
180 })
181 }
182
183 pub fn is_coin_metadata_with_coin_type(other: &StructTag) -> Option<&StructTag> {
186 if other.is_coin_metadata() {
187 match other.type_params().first() {
188 Some(TypeTag::Struct(coin_type)) => Some(coin_type),
189 _ => None,
190 }
191 } else {
192 None
193 }
194 }
195}
196
197impl TryFrom<Object> for CoinMetadata {
198 type Error = IotaError;
199 fn try_from(object: Object) -> Result<Self, Self::Error> {
200 TryFrom::try_from(&object)
201 }
202}
203
204impl TryFrom<&Object> for CoinMetadata {
205 type Error = IotaError;
206 fn try_from(object: &Object) -> Result<Self, Self::Error> {
207 match &object.data {
208 Data::Struct(o) => {
209 if o.struct_tag().is_coin_metadata() {
210 return CoinMetadata::from_bcs_bytes(o.contents());
211 }
212 }
213 Data::Package(_) => {}
214 }
215
216 Err(IotaError::Type {
217 error: format!("Object type is not a CoinMetadata: {object:?}"),
218 })
219 }
220}