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

Generated by: LCOV version 2.0-1