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