| 1 | import 'package:blockchain_utils/hex/hex.dart'; |
| 2 | import 'package:cw_core/crypto_currency.dart'; |
| 3 | import 'package:on_chain/on_chain.dart'; |
| 4 | |
| 5 | class TronTRC20TransactionModel extends TronTransactionModel { |
| 6 | String? transactionId; |
| 7 | |
| 8 | String? tokenSymbol; |
| 9 | |
| 10 | int? decimals; |
| 11 | |
| 12 | CryptoCurrency get currency => CryptoCurrency( |
| 13 | name: tokenSymbol ?? "TRX", title: tokenSymbol ?? "TRX", decimals: decimals ?? 6); |
| 14 | |
| 15 | int? timestamp; |
| 16 | |
| 17 | @override |
| 18 | String? from; |
| 19 | |
| 20 | @override |
| 21 | String? to; |
| 22 | |
| 23 | String? value; |
| 24 | |
| 25 | @override |
| 26 | String get hash => transactionId!; |
| 27 | |
| 28 | @override |
| 29 | DateTime get date => DateTime.fromMillisecondsSinceEpoch(timestamp ?? 0); |
| 30 | |
| 31 | @override |
| 32 | BigInt? get amount => BigInt.parse(value ?? '0'); |
| 33 | |
| 34 | @override |
| 35 | int? get fee => 0; |
| 36 | |
| 37 | TronTRC20TransactionModel({ |
| 38 | this.transactionId, |
| 39 | this.tokenSymbol, |
| 40 | this.timestamp, |
| 41 | this.from, |
| 42 | this.to, |
| 43 | this.value, |
| 44 | }); |
| 45 | |
| 46 | TronTRC20TransactionModel.fromJson(Map<String, dynamic> json) { |
| 47 | transactionId = json['transaction_id']; |
| 48 | tokenSymbol = json['token_info'] != null ? json['token_info']['symbol'] : null; |
| 49 | decimals = json['token_info'] != null ? json['token_info']['decimals'] : null; |
| 50 | timestamp = json['block_timestamp']; |
| 51 | from = json['from']; |
| 52 | to = json['to']; |
| 53 | value = json['value']; |
| 54 | } |
| 55 | } |
| 56 | |
| 57 | class TronTransactionModel { |
| 58 | List<Ret>? ret; |
| 59 | String? txID; |
| 60 | int? blockTimestamp; |
| 61 | List<Contract>? contracts; |
| 62 | |
| 63 | /// Getters to extract out the needed/useful information directly from the model params |
| 64 | /// Without having to go through extra steps in the methods that use this model. |
| 65 | bool get isError { |
| 66 | if (ret?.first.contractRet == null) return true; |
| 67 | |
| 68 | return ret?.first.contractRet != "SUCCESS"; |
| 69 | } |
| 70 | |
| 71 | String get hash => txID!; |
| 72 | |
| 73 | DateTime get date => DateTime.fromMillisecondsSinceEpoch(blockTimestamp ?? 0); |
| 74 | |
| 75 | String? get from => contracts?.first.parameter?.value?.ownerAddress; |
| 76 | |
| 77 | String? get to => contracts?.first.parameter?.value?.receiverAddress; |
| 78 | |
| 79 | BigInt? get amount => contracts?.first.parameter?.value?.txAmount; |
| 80 | |
| 81 | int? get fee => ret?.first.fee; |
| 82 | |
| 83 | String? get contractAddress => contracts?.first.parameter?.value?.contractAddress; |
| 84 | |
| 85 | TronTransactionModel({ |
| 86 | this.ret, |
| 87 | this.txID, |
| 88 | this.blockTimestamp, |
| 89 | this.contracts, |
| 90 | }); |
| 91 | |
| 92 | TronTransactionModel.fromJson(Map<String, dynamic> json) { |
| 93 | if (json['ret'] != null) { |
| 94 | ret = <Ret>[]; |
| 95 | json['ret'].forEach((v) { |
| 96 | ret!.add(Ret.fromJson(v)); |
| 97 | }); |
| 98 | } |
| 99 | txID = json['txID']; |
| 100 | blockTimestamp = json['block_timestamp']; |
| 101 | contracts = json['raw_data'] != null |
| 102 | ? (json['raw_data']['contract'] as List) |
| 103 | .map((e) => Contract.fromJson(e as Map<String, dynamic>)) |
| 104 | .toList() |
| 105 | : null; |
| 106 | } |
| 107 | } |
| 108 | |
| 109 | class Ret { |
| 110 | String? contractRet; |
| 111 | int? fee; |
| 112 | |
| 113 | Ret({this.contractRet, this.fee}); |
| 114 | |
| 115 | Ret.fromJson(Map<String, dynamic> json) { |
| 116 | contractRet = json['contractRet']; |
| 117 | fee = json['fee']; |
| 118 | } |
| 119 | } |
| 120 | |
| 121 | class Contract { |
| 122 | Parameter? parameter; |
| 123 | String? type; |
| 124 | |
| 125 | Contract({this.parameter, this.type}); |
| 126 | |
| 127 | Contract.fromJson(Map<String, dynamic> json) { |
| 128 | parameter = json['parameter'] != null ? Parameter.fromJson(json['parameter']) : null; |
| 129 | type = json['type']; |
| 130 | } |
| 131 | } |
| 132 | |
| 133 | class Parameter { |
| 134 | Value? value; |
| 135 | String? typeUrl; |
| 136 | |
| 137 | Parameter({this.value, this.typeUrl}); |
| 138 | |
| 139 | Parameter.fromJson(Map<String, dynamic> json) { |
| 140 | value = json['value'] != null ? Value.fromJson(json['value']) : null; |
| 141 | typeUrl = json['type_url']; |
| 142 | } |
| 143 | } |
| 144 | |
| 145 | class Value { |
| 146 | String? data; |
| 147 | String? ownerAddress; |
| 148 | String? contractAddress; |
| 149 | int? amount; |
| 150 | String? toAddress; |
| 151 | String? assetName; |
| 152 | |
| 153 | //Getters to extract address for tron transactions |
| 154 | /// If the contract address is null, it returns the toAddress |
| 155 | /// If it's not null, it decodes the data field and gets the receiver address. |
| 156 | String? get receiverAddress { |
| 157 | if (contractAddress == null) return toAddress; |
| 158 | |
| 159 | if (data == null) return null; |
| 160 | |
| 161 | return _decodeAddressFromEncodedDataField(data!); |
| 162 | } |
| 163 | |
| 164 | //Getters to extract amount for tron transactions |
| 165 | /// If the contract address is null, it returns the amount |
| 166 | /// If it's not null, it decodes the data field and gets the tx amount. |
| 167 | BigInt? get txAmount { |
| 168 | if (contractAddress == null) return BigInt.from(amount ?? 0); |
| 169 | |
| 170 | if (data == null) return null; |
| 171 | |
| 172 | return _decodeAmountInvolvedFromEncodedDataField(data!); |
| 173 | } |
| 174 | |
| 175 | Value( |
| 176 | {this.data, |
| 177 | this.ownerAddress, |
| 178 | this.contractAddress, |
| 179 | this.amount, |
| 180 | this.toAddress, |
| 181 | this.assetName}); |
| 182 | |
| 183 | Value.fromJson(Map<String, dynamic> json) { |
| 184 | data = json['data']; |
| 185 | ownerAddress = json['owner_address']; |
| 186 | contractAddress = json['contract_address']; |
| 187 | amount = json['amount']; |
| 188 | toAddress = json['to_address']; |
| 189 | assetName = json['asset_name']; |
| 190 | } |
| 191 | |
| 192 | /// To get the address from the encoded data field |
| 193 | String _decodeAddressFromEncodedDataField(String output) { |
| 194 | // To get the receiver address from the encoded params |
| 195 | output = output.replaceFirst('0x', '').substring(8); |
| 196 | final abiCoder = ABICoder.fromType('address'); |
| 197 | final decoded = abiCoder.decode(AbiParameter.bytes, hex.decode(output)); |
| 198 | final tronAddress = TronAddress.fromEthAddress((decoded.result as ETHAddress).toBytes()); |
| 199 | |
| 200 | return tronAddress.toString(); |
| 201 | } |
| 202 | |
| 203 | /// To get the amount from the encoded data field |
| 204 | BigInt _decodeAmountInvolvedFromEncodedDataField(String output) { |
| 205 | output = output.replaceFirst('0x', '').substring(72); |
| 206 | final amountAbiCoder = ABICoder.fromType('uint256'); |
| 207 | final decodedA = amountAbiCoder.decode(AbiParameter.uint256, hex.decode(output)); |
| 208 | final amount = decodedA.result as BigInt; |
| 209 | |
| 210 | return amount; |
| 211 | } |
| 212 | } |