1use crate::constants::HASH_LENGTH;
2use serde_json::Value;
3
4use crate::error::Error::{UnsupportedOperation, WrongTransactionType};
5use crate::error::{Error, Result};
6use crate::model::account::{PrivateKey, PublicKey};
7use crate::model::transaction::data_transaction::DataTransaction;
8use crate::model::transaction::TransactionData::Transfer;
9use crate::model::transaction::TransferTransaction;
10use crate::model::TransactionData::{
11 Burn, CreateAlias, Data, Ethereum, Exchange, Genesis, InvokeScript, Issue, Lease, LeaseCancel,
12 MassTransfer, Payment, Reissue, SetAssetScript, SetScript, SponsorFee, UpdateAssetInfo,
13};
14use crate::model::{
15 Address, AssetId, BurnTransaction, BurnTransactionInfo, ByteString, CreateAliasTransaction,
16 CreateAliasTransactionInfo, DataTransactionInfo, EthereumTransaction, EthereumTransactionInfo,
17 ExchangeTransaction, ExchangeTransactionInfo, GenesisTransaction, GenesisTransactionInfo, Id,
18 InvokeScriptTransaction, InvokeScriptTransactionInfo, IssueTransaction, IssueTransactionInfo,
19 LeaseCancelTransaction, LeaseCancelTransactionInfo, LeaseTransaction, LeaseTransactionInfo,
20 MassTransferTransaction, MassTransferTransactionInfo, PaymentTransaction,
21 PaymentTransactionInfo, Proof, ReissueTransaction, ReissueTransactionInfo,
22 SetAssetScriptTransaction, SetAssetScriptTransactionInfo, SetScriptTransaction,
23 SetScriptTransactionInfo, SponsorFeeTransaction, SponsorFeeTransactionInfo, TransactionBuilder,
24 TransferTransactionInfo, UpdateAssetInfoTransaction, UpdateAssetInfoTransactionInfo,
25};
26use crate::util::{sign_tx, Base58, BinarySerializer, Hash, JsonDeserializer, JsonSerializer};
27
28#[derive(Clone, Eq, PartialEq, Debug)]
29pub struct TransactionInfoResponse {
30 id: Id,
31 status: ApplicationStatus,
32 data: TransactionDataInfo,
33 fee: Amount,
34 timestamp: u64,
35 public_key: PublicKey,
36 tx_type: u8,
37 version: u8,
38 chain_id: u8,
39 height: u32,
40 proofs: Vec<Proof>,
41}
42
43#[allow(clippy::too_many_arguments)]
44impl TransactionInfoResponse {
45 pub fn new(
46 id: Id,
47 status: ApplicationStatus,
48 data: TransactionDataInfo,
49 fee: Amount,
50 timestamp: u64,
51 public_key: PublicKey,
52 tx_type: u8,
53 version: u8,
54 chain_id: u8,
55 height: u32,
56 proofs: Vec<Proof>,
57 ) -> TransactionInfoResponse {
58 TransactionInfoResponse {
59 id,
60 status,
61 data,
62 fee,
63 timestamp,
64 public_key,
65 tx_type,
66 version,
67 chain_id,
68 height,
69 proofs,
70 }
71 }
72
73 pub fn id(&self) -> Id {
74 self.id.clone()
75 }
76
77 pub fn status(&self) -> ApplicationStatus {
78 self.status
79 }
80
81 pub fn data(&self) -> TransactionDataInfo {
82 self.data.clone()
83 }
84
85 pub fn fee(&self) -> Amount {
86 self.fee.clone()
87 }
88
89 pub fn timestamp(&self) -> u64 {
90 self.timestamp
91 }
92
93 pub fn public_key(&self) -> PublicKey {
94 self.public_key.clone()
95 }
96
97 pub fn height(&self) -> u32 {
98 self.height
99 }
100
101 pub fn tx_type(&self) -> u8 {
102 self.tx_type
103 }
104
105 pub fn version(&self) -> u8 {
106 self.version
107 }
108
109 pub fn chain_id(&self) -> u8 {
110 self.chain_id
111 }
112
113 pub fn proofs(&self) -> Vec<Proof> {
114 self.proofs.clone()
115 }
116}
117
118#[derive(Debug, Clone, Copy, Eq, PartialEq)]
119pub enum ApplicationStatus {
120 Succeed,
121 ScriptExecutionFailed,
122 Unknown,
123}
124
125#[derive(Clone, Eq, PartialEq, Debug)]
126pub struct Transaction {
127 data: TransactionData,
128 fee: Amount,
129 timestamp: u64,
130 public_key: PublicKey,
131 tx_type: u8,
132 version: u8,
133 chain_id: u8,
134}
135
136#[derive(Clone, Eq, PartialEq, Debug)]
137pub struct Amount {
138 value: u64,
139 asset_id: Option<AssetId>,
140}
141
142impl Amount {
143 pub fn new(value: u64, asset_id: Option<AssetId>) -> Amount {
144 Amount { value, asset_id }
145 }
146
147 pub fn value(&self) -> u64 {
148 self.value
149 }
150
151 pub fn asset_id(&self) -> Option<AssetId> {
152 self.asset_id.clone()
153 }
154}
155
156impl Transaction {
157 pub fn new(
158 data: TransactionData,
159 fee: Amount,
160 timestamp: u64,
161 public_key: PublicKey,
162 version: u8,
163 chain_id: u8,
164 ) -> Transaction {
165 let tx_type = data.tx_type();
166 Transaction {
167 data,
168 fee,
169 timestamp,
170 public_key,
171 tx_type,
172 version,
173 chain_id,
174 }
175 }
176
177 pub fn with_defaults(
178 public_key: &PublicKey,
179 chain_id: u8,
180 tx_data: &TransactionData,
181 ) -> TransactionBuilder {
182 TransactionBuilder::new(public_key, chain_id, tx_data)
183 }
184
185 pub fn data(&self) -> &TransactionData {
186 &self.data
187 }
188
189 pub fn fee(&self) -> Amount {
190 self.fee.clone()
191 }
192
193 pub fn timestamp(&self) -> u64 {
194 self.timestamp
195 }
196
197 pub fn public_key(&self) -> PublicKey {
198 self.public_key.clone()
199 }
200
201 pub fn tx_type(&self) -> u8 {
202 self.tx_type
203 }
204
205 pub fn version(&self) -> u8 {
206 self.version
207 }
208
209 pub fn chain_id(&self) -> u8 {
210 self.chain_id
211 }
212
213 pub fn sign(&self, private_key: &PrivateKey) -> Result<SignedTransaction> {
214 sign_tx(self, private_key)
215 }
216
217 pub fn bytes(&self) -> Result<Vec<u8>> {
218 BinarySerializer::tx_body_bytes(self)
219 }
220
221 pub fn id(&self) -> Result<Id> {
222 match self.tx_type {
223 1 | 2 => Err(Error::UnsupportedOperation(
224 "id calculation from unsigned payment or genesis transaction".to_owned(),
225 )),
226 _ => Ok(Id::from_bytes(&Hash::blake(&self.bytes()?)?)),
227 }
228 }
229}
230
231#[derive(Clone, Eq, PartialEq, Debug)]
232#[allow(clippy::large_enum_variant)]
234pub enum TransactionDataInfo {
235 Genesis(GenesisTransactionInfo),
236 Payment(PaymentTransactionInfo),
237 Transfer(TransferTransactionInfo),
238 Data(DataTransactionInfo),
239 Issue(IssueTransactionInfo),
240 Reissue(ReissueTransactionInfo),
241 Lease(LeaseTransactionInfo),
242 LeaseCancel(LeaseCancelTransactionInfo),
243 CreateAlias(CreateAliasTransactionInfo),
244 MassTransfer(MassTransferTransactionInfo),
245 SetScript(SetScriptTransactionInfo),
246 SponsorFee(SponsorFeeTransactionInfo),
247 SetAssetScript(SetAssetScriptTransactionInfo),
248 Burn(BurnTransactionInfo),
249 Exchange(ExchangeTransactionInfo),
250 Invoke(InvokeScriptTransactionInfo),
251 UpdateAssetInfo(UpdateAssetInfoTransactionInfo),
252 Ethereum(EthereumTransactionInfo),
253}
254
255impl TransactionDataInfo {
256 pub fn transfer_tx(&self) -> Result<&TransferTransactionInfo> {
257 match self {
258 TransactionDataInfo::Transfer(tx) => Ok(tx),
259 tx => Err(WrongTransactionType {
260 expected_type: TransferTransaction::tx_type(),
261 actual_type: tx.tx_type(),
262 }),
263 }
264 }
265
266 pub fn data_tx(&self) -> Result<&DataTransactionInfo> {
267 match self {
268 TransactionDataInfo::Data(tx) => Ok(tx),
269 tx => Err(WrongTransactionType {
270 expected_type: DataTransaction::tx_type(),
271 actual_type: tx.tx_type(),
272 }),
273 }
274 }
275
276 pub fn tx_type(&self) -> u8 {
277 match self {
278 TransactionDataInfo::Genesis(_) => GenesisTransaction::tx_type(),
279 TransactionDataInfo::Payment(_) => PaymentTransaction::tx_type(),
280 TransactionDataInfo::Transfer(_) => TransferTransaction::tx_type(),
281 TransactionDataInfo::Data(_) => DataTransaction::tx_type(),
282 TransactionDataInfo::Issue(_) => IssueTransaction::tx_type(),
283 TransactionDataInfo::Exchange(_) => ExchangeTransaction::tx_type(),
284 TransactionDataInfo::Invoke(_) => InvokeScriptTransaction::tx_type(),
285 TransactionDataInfo::Reissue(_) => ReissueTransaction::tx_type(),
286 TransactionDataInfo::Burn(_) => BurnTransaction::tx_type(),
287 TransactionDataInfo::Lease(_) => LeaseTransaction::tx_type(),
288 TransactionDataInfo::LeaseCancel(_) => LeaseCancelTransaction::tx_type(),
289 TransactionDataInfo::CreateAlias(_) => CreateAliasTransaction::tx_type(),
290 TransactionDataInfo::MassTransfer(_) => MassTransferTransaction::tx_type(),
291 TransactionDataInfo::SetScript(_) => SetScriptTransaction::tx_type(),
292 TransactionDataInfo::SetAssetScript(_) => SetAssetScriptTransaction::tx_type(),
293 TransactionDataInfo::SponsorFee(_) => SponsorFeeTransaction::tx_type(),
294 TransactionDataInfo::UpdateAssetInfo(_) => UpdateAssetInfoTransaction::tx_type(),
295 TransactionDataInfo::Ethereum(_) => EthereumTransaction::tx_type(),
296 }
297 }
298}
299
300#[derive(Clone, Eq, PartialEq, Debug)]
301#[allow(clippy::large_enum_variant)]
303pub enum TransactionData {
304 Genesis(GenesisTransaction),
305 Payment(PaymentTransaction),
306 Transfer(TransferTransaction),
307 Reissue(ReissueTransaction),
308 Burn(BurnTransaction),
309 Lease(LeaseTransaction),
310 LeaseCancel(LeaseCancelTransaction),
311 CreateAlias(CreateAliasTransaction),
312 MassTransfer(MassTransferTransaction),
313 SetScript(SetScriptTransaction),
314 SponsorFee(SponsorFeeTransaction),
315 SetAssetScript(SetAssetScriptTransaction),
316 Data(DataTransaction),
317 Issue(IssueTransaction),
318 InvokeScript(InvokeScriptTransaction),
319 UpdateAssetInfo(UpdateAssetInfoTransaction),
320 Exchange(ExchangeTransaction),
321 Ethereum(EthereumTransaction),
322}
323
324impl TransactionData {
325 pub fn tx_type(&self) -> u8 {
326 match self {
327 Genesis(_) => GenesisTransaction::tx_type(),
328 Payment(_) => PaymentTransaction::tx_type(),
329 Transfer(_) => TransferTransaction::tx_type(),
330 Data(_) => DataTransaction::tx_type(),
331 Issue(_) => IssueTransaction::tx_type(),
332 InvokeScript(_) => InvokeScriptTransaction::tx_type(),
333 Exchange(_) => ExchangeTransaction::tx_type(),
334 Reissue(_) => ReissueTransaction::tx_type(),
335 Burn(_) => BurnTransaction::tx_type(),
336 Lease(_) => LeaseTransaction::tx_type(),
337 LeaseCancel(_) => LeaseCancelTransaction::tx_type(),
338 CreateAlias(_) => CreateAliasTransaction::tx_type(),
339 MassTransfer(_) => MassTransferTransaction::tx_type(),
340 SetScript(_) => SetScriptTransaction::tx_type(),
341 SetAssetScript(_) => SetAssetScriptTransaction::tx_type(),
342 SponsorFee(_) => SponsorFeeTransaction::tx_type(),
343 UpdateAssetInfo(_) => UpdateAssetInfoTransaction::tx_type(),
344 Ethereum(_) => EthereumTransaction::tx_type(),
345 }
346 }
347
348 pub fn get_min_supported_version(&self) -> u8 {
349 match self {
350 Genesis(_) => 2,
351 Payment(_) => 2,
352 Transfer(_) => 3,
353 Issue(_) => 3,
354 Reissue(_) => 3,
355 Burn(_) => 3,
356 Exchange(_) => 3,
357 Lease(_) => 3,
358 LeaseCancel(_) => 3,
359 CreateAlias(_) => 3,
360 MassTransfer(_) => 2,
361 Data(_) => 2,
362 SetScript(_) => 2,
363 SponsorFee(_) => 2,
364 SetAssetScript(_) => 2,
365 InvokeScript(_) => 2,
366 UpdateAssetInfo(_) => 1,
367 Ethereum(_) => 1,
368 }
369 }
370
371 pub fn get_min_fee(&self) -> Result<Amount> {
372 let value = match self {
373 Genesis(_) => 0,
374 Payment(_) => 1,
375 Transfer(_) => 100_000,
376 Issue(tx) => tx.min_fee().value,
377 Reissue(_) => 100_000,
378 Burn(_) => 100_000,
379 Exchange(_) => 300_000,
380 Lease(_) => 100_000,
381 LeaseCancel(_) => 100_000,
382 CreateAlias(_) => 100_000,
383 MassTransfer(_) => 100_000,
384 Data(_) => 100_000,
385 SetScript(_) => 1_000_000,
386 SponsorFee(_) => 100_000,
387 SetAssetScript(_) => 100_000_000,
388 InvokeScript(_) => 500_000,
389 UpdateAssetInfo(_) => 100_000,
390 Ethereum(_) => Err(UnsupportedOperation(
391 "Min fee for Ethereum transaction is undefined".into(),
392 ))?,
393 };
394 Ok(Amount::new(value, None))
395 }
396}
397
398#[derive(Clone, Eq, PartialEq, Debug)]
399pub struct SignedTransaction {
400 transaction: Transaction,
401 proofs: Vec<Proof>,
402}
403
404impl SignedTransaction {
405 pub fn new(transaction: Transaction, proofs: Vec<Proof>) -> SignedTransaction {
406 SignedTransaction {
407 transaction,
408 proofs,
409 }
410 }
411
412 pub fn tx(&self) -> &Transaction {
413 &self.transaction
414 }
415
416 pub fn id(&self) -> Result<Id> {
417 let tx = self.tx();
418 match tx.tx_type {
419 1 | 2 => Ok(Id::from_bytes(&self.proofs[0].bytes())),
420 _ => tx.id(),
421 }
422 }
423
424 pub fn proofs(&self) -> Vec<Proof> {
425 self.proofs.clone()
426 }
427
428 pub fn to_json(&self) -> Result<Value> {
429 JsonSerializer::serialize_signed_tx(self)
430 }
431
432 }
434
435impl TryFrom<&Value> for TransactionInfoResponse {
436 type Error = Error;
437
438 fn try_from(value: &Value) -> Result<Self> {
439 let id = Id::from_string(&JsonDeserializer::safe_to_string_from_field(value, "id")?)?;
440
441 let signed_tx: SignedTransaction = value.try_into()?;
442
443 let tx_type = JsonDeserializer::safe_to_int_from_field(value, "type")? as u8;
444
445 let application_status = if tx_type == 1 || tx_type == 2 {
446 ApplicationStatus::Unknown
447 } else {
448 match value["applicationStatus"].as_str() {
449 Some(status) => match status {
450 "succeeded" => ApplicationStatus::Succeed,
451 "script_execution_failed" => ApplicationStatus::ScriptExecutionFailed,
452 &_ => ApplicationStatus::Unknown,
453 },
454 None => ApplicationStatus::Unknown,
455 }
456 };
457 let height = JsonDeserializer::safe_to_int_from_field(value, "height")? as u32;
458 let transaction_data = match tx_type {
459 1 => TransactionDataInfo::Genesis(value.try_into()?),
460 2 => TransactionDataInfo::Payment(value.try_into()?),
461 3 => TransactionDataInfo::Issue(value.try_into()?),
462 4 => TransactionDataInfo::Transfer(value.try_into()?),
463 5 => TransactionDataInfo::Reissue(value.try_into()?),
464 6 => TransactionDataInfo::Burn(value.try_into()?),
465 7 => TransactionDataInfo::Exchange(value.try_into()?),
466 8 => TransactionDataInfo::Lease(value.try_into()?),
467 9 => TransactionDataInfo::LeaseCancel(value.try_into()?),
468 10 => TransactionDataInfo::CreateAlias(value.try_into()?),
469 11 => TransactionDataInfo::MassTransfer(value.try_into()?),
470 12 => TransactionDataInfo::Data(value.try_into()?),
471 13 => TransactionDataInfo::SetScript(value.try_into()?),
472 14 => TransactionDataInfo::SponsorFee(value.try_into()?),
473 15 => TransactionDataInfo::SetAssetScript(value.try_into()?),
474 16 => TransactionDataInfo::Invoke(value.try_into()?),
475 17 => TransactionDataInfo::UpdateAssetInfo(value.try_into()?),
476 18 => TransactionDataInfo::Ethereum(value.try_into()?),
477 _ => return Err(UnsupportedOperation("unknown tx type".to_owned())),
478 };
479 let timestamp = JsonDeserializer::safe_to_int_from_field(value, "timestamp")? as u64;
480
481 let tx = signed_tx.tx();
482 Ok(TransactionInfoResponse::new(
483 id,
484 application_status,
485 transaction_data,
486 tx.fee(),
487 timestamp,
488 tx.public_key(),
489 tx_type,
490 tx.version(),
491 tx.chain_id(),
492 height,
493 signed_tx.proofs(),
494 ))
495 }
496}
497
498impl TryFrom<&Value> for SignedTransaction {
499 type Error = Error;
500
501 fn try_from(value: &Value) -> Result<Self> {
502 let transaction: Transaction = value.try_into()?;
503
504 let proofs_array = match transaction.tx_type() {
505 1 => vec![Value::String(JsonDeserializer::safe_to_string_from_field(
506 value,
507 "signature",
508 )?)],
509 18 => vec![],
510 _ => JsonDeserializer::safe_to_array_from_field(value, "proofs")?,
511 };
512
513 let proofs = proofs_array
514 .iter()
515 .map(|v| {
516 Ok(Proof::new(Base58::decode(
517 &JsonDeserializer::safe_to_string(v)?,
518 )?))
519 })
520 .collect::<Result<Vec<Proof>>>()?;
521 Ok(SignedTransaction::new(transaction, proofs))
522 }
523}
524
525impl TryFrom<&Value> for Transaction {
526 type Error = Error;
527
528 fn try_from(value: &Value) -> Result<Self> {
529 let tx_type = JsonDeserializer::safe_to_int_from_field(value, "type")? as u8;
530 let fee = JsonDeserializer::safe_to_int_from_field(value, "fee")? as u64;
531 let fee_asset_id = match value["feeAssetId"].as_str() {
532 Some(val) => Some(AssetId::from_string(val)?),
533 None => None,
534 };
535 let transaction_data = match tx_type {
536 1 => Genesis(value.try_into()?),
537 2 => Payment(value.try_into()?),
538 3 => Issue(value.try_into()?),
539 4 => Transfer(value.try_into()?),
540 5 => Reissue(value.try_into()?),
541 6 => Burn(value.try_into()?),
542 7 => Exchange(value.try_into()?),
543 8 => Lease(value.try_into()?),
544 9 => LeaseCancel(value.try_into()?),
545 10 => CreateAlias(value.try_into()?),
546 11 => MassTransfer(value.try_into()?),
547 12 => Data(value.try_into()?),
548 13 => SetScript(value.try_into()?),
549 14 => SponsorFee(value.try_into()?),
550 15 => SetAssetScript(value.try_into()?),
551 16 => InvokeScript(InvokeScriptTransaction::from_json(value)?),
552 17 => UpdateAssetInfo(value.try_into()?),
553 18 => Ethereum(value.try_into()?),
554 _ => return Err(UnsupportedOperation("unknown transaction type".to_owned())),
555 };
556 let timestamp = JsonDeserializer::safe_to_int_from_field(value, "timestamp")? as u64;
557 let public_key = match tx_type {
558 1 => PublicKey::from_bytes(&[0; HASH_LENGTH])?,
559 _ => {
560 JsonDeserializer::safe_to_string_from_field(value, "senderPublicKey")?.try_into()?
561 }
562 };
563
564 let chain_id = match tx_type {
565 1 => Address::from_string(&JsonDeserializer::safe_to_string_from_field(
566 value,
567 "recipient",
568 )?)?
569 .chain_id(),
570 _ => Address::from_string(&JsonDeserializer::safe_to_string_from_field(
571 value, "sender",
572 )?)?
573 .chain_id(),
574 };
575
576 let version = match tx_type {
577 1 | 2 => 1_u8,
578 _ => JsonDeserializer::safe_to_int_from_field(value, "version")? as u8,
579 };
580 Ok(Transaction::new(
581 transaction_data,
582 Amount::new(fee, fee_asset_id),
583 timestamp,
584 public_key,
585 version,
586 chain_id,
587 ))
588 }
589}