LCOV - code coverage report
Current view: top level - crypto/utxo/entities/transactions - utxo_transaction.dart (source / functions) Coverage Total Hit
Test: lcov.info Lines: 92.8 % 221 205
Test Date: 2025-04-01 01:23:07 Functions: - 0 0

            Line data    Source code
       1              : part of '../../../../domain/entities/generic_transaction.dart';
       2              : 
       3              : const String ADDRESS_NOT_SUPPORTED = "Address not supported";
       4              : 
       5              : final class UTXOTransaction extends GenericTransaction {
       6              :   final String id;
       7              :   final List<ElectrumInput> inputs;
       8              :   final List<ElectrumOutput> outputs;
       9              :   final int version;
      10              : 
      11            9 :   const UTXOTransaction({
      12              :     required super.block,
      13              :     required super.fee,
      14              :     required super.hash,
      15              :     required super.timeMilli,
      16              :     required super.amount,
      17              :     required super.sender,
      18              :     required super.recipient,
      19              :     required super.token,
      20              :     required super.transferMethod,
      21              :     required super.confirmations,
      22              :     required super.status,
      23              :     required this.inputs,
      24              :     required this.outputs,
      25              :     required this.id,
      26              :     required this.version,
      27              :   });
      28              : 
      29            4 :   UTXOTransaction copyWith({
      30              :     int? block,
      31              :     Amount? fee,
      32              :     String? hash,
      33              :     int? timeMilli,
      34              :     Amount? amount,
      35              :     String? sender,
      36              :     String? recipient,
      37              :     CoinEntity? token,
      38              :     TransactionTransferMethod? transferMethod,
      39              :     int? confirmations,
      40              :     List<ElectrumInput>? inputs,
      41              :     List<ElectrumOutput>? outputs,
      42              :     String? id,
      43              :     int? version,
      44              :     ConfirmationStatus? status,
      45              :   }) {
      46            4 :     return UTXOTransaction(
      47            4 :       block: block ?? this.block,
      48            4 :       fee: fee ?? this.fee,
      49            4 :       hash: hash ?? this.hash,
      50            4 :       timeMilli: timeMilli ?? this.timeMilli,
      51            4 :       amount: amount ?? this.amount,
      52            0 :       sender: sender ?? this.sender,
      53            4 :       recipient: recipient ?? this.recipient,
      54            4 :       token: token ?? this.token,
      55            4 :       transferMethod: transferMethod ?? this.transferMethod,
      56            4 :       confirmations: confirmations ?? this.confirmations,
      57            4 :       inputs: inputs ?? this.inputs,
      58            4 :       outputs: outputs ?? this.outputs,
      59            4 :       id: id ?? this.id,
      60            4 :       version: version ?? this.version,
      61            4 :       status: status ?? this.status,
      62              :     );
      63              :   }
      64              : 
      65            8 :   factory UTXOTransaction.create({
      66              :     required Map<String, dynamic> json,
      67              :     required UTXONetworkType type,
      68              :     required Iterable<AddressType> addressTypes,
      69              :     required Iterable<NodeWithAddress> nodes,
      70              :     required Iterable<ElectrumOutput> spentOutputs,
      71              :   }) {
      72            8 :     final coin = type.coin;
      73            8 :     final id = json['txid'] as String;
      74              :     //final hash = json['hash'] as String;
      75              : 
      76            8 :     final inputs = (json['vin'] as List<dynamic>)
      77           24 :         .map((e) => ElectrumInput.fromJson(e as Map<String, dynamic>))
      78            8 :         .toList();
      79              : 
      80            8 :     final rawOutputs = (json['vout'] as List<dynamic>)
      81           24 :         .map((e) => ElectrumOutput.fromJson(e as Map<String, dynamic>))
      82            8 :         .toList();
      83              : 
      84            8 :     final outputs = findOurOwnCoins(rawOutputs, nodes, addressTypes, type);
      85              : 
      86           16 :     final sender = inputs.first.getAddress(
      87              :       type,
      88            8 :       addressType: addressTypes.first,
      89              :     );
      90              : 
      91            8 :     final transferMethod = determineSendDirection(
      92              :       inputs: inputs,
      93              :       outputs: outputs,
      94              :       nodes: nodes,
      95              :       type: type,
      96              :       addressTypes: addressTypes,
      97              :     );
      98              : 
      99            8 :     final (value, totalOutputValue) = determineTransactionValue(
     100              :       outputs,
     101              :       transferMethod,
     102              :       nodes,
     103              :       type,
     104              :     );
     105              : 
     106            8 :     final totalInputValue = spentOutputs.fold(
     107            8 :       BigInt.zero,
     108            9 :       (prev, spentOutput) => prev + spentOutput.value,
     109              :     );
     110              : 
     111            8 :     final fee_int = json['fee_int'] as int?;
     112            2 :     var fee = fee_int != null ? BigInt.from(fee_int) : null;
     113            9 :     fee ??= spentOutputs.isEmpty ? null : totalInputValue - totalOutputValue;
     114              : 
     115            8 :     final recipient = determineTransactionTarget(
     116              :           outputs,
     117              :           transferMethod,
     118              :           type,
     119            8 :           addressTypes.first,
     120              :         ) ??
     121              :         ADDRESS_NOT_SUPPORTED;
     122              : 
     123              :     //final blockHash = json['blockhash'] as String;
     124              : 
     125           16 :     final timestamp = (json['time'] ?? -1) * 1000;
     126           32 :     final height = int.tryParse(json['height'].toString()) ?? -1;
     127           24 :     final confirmations = int.tryParse(json['confirmations'].toString()) ?? -1;
     128            8 :     final version = json['version'] as int;
     129              : 
     130            8 :     return UTXOTransaction(
     131              :       block: height,
     132            8 :       fee: fee != null ? Amount(value: fee, decimals: coin.decimals) : null,
     133              :       hash: id,
     134              :       timeMilli: timestamp,
     135           16 :       amount: Amount(value: value, decimals: coin.decimals),
     136              :       sender: sender,
     137              :       recipient: recipient,
     138              :       token: coin,
     139              :       transferMethod: transferMethod,
     140              :       confirmations: confirmations,
     141              :       inputs: inputs,
     142              :       outputs: outputs,
     143              :       id: id,
     144              :       version: version,
     145            8 :       status: ConfirmationStatus.fromConfirmations(confirmations),
     146              :     );
     147              :   }
     148              : 
     149            1 :   factory UTXOTransaction.fromJson(Map<dynamic, dynamic> json) {
     150              :     if (json
     151              :         case {
     152            2 :           'hash': String hash,
     153            2 :           'block': int block,
     154            2 :           'confirmations': int confirmations,
     155            2 :           'timeMilli': int timeMilli,
     156            2 :           'amount': Map amount,
     157            2 :           'fee': Map? fee,
     158            2 :           'sender': String sender,
     159            2 :           'recipient': String recipient,
     160            2 :           'transferMethod': int transferMethod,
     161            2 :           'status': int status,
     162            2 :           'token': Map token,
     163            2 :           'id': String id,
     164            2 :           'version': int version,
     165            2 :           'inputs': JsonList inputs,
     166            2 :           'outputs': JsonList outputs,
     167              :         }) {
     168            1 :       return UTXOTransaction(
     169              :         hash: hash,
     170              :         block: block,
     171              :         confirmations: confirmations,
     172              :         timeMilli: timeMilli,
     173            1 :         amount: Amount.fromJson(amount),
     174            0 :         fee: fee != null ? Amount.fromJson(fee) : null,
     175              :         sender: sender,
     176              :         recipient: recipient,
     177            1 :         transferMethod: TransactionTransferMethod.fromJson(transferMethod),
     178            1 :         status: ConfirmationStatus.fromJson(status),
     179              :         id: id,
     180              :         version: version,
     181            4 :         inputs: inputs.map((e) => ElectrumInput.fromJson(e)).toList(),
     182            4 :         outputs: outputs.map((e) => ElectrumOutput.fromJson(e)).toList(),
     183            1 :         token: CoinEntity.fromJson(token),
     184              :       );
     185              :     }
     186              : 
     187            0 :     throw Exception("Could not parse UTXOTransaction from $json");
     188              :   }
     189              : 
     190            1 :   @override
     191              :   Map<String, dynamic> toJson() {
     192            1 :     return {
     193            1 :       'hash': hash,
     194            1 :       'block': block,
     195            1 :       'confirmations': confirmations,
     196            1 :       'timeMilli': timeMilli,
     197            2 :       'amount': amount.toJson(),
     198            1 :       'fee': fee?.toJson(),
     199            1 :       'sender': sender,
     200            1 :       'recipient': recipient,
     201            2 :       'transferMethod': transferMethod.index,
     202            2 :       'status': status.index,
     203            1 :       'id': id,
     204            1 :       'version': version,
     205            2 :       'token': token.toJson(),
     206            5 :       'inputs': inputs.map((e) => e.toJson()).toList(),
     207            5 :       'outputs': outputs.map((e) => e.toJson()).toList(),
     208              :     };
     209              :   }
     210              : }
     211              : 
     212              : class ElectrumInput {
     213              :   final String? scriptSig;
     214              :   final int? sequence;
     215              :   final String? txid;
     216              :   final int? vout;
     217              :   final List<String>? txinwitness;
     218              :   final String? coinbase;
     219              : 
     220           14 :   bool get isCoinbase => coinbase != null;
     221              : 
     222            9 :   const ElectrumInput({
     223              :     this.scriptSig,
     224              :     this.sequence,
     225              :     this.txid,
     226              :     this.vout,
     227              :     this.txinwitness,
     228              :     this.coinbase,
     229              :   });
     230              : 
     231            8 :   String getAddress(UTXONetworkType type, {AddressType? addressType}) {
     232              :     try {
     233            8 :       return getAddressFromInput(type, this, addressType: addressType);
     234              :     } catch (e) {
     235              :       return ADDRESS_NOT_SUPPORTED;
     236              :     }
     237              :   }
     238              : 
     239            8 :   Uint8List? get publicKey {
     240              :     try {
     241            8 :       return getPubKeyFromInput(this).$1;
     242              :     } catch (e) {
     243              :       return null;
     244              :     }
     245              :   }
     246              : 
     247            8 :   List<String> getAddresses({
     248              :     required Iterable<AddressType> addressTypes,
     249              :     required UTXONetworkType networkType,
     250              :   }) {
     251            8 :     final pubKey = publicKey;
     252              : 
     253              :     if (pubKey == null) {
     254            4 :       return throw Exception("Cannot get public key from input");
     255              :     }
     256            8 :     return [
     257            8 :       for (final addressType in addressTypes)
     258            8 :         pubKeyToAddress(pubKey, addressType, networkType)
     259              :     ];
     260              :   }
     261              : 
     262            9 :   factory ElectrumInput.fromJson(Map json) {
     263              :     return switch (json) {
     264              :       {
     265           22 :         "txinwitness": [String sig, String pubKey],
     266           13 :         "scriptSig": {
     267            8 :           "asm": _,
     268           12 :           "hex": String hex,
     269              :         },
     270           13 :         "sequence": int sequence,
     271           13 :         "txid": String txid,
     272           13 :         "vout": int vout,
     273              :       } =>
     274            4 :         ElectrumInput(
     275            4 :           txinwitness: [sig, pubKey],
     276              :           scriptSig: hex,
     277              :           sequence: sequence,
     278              :           txid: txid,
     279              :           vout: vout,
     280              :         ),
     281              :       {
     282            9 :         "scriptSig": {
     283              :           "asm": _,
     284            8 :           "hex": String hex,
     285              :         },
     286            7 :         "sequence": int sequence,
     287            7 :         "txid": String txid,
     288            7 :         "vout": int vout,
     289              :       } =>
     290            7 :         ElectrumInput(
     291              :           scriptSig: hex,
     292              :           sequence: sequence,
     293              :           txid: txid,
     294              :           vout: vout,
     295              :         ),
     296              :       {
     297            6 :         "coinbase": String coinbase,
     298            1 :         "sequence": int sequence,
     299              :       } =>
     300            1 :         ElectrumInput(
     301              :           coinbase: coinbase,
     302              :           sequence: sequence,
     303              :         ),
     304              :       {
     305            3 :         "scriptSig": {
     306              :           "asm": _,
     307            2 :           "hex": String hex,
     308              :         },
     309            2 :         "txid": String txid,
     310            2 :         "vout": int vout,
     311            4 :         "value_int": int _,
     312            4 :         "weight": int weight,
     313              :       } =>
     314            2 :         ElectrumInput(
     315              :           scriptSig: hex,
     316              :           txid: txid,
     317              :           vout: vout,
     318              :           sequence: weight,
     319              :         ),
     320              :       {
     321            1 :         'scriptSig': String? scriptSig,
     322            1 :         'sequence': int? sequence,
     323            1 :         'txid': String? txid,
     324            1 :         'vout': int? vout,
     325            1 :         'txinwitness': List<String>? txinwitness,
     326            1 :         'coinbase': String? coinbase,
     327              :       } =>
     328            1 :         ElectrumInput(
     329              :           scriptSig: scriptSig,
     330              :           sequence: sequence,
     331              :           txid: txid,
     332              :           vout: vout,
     333              :           txinwitness: txinwitness,
     334              :           coinbase: coinbase,
     335              :         ),
     336            0 :       _ => throw Exception("Could not parse ElectrumInput from $json"),
     337              :     };
     338              :   }
     339              : 
     340            1 :   Json toJson() {
     341            1 :     return {
     342            1 :       'scriptSig': scriptSig,
     343            1 :       'sequence': sequence,
     344            1 :       'txid': txid,
     345            1 :       'vout': vout,
     346            1 :       'txinwitness': txinwitness,
     347            1 :       'coinbase': coinbase,
     348              :     };
     349              :   }
     350              : }
     351              : 
     352              : class ElectrumOutput {
     353              :   final ElectrumScriptPubKey scriptPubKey;
     354              :   final bool belongsToUs;
     355              :   final bool spent;
     356              :   final BigInt value;
     357              :   final int n;
     358              : 
     359              :   ///
     360              :   /// Only available if [belongsToUs] is true
     361              :   ///
     362              :   final NodeWithAddress node;
     363              : 
     364           10 :   const ElectrumOutput({
     365              :     required this.scriptPubKey,
     366              :     required this.value,
     367              :     required this.n,
     368              :     this.belongsToUs = false,
     369              :     this.spent = false,
     370              :     required this.node,
     371              :   });
     372              : 
     373              :   /// Zeniq: { value_coin || value_satoshi: int, ... }
     374              :   /// Bitcoin: { value: float, ... }
     375              : 
     376            9 :   factory ElectrumOutput.fromJson(Map json) {
     377              :     if (json
     378              :         case {
     379           18 :           'value': int value,
     380           12 :           'n': int n,
     381            7 :           'spent': bool spent,
     382            2 :           'belongsToUs': bool belongsToUs,
     383            2 :           'scriptPubKey': Map scriptPubKey,
     384            2 :           'node': Map node,
     385              :         }) {
     386            1 :       return ElectrumOutput(
     387            1 :         value: value.toBigInt,
     388              :         n: n,
     389              :         spent: spent,
     390              :         belongsToUs: belongsToUs,
     391            1 :         scriptPubKey: ElectrumScriptPubKey.fromJson(scriptPubKey),
     392            1 :         node: NodeWithAddress.fromJson(node),
     393              :       );
     394              :     }
     395              : 
     396              :     final valIsSatoshi =
     397           16 :         json.containsKey('value_satoshi') || json.containsKey('value_int');
     398              : 
     399              :     var value =
     400           15 :         json['value_int'] ?? json['value'] ?? json['value_satoshi'] ?? 0;
     401              : 
     402              :     value =
     403           16 :         valIsSatoshi ? BigInt.from(value) : BigInt.from(toSatoshiValue(value));
     404              : 
     405            8 :     final n = json['n'] ?? -1;
     406              : 
     407            8 :     return ElectrumOutput(
     408              :       value: value,
     409              :       n: n,
     410            8 :       scriptPubKey: ElectrumScriptPubKey.fromJson(
     411            8 :         json['scriptPubKey'],
     412              :       ),
     413            8 :       node: EmptyNode(),
     414              :     );
     415              :   }
     416              : 
     417            8 :   String getAddress(UTXONetworkType type, {AddressType? addressType}) {
     418              :     try {
     419           16 :       return getAddressFromLockingScript(scriptPubKey, type,
     420              :           addressType: addressType);
     421              :     } catch (e) {
     422              :       return ADDRESS_NOT_SUPPORTED;
     423              :     }
     424              :   }
     425              : 
     426            8 :   Iterable<String> getAddresses({
     427              :     required UTXONetworkType networkType,
     428              :     required Iterable<AddressType> addressTypes,
     429              :   }) {
     430              :     try {
     431              :       final (pubKey, _) =
     432           16 :           getPublicKeyFromLockingScript(scriptPubKey, networkType);
     433              : 
     434            8 :       return [
     435            8 :         for (final addressType in addressTypes)
     436            8 :           pubKeyHashToAddress(pubKey, addressType, networkType)
     437              :       ];
     438              :     } catch (e) {
     439            1 :       return [];
     440              :     }
     441              :   }
     442              : 
     443            8 :   ElectrumOutput copyWith({
     444              :     bool? belongsToUs,
     445              :     bool? spent,
     446              :     NodeWithAddress? node,
     447              :   }) {
     448            8 :     return ElectrumOutput(
     449            8 :       scriptPubKey: scriptPubKey,
     450            8 :       value: value,
     451            8 :       n: n,
     452            8 :       spent: spent ?? this.spent,
     453            7 :       belongsToUs: belongsToUs ?? this.belongsToUs,
     454            8 :       node: node ?? this.node,
     455              :     );
     456              :   }
     457              : 
     458            1 :   Json toJson() {
     459            1 :     return {
     460            2 :       'value': value.toInt(),
     461            1 :       'n': n,
     462            1 :       'spent': spent,
     463            1 :       'belongsToUs': belongsToUs,
     464            2 :       'scriptPubKey': scriptPubKey.toJson(),
     465            2 :       'node': node.toJson(),
     466              :     };
     467              :   }
     468              : 
     469            0 :   @override
     470              :   String toString() {
     471            0 :     return 'ElectrumOutput{scriptPubKey: $scriptPubKey, belongsToUs: $belongsToUs, spent: $spent, value: $value, n: $n, node: $node}';
     472              :   }
     473              : }
     474              : 
     475            7 : int toSatoshiValue(num val) {
     476            7 :   final value_s = val.toString();
     477            7 :   final splits = value_s.split('.');
     478              : 
     479           14 :   if (splits.length == 2) {
     480            7 :     final intPart = splits[0];
     481            7 :     var decPart = splits[1];
     482            7 :     decPart = decPart.padRight(8, '0');
     483           14 :     return int.parse(intPart + decPart);
     484              :   }
     485            8 :   return (val * 1E8).toInt();
     486              : }
     487              : 
     488              : class ElectrumScriptPubKey {
     489              :   final String hexString;
     490              :   final String type;
     491              : 
     492           10 :   const ElectrumScriptPubKey({
     493              :     required this.hexString,
     494              :     required this.type,
     495              :   });
     496              : 
     497            0 :   bool get isP2SH => type == 'scripthash';
     498            0 :   bool get isP2PKH => type == 'pubkeyhash';
     499            0 :   bool get isP2WSH => type == 'witness_v0_scripthash';
     500            9 :   bool get isSegwit => type == 'witness_v0_keyhash';
     501              : 
     502            9 :   factory ElectrumScriptPubKey.fromJson(Map json) {
     503            9 :     return ElectrumScriptPubKey(
     504            9 :       hexString: json['hex'] as String,
     505            9 :       type: json['type'] as String,
     506              :     );
     507              :   }
     508              : 
     509            4 :   Uint8List get lockingScript {
     510           12 :     return Uint8List.fromList(hex.decode(hexString));
     511              :   }
     512              : 
     513            1 :   Json toJson() {
     514            1 :     return {
     515            1 :       'hex': hexString,
     516            1 :       'type': type,
     517              :     };
     518              :   }
     519              : }
     520              : 
     521              : final class NotAvaialableUTXOTransaction extends UTXOTransaction {
     522            0 :   NotAvaialableUTXOTransaction(String hash, int block, CoinEntity token)
     523            0 :       : super(
     524              :           block: block,
     525              :           hash: hash,
     526              :           id: hash,
     527            0 :           version: -1,
     528            0 :           confirmations: -1,
     529            0 :           amount: Amount.zero,
     530            0 :           fee: Amount.zero,
     531              :           inputs: const [],
     532              :           outputs: const [],
     533              :           recipient: "",
     534              :           sender: "",
     535              :           status: ConfirmationStatus.notSubmitted,
     536            0 :           timeMilli: -1,
     537              :           token: token,
     538              :           transferMethod: TransactionTransferMethod.unknown,
     539              :         );
     540              : }
        

Generated by: LCOV version 2.0-1