LCOV - code coverage report
Current view: top level - crypto/utxo/repositories - electrum_json_rpc_client.dart (source / functions) Coverage Total Hit
Test: lcov.info Lines: 80.5 % 82 66
Test Date: 2025-06-07 01:20:49 Functions: - 0 0

            Line data    Source code
       1              : import 'package:walletkit_dart/src/crypto/utxo/utils/endpoint_utils.dart';
       2              : import 'package:walletkit_dart/src/crypto/utxo/entities/electrum_peer.dart';
       3              : import 'package:walletkit_dart/src/crypto/utxo/entities/transactions/electrum_transaction.dart';
       4              : import 'package:walletkit_dart/src/crypto/utxo/utils/utxo_in_memory_cache.dart';
       5              : import 'package:walletkit_dart/walletkit_dart.dart';
       6              : 
       7              : import 'json_rpc_client.dart';
       8              : 
       9              : class ElectrumXClient {
      10              :   final TcpJsonRpcClient _client;
      11              : 
      12           13 :   ElectrumXClient(this._client);
      13              : 
      14            9 :   Future<int?> getCurrentBlock() async {
      15           18 :     final response = await _client.sendRequest(
      16           18 :       {"method": "blockchain.headers.subscribe", "params": []},
      17              :     );
      18            9 :     if (response == null || response is! Json) return null;
      19            9 :     final block = response["height"];
      20              :     return block;
      21              :   }
      22              : 
      23            0 :   Future<List<ElectrumPeer>?> fetchPeers() async {
      24            0 :     final response = await _client.sendRequest(
      25            0 :       {"method": "server.peers.subscribe", "params": []},
      26              :     );
      27            0 :     if (response == null || response is! JsonList) return null;
      28              : 
      29            0 :     return [
      30            0 :       for (var [_, String host, [String version, ...args]] in response)
      31            0 :         if (args.whereType<String>().any((args) => args.startsWith("t")))
      32            0 :           ElectrumPeer.fromJson(
      33              :             host: host,
      34              :             version: version,
      35              :             args: args,
      36              :           )
      37              :     ];
      38              :   }
      39              : 
      40            8 :   Future<UTXOTransaction> getTransaction({
      41              :     required String txHash,
      42              :     required Iterable<AddressType> addressTypes,
      43              :     required UTXONetworkType type,
      44              :     required Iterable<NodeWithAddress> nodes,
      45              :     bool verbose = true,
      46              :   }) async {
      47           16 :     final cache = getUtxoInMemoryCache(type.coin);
      48            8 :     final cachedTx = cache.getTx(txHash: txHash);
      49              :     if (cachedTx != null) {
      50              :       return cachedTx;
      51              :     }
      52           16 :     assert(nodes.isNotEmpty, "Nodes must not be empty");
      53              : 
      54            8 :     Future<Json> blockchainTransactionGet(String txHash) async {
      55           16 :       final response = await _client.sendRequest(
      56            8 :         {
      57              :           "method": "blockchain.transaction.get",
      58            8 :           "params": [txHash, verbose]
      59              :         },
      60              :       );
      61            8 :       if (response == null || response is! Json) {
      62            0 :         throw Exception("Could not fetch transaction $txHash from ElectrumX");
      63              :       }
      64              : 
      65              :       return response;
      66              :     }
      67              : 
      68              :     ///
      69              :     /// Get Initial Transaction
      70              :     ///
      71              :     try {
      72            8 :       final mainTxJson = await blockchainTransactionGet(txHash);
      73              : 
      74              :       ///
      75              :       /// Get Transaction Inputs
      76              :       ///
      77              :       // final inputs = <(String, int)>[];
      78              :       // if (mainTxJson case {"vin": JsonList _inputs}) {
      79              :       //   for (final {"txid": String txid, "vout": int index} in _inputs) {
      80              :       //     inputs.add((txid, index));
      81              :       //   }
      82              :       // }
      83              :       // final outputsFutures = [
      84              :       //   for (final (hash, index) in inputs)
      85              :       //     blockchainTransactionGet(hash).then((tx) {
      86              :       //       if (tx case {"vout": JsonList _outputs}) {
      87              :       //         return ElectrumOutput.fromJson(_outputs[index]);
      88              :       //       }
      89              :       //     })
      90              :       // ];
      91              : 
      92              :       // Logger.logWarning("Fetching ${outputsFutures.length} outputs for $txHash");
      93              : 
      94              :       // final outputs = await Future.wait(outputsFutures);
      95              : 
      96            8 :       final tx = UTXOTransaction.create(
      97              :         json: mainTxJson,
      98              :         addressTypes: addressTypes,
      99              :         type: type,
     100              :         nodes: nodes,
     101            8 :         spentOutputs: [],
     102              :       );
     103              : 
     104              :       /// P2SH Addresses ?
     105              : 
     106            8 :       cache.insertTxIfConfirmed(tx);
     107              :       return tx;
     108              :     } catch (e) {
     109            4 :       throw Exception("Could not fetch transaction $txHash from ElectrumX");
     110              :     }
     111              :   }
     112              : 
     113            4 :   Future<UTXOTransaction> getTransactionForSimulation({
     114              :     required String txHash,
     115              :     required Iterable<AddressType> addressTypes,
     116              :     required UTXONetworkType type,
     117              :     required Iterable<NodeWithAddress> nodes,
     118              :     bool verbose = true,
     119              :   }) async {
     120            4 :     Future<Json> blockchainTransactionGet(String txHash) async {
     121            8 :       final response = await _client.sendRequest(
     122            4 :         {
     123              :           "method": "blockchain.transaction.get",
     124            4 :           "params": [txHash, verbose]
     125              :         },
     126              :       );
     127            4 :       if (response == null || response is! Json) {
     128            0 :         throw Exception("Could not fetch transaction $txHash from ElectrumX");
     129              :       }
     130              : 
     131              :       return response;
     132              :     }
     133              : 
     134              :     ///
     135              :     /// Get Initial Transaction
     136              :     ///
     137            4 :     final mainTxJson = await blockchainTransactionGet(txHash);
     138              : 
     139              :     ///
     140              :     /// Get Transaction Inputs
     141              :     ///
     142            4 :     final inputs = <(String, int)>[];
     143            8 :     if (mainTxJson case {"vin": JsonList _inputs}) {
     144            8 :       for (final input in _inputs) {
     145           20 :         if (input case {"txid": String txid, "vout": int index}) inputs.add((txid, index));
     146              :       }
     147              :     }
     148            4 :     final outputs = [
     149            4 :       for (final (hash, index) in inputs)
     150           12 :         await blockchainTransactionGet(hash).then((tx) {
     151            6 :           if (tx case {"vout": JsonList _outputs}) {
     152            6 :             return ElectrumOutput.fromJson(_outputs[index]);
     153              :           }
     154              :         })
     155            4 :     ].nonNulls;
     156              : 
     157            4 :     return UTXOTransaction.create(
     158              :       json: mainTxJson,
     159              :       addressTypes: addressTypes,
     160              :       type: type,
     161              :       nodes: nodes,
     162              :       spentOutputs: outputs,
     163              :     );
     164              :   }
     165              : 
     166              :   // do not use getRaw because it does not use an in-memory-cache !
     167            6 :   Future<String?> getRaw(
     168              :     String txHash, {
     169              :     bool verbose = false,
     170              :   }) async {
     171           12 :     final response = await _client.sendRequest(
     172            6 :       {
     173              :         "method": "blockchain.transaction.get",
     174            6 :         "params": [txHash, verbose]
     175              :       },
     176              :     );
     177            6 :     return response.toString();
     178              :   }
     179              : 
     180            9 :   Future<Set<ElectrumTransactionInfo>?> getHistory(
     181              :     String publicKeyScriptHash,
     182              :   ) async {
     183           18 :     final response = await _client.sendRequest(
     184            9 :       {
     185              :         "method": "blockchain.scripthash.get_history",
     186            9 :         "params": [publicKeyScriptHash]
     187              :       },
     188              :     );
     189            9 :     if (response == null || response is! List<dynamic>) return null;
     190              :     return {
     191           18 :       for (final json in response) ElectrumTransactionInfo.fromJson(json),
     192              :     };
     193              :   }
     194              : 
     195            0 :   Future<String> broadcastTransaction({required String rawTxHex}) async {
     196            0 :     final response = await _client.sendRawRequest(
     197            0 :       {
     198              :         "method": "blockchain.transaction.broadcast",
     199            0 :         "params": [rawTxHex]
     200              :       },
     201              :     );
     202              :     return response;
     203              :   }
     204              : 
     205            1 :   Future<double> estimateFee({required int blocks}) async {
     206            2 :     final response = await _client.sendRequest(
     207            1 :       {
     208              :         "method": "blockchain.estimatefee",
     209            1 :         "params": [blocks]
     210              :       },
     211              :     );
     212              :     final fee = response as double?;
     213            1 :     if (fee == null || fee == 0) throw Exception("Fee estimation failed");
     214              :     return fee;
     215              :   }
     216              : 
     217           13 :   Future<bool> disconnect() async {
     218           26 :     await _client.disconnect();
     219              :     return true;
     220              :   }
     221              : 
     222           24 :   String get host => _client.host;
     223            0 :   int get port => _client.port;
     224              : }
     225              : 
     226            2 : Future<String> fetchRawTxByHash(
     227              :   String hash,
     228              :   UTXONetworkType networkType,
     229              : ) async {
     230            2 :   final (result, _client, _) = await fetchFromRandomElectrumXNode(
     231            2 :     (client) async {
     232            2 :       return await client.getRaw(hash);
     233              :     },
     234              :     client: null,
     235            2 :     endpoints: networkType.endpoints,
     236            2 :     token: networkType.coin,
     237            2 :     timeout: Duration(seconds: 20),
     238              :   );
     239            2 :   await _client?.disconnect();
     240            4 :   if (result == null || result.isEmpty || result == "null") {
     241            0 :     throw Exception("No result for $hash");
     242              :   }
     243              : 
     244              :   return result;
     245              : }
        

Generated by: LCOV version 2.0-1