waves_rust/model/transaction/
issue_transaction.rs1use crate::error::{Error, Result};
2use crate::model::{Amount, AssetId, Base64String, ByteString};
3use crate::util::JsonDeserializer;
4use crate::waves_proto::IssueTransactionData;
5use serde_json::{Map, Value};
6
7const TYPE: u8 = 3;
8
9#[derive(Clone, Eq, PartialEq, Debug)]
10pub struct IssueTransactionInfo {
11 asset_id: AssetId,
12 name: String,
13 description: String,
14 quantity: u64,
15 decimals: u32,
16 is_reissuable: bool,
17 script: Option<Base64String>,
18}
19
20impl IssueTransactionInfo {
21 pub fn new(
22 asset_id: AssetId,
23 name: String,
24 description: String,
25 quantity: u64,
26 decimals: u32,
27 is_reissuable: bool,
28 script: Option<Base64String>,
29 ) -> Self {
30 IssueTransactionInfo {
31 asset_id,
32 name,
33 description,
34 quantity,
35 decimals,
36 is_reissuable,
37 script,
38 }
39 }
40
41 pub fn tx_type() -> u8 {
42 TYPE
43 }
44
45 pub fn asset_id(&self) -> AssetId {
46 self.asset_id.clone()
47 }
48
49 pub fn name(&self) -> String {
50 self.name.clone()
51 }
52
53 pub fn description(&self) -> String {
54 self.description.clone()
55 }
56
57 pub fn quantity(&self) -> u64 {
58 self.quantity
59 }
60
61 pub fn decimals(&self) -> u32 {
62 self.decimals
63 }
64
65 pub fn is_reissuable(&self) -> bool {
66 self.is_reissuable
67 }
68
69 pub fn script(&self) -> Option<Base64String> {
70 self.script.clone()
71 }
72}
73
74impl TryFrom<&Value> for IssueTransactionInfo {
75 type Error = Error;
76
77 fn try_from(value: &Value) -> Result<Self> {
78 let issue_transaction: IssueTransaction = value.try_into()?;
79 let asset_id = AssetId::from_string(&JsonDeserializer::safe_to_string_from_field(
80 value, "assetId",
81 )?)?;
82
83 Ok(IssueTransactionInfo {
84 asset_id,
85 name: issue_transaction.name(),
86 description: issue_transaction.description(),
87 quantity: issue_transaction.quantity(),
88 decimals: issue_transaction.decimals(),
89 is_reissuable: issue_transaction.is_reissuable(),
90 script: issue_transaction.script(),
91 })
92 }
93}
94
95#[derive(Clone, Eq, PartialEq, Debug)]
96pub struct IssueTransaction {
97 name: String,
98 description: String,
99 quantity: u64,
100 decimals: u32,
101 is_reissuable: bool,
102 script: Option<Base64String>,
103}
104
105impl IssueTransaction {
106 pub fn new(
107 name: String,
108 description: String,
109 quantity: u64,
110 decimals: u32,
111 is_reissuable: bool,
112 script: Option<Base64String>,
113 ) -> Self {
114 IssueTransaction {
115 name,
116 description,
117 quantity,
118 decimals,
119 is_reissuable,
120 script,
121 }
122 }
123
124 pub fn tx_type() -> u8 {
125 TYPE
126 }
127
128 pub fn name(&self) -> String {
129 self.name.clone()
130 }
131
132 pub fn description(&self) -> String {
133 self.description.clone()
134 }
135
136 pub fn quantity(&self) -> u64 {
137 self.quantity
138 }
139
140 pub fn decimals(&self) -> u32 {
141 self.decimals
142 }
143
144 pub fn is_reissuable(&self) -> bool {
145 self.is_reissuable
146 }
147
148 pub fn script(&self) -> Option<Base64String> {
149 self.script.clone()
150 }
151
152 pub fn min_fee(&self) -> Amount {
153 let value = if self.is_nft_issue() {
154 100_000
155 } else {
156 100_000_000
157 };
158 Amount::new(value, None)
159 }
160
161 pub fn is_nft_issue(&self) -> bool {
162 self.quantity == 1 && self.decimals == 0 && !self.is_reissuable
163 }
164}
165
166impl TryFrom<&IssueTransaction> for IssueTransactionData {
167 type Error = Error;
168
169 fn try_from(issue_tx: &IssueTransaction) -> Result<Self> {
170 let script = match issue_tx.script() {
171 Some(script) => script.bytes(),
172 None => vec![],
173 };
174
175 Ok(IssueTransactionData {
176 name: issue_tx.name(),
177 description: issue_tx.description(),
178 amount: issue_tx.quantity() as i64,
179 decimals: issue_tx.decimals() as i32,
180 reissuable: issue_tx.is_reissuable(),
181 script,
182 })
183 }
184}
185
186impl TryFrom<&Value> for IssueTransaction {
187 type Error = Error;
188
189 fn try_from(value: &Value) -> Result<Self> {
190 let name = JsonDeserializer::safe_to_string_from_field(value, "name")?;
191 let description = JsonDeserializer::safe_to_string_from_field(value, "description")?;
192 let quantity = JsonDeserializer::safe_to_int_from_field(value, "quantity")? as u64;
193 let decimals = JsonDeserializer::safe_to_int_from_field(value, "decimals")? as u32;
194 let is_reissuable = JsonDeserializer::safe_to_boolean_from_field(value, "reissuable")?;
195 let script = match value["script"].as_str() {
196 Some(val) => Some(Base64String::from_string(val)?),
197 None => None,
198 };
199
200 Ok(IssueTransaction {
201 name,
202 description,
203 quantity,
204 decimals,
205 is_reissuable,
206 script,
207 })
208 }
209}
210
211impl TryFrom<&IssueTransaction> for Map<String, Value> {
212 type Error = Error;
213
214 fn try_from(issue_tx: &IssueTransaction) -> Result<Self> {
215 let mut json = Map::new();
216 json.insert("name".to_string(), issue_tx.name().into());
217 json.insert("description".to_string(), issue_tx.description().into());
218 json.insert("quantity".to_string(), issue_tx.quantity().into());
219 json.insert("decimals".to_string(), issue_tx.decimals().into());
220 json.insert("reissuable".to_string(), issue_tx.is_reissuable().into());
221 json.insert(
222 "script".to_string(),
223 issue_tx.script().map(|it| it.encoded()).into(),
224 );
225 Ok(json)
226 }
227}
228
229#[cfg(test)]
230mod tests {
231 use crate::error::Result;
232 use crate::model::{Base64String, ByteString, IssueTransaction, IssueTransactionInfo};
233 use crate::waves_proto::IssueTransactionData;
234 use serde_json::{json, Map, Value};
235 use std::borrow::Borrow;
236 use std::fs;
237
238 #[test]
239 fn test_json_to_issue_transaction() {
240 let data = fs::read_to_string("./tests/resources/issue_transaction_rs.json")
241 .expect("Unable to read file");
242 let json: Value = serde_json::from_str(&data).expect("failed to generate json from str");
243
244 let issue_tx_from_json: IssueTransactionInfo = json.borrow().try_into().unwrap();
245
246 assert_eq!(
247 "5HCFX88m6Xxws4SunQuW9ghvYBmk8rK8b6xVCRL8PyAw",
248 issue_tx_from_json.asset_id().encoded()
249 );
250 assert_eq!("test asset", issue_tx_from_json.name());
251 assert_eq!(32, issue_tx_from_json.quantity());
252 assert_eq!(false, issue_tx_from_json.is_reissuable());
253 assert_eq!(3, issue_tx_from_json.decimals());
254 assert_eq!("this is test asset", issue_tx_from_json.description());
255
256 let script = "base64:AgQAAAAHbWFzdGVyMQkBAAAAEWFkZHJlc3NGcm9tU3RyaW5nAAAAAQIAAAAQMzMzbWFzdGVyQWRkcmVzcwQAAAAHJG1hdGNoMAUAAAACdHgDCQAAAQAAAAIFAAAAByRtYXRjaDACAAAAE1RyYW5zZmVyVHJhbnNhY3Rpb24EAAAAAXQFAAAAByRtYXRjaDADCQAAAAAAAAIIBQAAAAF0AAAABnNlbmRlcgUAAAAHbWFzdGVyMQYJAAAAAAAAAggFAAAAAXQAAAAJcmVjaXBpZW50BQAAAAdtYXN0ZXIxAwkAAAEAAAACBQAAAAckbWF0Y2gwAgAAABdNYXNzVHJhbnNmZXJUcmFuc2FjdGlvbgQAAAACbXQFAAAAByRtYXRjaDAJAAAAAAAAAggFAAAAAm10AAAABnNlbmRlcgUAAAAHbWFzdGVyMQMJAAABAAAAAgUAAAAHJG1hdGNoMAIAAAATRXhjaGFuZ2VUcmFuc2FjdGlvbgcGFLbwIw==";
257
258 assert_eq!(
259 script,
260 issue_tx_from_json.script().unwrap().encoded_with_prefix()
261 );
262 }
263
264 #[test]
265 fn test_issue_transaction_to_proto() -> Result<()> {
266 let issue_tx = &IssueTransaction::new(
267 "name".to_owned(),
268 "descr".to_owned(),
269 32,
270 0,
271 false,
272 Some(Base64String::from_bytes(vec![1, 2, 3])),
273 );
274 let proto: IssueTransactionData = issue_tx.try_into()?;
275 assert_eq!(proto.name, issue_tx.name());
276 assert_eq!(proto.description, issue_tx.description());
277 assert_eq!(proto.amount as u64, issue_tx.quantity());
278 assert_eq!(proto.decimals as u32, issue_tx.decimals());
279 assert_eq!(proto.reissuable, issue_tx.is_reissuable());
280 assert_eq!(proto.script, issue_tx.script().unwrap().bytes());
281 Ok(())
282 }
283
284 #[test]
285 fn test_issue_tx_to_json() -> Result<()> {
286 let issue_tx = &IssueTransaction::new(
287 "test asset".to_owned(),
288 "this is test asset".to_owned(),
289 32,
290 3,
291 false,
292 Some(Base64String::from_bytes(vec![1, 2, 3])),
293 );
294
295 let map: Map<String, Value> = issue_tx.try_into()?;
296 let json: Value = map.into();
297 let expected_json = json!({
298 "name": "test asset",
299 "quantity": 32,
300 "reissuable": false,
301 "decimals": 3,
302 "description": "this is test asset",
303 "script": "AQID",
304 });
305 assert_eq!(expected_json, json);
306 Ok(())
307 }
308
309 #[test]
310 fn test_min_fee_for_issue_transaction() {
311 let issue_transaction =
312 IssueTransaction::new("name".into(), "description".into(), 123, 8, true, None);
313
314 let nft_issue_transaction =
315 IssueTransaction::new("nft".into(), "description".into(), 1, 0, false, None);
316
317 assert_eq!(issue_transaction.min_fee().value(), 100_000_000);
318
319 assert!(nft_issue_transaction.is_nft_issue());
320 assert_eq!(nft_issue_transaction.min_fee().value(), 100_000);
321 }
322}