LCOV - code coverage report
Current view: top level - crypto/evm/repositories/etherscan - etherscan_explorer.dart (source / functions) Coverage Total Hit
Test: lcov.info Lines: 67.3 % 104 70
Test Date: 2025-07-02 01:23:33 Functions: - 0 0

            Line data    Source code
       1              : import 'package:walletkit_dart/src/common/types.dart';
       2              : import 'package:walletkit_dart/src/crypto/evm/entities/transactions/etherscan_transaction.dart';
       3              : import 'package:walletkit_dart/src/crypto/evm/repositories/etherscan/etherscan_repository.dart';
       4              : import 'package:walletkit_dart/src/domain/entities/amount.dart';
       5              : import 'package:walletkit_dart/src/domain/entities/coin_entity.dart';
       6              : import 'package:walletkit_dart/src/domain/entities/tx_gasFee_entity.dart';
       7              : 
       8              : enum Sorting { asc, desc }
       9              : 
      10              : class EtherscanExplorer extends EtherscanRepository {
      11              :   final EvmCoinEntity currency;
      12              : 
      13            1 :   EtherscanExplorer(super.baseUrl, super.apiKeys, this.currency);
      14              : 
      15            5 :   String get base => "${super.baseUrl}?chainid=${currency.chainID}";
      16              : 
      17            1 :   String buildBalanceEndpoint(String address) =>
      18            3 :       "$base&module=account&action=balance&address=$address".addOptionalParameter('tag', 'latest');
      19              : 
      20            1 :   String buildTokenBalanceEndpoint(String address, String contractAddress) =>
      21            1 :       "$base&module=account&action=tokenbalance&address=$address&contractaddress=$contractAddress"
      22            2 :           .addOptionalParameter('tag', 'latest');
      23              : 
      24            1 :   String buildTransactionEndpoint({
      25              :     required String address,
      26              :     int? startblock,
      27              :     int? endblock,
      28              :     int? page,
      29              :     int? offset,
      30              :     Sorting? sorting,
      31              :   }) =>
      32            1 :       "$base&module=account&action=txlist&address=$address"
      33            2 :           .addOptionalParameter('startblock', startblock)
      34            1 :           .addOptionalParameter('endblock', endblock)
      35            1 :           .addOptionalParameter('page', page)
      36            1 :           .addOptionalParameter('offset', offset)
      37            1 :           .addOptionalParameter('sort', sorting?.name);
      38              : 
      39            1 :   String buildERC20TransactionEndpoint({
      40              :     required String address,
      41              :     required String contractAddress,
      42              :     int? startblock,
      43              :     int? endblock,
      44              :     int? page,
      45              :     int? offset,
      46              :     Sorting? sorting,
      47              :   }) =>
      48            1 :       "$base&module=account&action=tokentx&address=$address&contractaddress=$contractAddress"
      49            2 :           .addOptionalParameter('startblock', startblock)
      50            1 :           .addOptionalParameter('endblock', endblock)
      51            1 :           .addOptionalParameter('page', page)
      52            1 :           .addOptionalParameter('offset', offset)
      53            1 :           .addOptionalParameter('sort', sorting?.name);
      54              : 
      55            1 :   String buildERC721TransactionEndpoint({
      56              :     required String address,
      57              :     String? contractAddress,
      58              :     int? startblock,
      59              :     int? endblock,
      60              :     int? page,
      61              :     int? offset,
      62              :     Sorting? sorting,
      63              :   }) =>
      64            1 :       "$base&module=account&action=tokennfttx&address=$address"
      65            2 :           .addOptionalParameter('contractaddress', contractAddress)
      66            1 :           .addOptionalParameter('startblock', startblock)
      67            1 :           .addOptionalParameter('endblock', endblock)
      68            1 :           .addOptionalParameter('page', page)
      69            1 :           .addOptionalParameter('offset', offset)
      70            1 :           .addOptionalParameter('sort', sorting?.name);
      71              : 
      72            0 :   String buildERC1155TransactionEndpoint({
      73              :     required String address,
      74              :     required String contractAddress,
      75              :     int? startblock,
      76              :     int? endblock,
      77              :     int? page,
      78              :     int? offset,
      79              :     Sorting? sorting,
      80              :   }) =>
      81            0 :       "$base&module=account&action=token1155tx&contractaddress=$contractAddress&address=$address"
      82            0 :           .addOptionalParameter('page', page)
      83            0 :           .addOptionalParameter('offset', offset)
      84            0 :           .addOptionalParameter('startblock', startblock)
      85            0 :           .addOptionalParameter('endblock', endblock)
      86            0 :           .addOptionalParameter('sort', sorting?.name);
      87              : 
      88              :   ///
      89              :   /// Fetch all Transactions for the given [token] on the given [address]
      90              :   ///
      91            1 :   Future<List<EtherscanTransaction>> fetchTransactions({
      92              :     required String address,
      93              :     int? startblock,
      94              :     int? endblock,
      95              :     int? page,
      96              :     int? offset,
      97              :     Sorting? sorting,
      98              :   }) async {
      99            1 :     final endpoint = buildTransactionEndpoint(
     100              :       address: address,
     101              :       startblock: startblock,
     102              :       endblock: endblock,
     103              :       page: page,
     104              :       offset: offset,
     105              :       sorting: sorting,
     106              :     );
     107              : 
     108            1 :     final txResults = await fetchEtherscanWithRatelimitRetries(endpoint);
     109            1 :     return [
     110            1 :       for (final tx in txResults)
     111            1 :         EtherscanTransaction.fromJson(
     112              :           tx,
     113            1 :           token: currency,
     114              :           address: address,
     115              :         ),
     116              :     ];
     117              :   }
     118              : 
     119            1 :   Future<List<EtherscanTransaction>> fetchERC1155Transactions({
     120              :     required String contractAddress,
     121              :     required String address,
     122              :     int? startblock,
     123              :     int? endblock,
     124              :     int? page,
     125              :     int? offset,
     126              :     Sorting? sorting,
     127              :   }) async {
     128            1 :     final endpoint = buildERC1155TransactionEndpoint(
     129              :       address: address,
     130              :       contractAddress: contractAddress,
     131              :       startblock: startblock,
     132              :       endblock: endblock,
     133              :       page: page,
     134              :       offset: offset,
     135              :       sorting: sorting,
     136              :     );
     137            0 :     final txResults = await fetchEtherscanWithRatelimitRetries(endpoint);
     138              : 
     139            0 :     return [
     140            0 :       for (final tx in txResults)
     141            0 :         EtherscanTransaction.fromJsonErc1155(
     142              :           tx,
     143              :           address: address,
     144            0 :           currency: currency,
     145              :         ),
     146              :     ];
     147              :   }
     148              : 
     149              :   ///
     150              :   /// Fetch all ERC20 Transactions for a given [token] and [address]
     151              :   ///
     152            1 :   Future<List<EtherscanTransaction>> fetchERC20Transactions({
     153              :     required String contractAddress,
     154              :     required String address,
     155              :     int? startblock,
     156              :     int? endblock,
     157              :     int? page,
     158              :     int? offset,
     159              :     Sorting? sorting,
     160              :   }) async {
     161            1 :     final endpoint = buildERC20TransactionEndpoint(
     162              :       address: address,
     163              :       contractAddress: contractAddress,
     164              :       startblock: startblock,
     165              :       endblock: endblock,
     166              :       page: page,
     167              :       offset: offset,
     168              :       sorting: sorting,
     169              :     );
     170              : 
     171            1 :     final txResults = await fetchEtherscanWithRatelimitRetries(endpoint);
     172            1 :     return [
     173            1 :       for (final tx in txResults)
     174            1 :         EtherscanTransaction.fromJsonErc20(
     175              :           tx,
     176              :           address: address,
     177            1 :           currency: currency,
     178              :         )
     179              :     ];
     180              :   }
     181              : 
     182            1 :   Future<BigInt> fetchBalance({
     183              :     required String address,
     184              :   }) async {
     185            1 :     final endpoint = buildBalanceEndpoint(address);
     186            1 :     final result = await fetchEtherscanWithRatelimitRetries<String>(endpoint);
     187              : 
     188            1 :     final balance = BigInt.tryParse(result);
     189              : 
     190              :     if (balance == null) {
     191            0 :       throw Exception('Failed to parse balance: $result');
     192              :     }
     193              : 
     194              :     return balance;
     195              :   }
     196              : 
     197            1 :   Future<BigInt> fetchTokenBalance({
     198              :     required String address,
     199              :     required String contractAddress,
     200              :   }) async {
     201            1 :     final endpoint = buildTokenBalanceEndpoint(address, contractAddress);
     202            1 :     final result = await fetchEtherscanWithRatelimitRetries<String>(endpoint);
     203              : 
     204            1 :     final balance = BigInt.tryParse(result);
     205              : 
     206              :     if (balance == null) {
     207            0 :       throw Exception('Failed to parse balance: $result');
     208              :     }
     209              : 
     210              :     return balance;
     211              :   }
     212              : 
     213              :   ///
     214              :   /// Fetch the Balance of a [token] given  for a given [address]
     215              :   ///
     216            0 :   Future<Amount> fetchBalanceForToken(
     217              :     String address,
     218              :     CoinEntity token,
     219              :   ) async =>
     220              :       switch (token) {
     221            0 :         ERC20Entity erc20 => fetchTokenBalance(
     222              :             address: address,
     223            0 :             contractAddress: erc20.contractAddress,
     224            0 :           ).then((balance) => Amount(value: balance, decimals: erc20.decimals)),
     225            0 :         CoinEntity coin => fetchBalance(
     226              :             address: address,
     227            0 :           ).then((balance) => Amount(value: balance, decimals: coin.decimals)),
     228              :       };
     229              : 
     230              :   ///
     231              :   /// Fetch a list of all ERC721 Tokens for a given [address]
     232              :   ///
     233            1 :   Future<List<EtherscanTransaction>> fetchERC721Transactions({
     234              :     required String address,
     235              :     String? contractAddress,
     236              :     int? startblock,
     237              :     int? endblock,
     238              :     int? page,
     239              :     int? offset,
     240              :     Sorting? sorting,
     241              :   }) async {
     242            1 :     final endpoint = buildERC721TransactionEndpoint(
     243              :       address: address,
     244              :       contractAddress: contractAddress,
     245              :       startblock: startblock,
     246              :       endblock: endblock,
     247              :       page: page,
     248              :       offset: offset,
     249              :       sorting: sorting,
     250              :     );
     251              : 
     252            1 :     final result = await fetchEtherscanWithRatelimitRetries(endpoint) as List<dynamic>;
     253              : 
     254            1 :     return [
     255            1 :       for (final tx in result)
     256            1 :         EtherscanTransaction.fromJsonErc721(
     257              :           tx,
     258            1 :           currency: currency,
     259              :           address: address,
     260              :         ),
     261              :     ];
     262              :   }
     263              : 
     264              :   ///
     265              :   /// Fetch Gas Prices
     266              :   ///
     267            1 :   Future<EvmNetworkFees> fetchGasPrice() async {
     268            2 :     final gasOracleEndpoint = "${base}&module=gastracker&action=gasoracle";
     269              : 
     270            1 :     final result = await fetchEtherscanWithRatelimitRetries(gasOracleEndpoint);
     271            1 :     if (result is! Json) {
     272            0 :       throw Exception("Failed to fetch gas price");
     273              :     }
     274            1 :     final entity = EvmNetworkFees.fromJson(result);
     275              : 
     276              :     return entity;
     277              :   }
     278              : 
     279            0 :   Future<int?> fetchEstimatedTime(int gasPrice) async {
     280            0 :     final endpoint = "$base&module=gastracker&action=gasestimate&gasprice=$gasPrice";
     281            0 :     final result = await fetchEtherscanWithRatelimitRetries(endpoint);
     282            0 :     if (result is! String) {
     283            0 :       throw Exception("Failed to fetch gas price");
     284              :     }
     285            0 :     return int.tryParse(result);
     286              :   }
     287              : }
     288              : 
     289            0 : Future<List<T>> batchFutures<T>(
     290              :   Iterable<Future<T>> futures, {
     291              :   int batchSize = 10,
     292              : }) async {
     293            0 :   final results = <T>[];
     294            0 :   final batches = [
     295            0 :     for (var i = 0; i < futures.length; i += batchSize) futures.skip(i).take(batchSize)
     296              :   ];
     297              : 
     298            0 :   for (final batch in batches) {
     299            0 :     final batchResults = await Future.wait(batch);
     300            0 :     results.addAll(batchResults);
     301              :   }
     302              : 
     303              :   return results;
     304              : }
     305              : 
     306              : extension URLBuilder on String {
     307            1 :   String addOptionalParameter(String key, dynamic value) {
     308              :     if (value == null) {
     309              :       return this;
     310              :     }
     311            1 :     return "$this&$key=$value";
     312              :   }
     313              : }
     314              : 
     315              : class ZeniqScanExplorer extends EtherscanExplorer {
     316            1 :   ZeniqScanExplorer(super.base, super.apiKeys, super.currency);
     317              : 
     318            1 :   @override
     319              :   String buildERC1155TransactionEndpoint({
     320              :     required String address,
     321              :     required String contractAddress,
     322              :     int? startblock,
     323              :     int? endblock,
     324              :     int? page,
     325              :     int? offset,
     326              :     Sorting? sorting,
     327              :   }) {
     328            1 :     throw UnsupportedError(
     329              :       "Building ERC1155 Transaction Endpoint not supported",
     330              :     );
     331              :   }
     332              : }
        

Generated by: LCOV version 2.0-1