diff --git a/lib/core/fiat_conversion_service.dart b/lib/core/fiat_conversion_service.dart index 3725a16ff..10f9113bc 100644 --- a/lib/core/fiat_conversion_service.dart +++ b/lib/core/fiat_conversion_service.dart @@ -50,19 +50,19 @@ Future _fetchPrice(Map args) async { } } -Future _fetchHistoricalPrice(Map args) async { +Future?> _fetchHistoricalPrice(Map args) async { final crypto = args['crypto'] as CryptoCurrency; final fiat = args['fiat'] as FiatCurrency; final torOnly = args['torOnly'] as bool; - final date = args['date'] as DateTime; - final intervalFromNow = DateTime.now().difference(date).inMinutes; + final intervalCount = args['intervalCount'] as int; + final intervalMinutes = args['intervalMinutes'] as int; final Map queryParams = { - 'interval_count': '2', + 'interval_count': intervalCount.toString(), 'base': crypto.toString(), 'quote': fiat.toString(), 'key': secrets.fiatApiKey, - 'interval_minutes': intervalFromNow.toString() + 'interval_minutes': intervalMinutes.toString() }; try { @@ -78,19 +78,12 @@ Future _fetchHistoricalPrice(Map args) async { if (response.statusCode != 200) return null; final data = json.decode(response.body) as Map; - final errors = data['errors'] as Map; - - if (errors.isNotEmpty) return null; final results = data['results'] as Map; - if (results.isNotEmpty) { - return (results.values.first as double) / 100000000; + if (results.isNotEmpty) return results; - } else { return null; - } - } catch (e) { print(e.toString()); return null; @@ -104,10 +97,15 @@ Future _fetchPriceAsync(CryptoCurrency crypto, FiatCurrency fiat, bool t 'torOnly': torOnly, }); -Future _fetchHistoricalAsync( - CryptoCurrency crypto, FiatCurrency fiat, bool torOnly, DateTime date) async => - compute( - _fetchHistoricalPrice, {'fiat': fiat, 'crypto': crypto, 'torOnly': torOnly, 'date': date}); +Future?> _fetchHistoricalAsync(CryptoCurrency crypto, FiatCurrency fiat, bool torOnly, + int intervalCount, int intervalMinutes) async => + compute(_fetchHistoricalPrice, { + 'fiat': fiat, + 'crypto': crypto, + 'torOnly': torOnly, + 'intervalCount': intervalCount, + 'intervalMinutes': intervalMinutes + }); class FiatConversionService { static Future fetchPrice({ @@ -117,11 +115,12 @@ class FiatConversionService { }) async => await _fetchPriceAsync(crypto, fiat, torOnly); - static Future fetchHistoricalPrice({ + static Future?> fetchHistoricalPrice({ required CryptoCurrency crypto, required FiatCurrency fiat, required bool torOnly, - required DateTime date, + required int intervalCount, + required int intervalMinutes, }) async => - await _fetchHistoricalAsync(crypto, fiat, torOnly, date); + await _fetchHistoricalAsync(crypto, fiat, torOnly, intervalCount, intervalMinutes); } diff --git a/lib/reactions/fiat_historical_rate_update.dart b/lib/reactions/fiat_historical_rate_update.dart index 9adecba4e..c4724a331 100644 --- a/lib/reactions/fiat_historical_rate_update.dart +++ b/lib/reactions/fiat_historical_rate_update.dart @@ -4,7 +4,9 @@ import 'package:cake_wallet/entities/fiat_api_mode.dart'; import 'package:cake_wallet/entities/transaction_description.dart'; import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart'; import 'package:cake_wallet/store/settings_store.dart'; +import 'package:cw_core/amount_converter.dart'; import 'package:cw_core/wallet_base.dart'; +import 'package:cw_core/wallet_type.dart'; import 'package:hive/hive.dart'; Future historicalRateUpdate( @@ -12,56 +14,96 @@ Future historicalRateUpdate( SettingsStore settingsStore, FiatConversionStore fiatConversionStore, Box transactionDescription) async { + int accuracyMinutes = 0; + + switch (wallet.type) { + case WalletType.monero: + case WalletType.polygon: + case WalletType.nano: + case WalletType.solana: + case WalletType.haven: + case WalletType.tron: + accuracyMinutes = 540; // 9 hours + break; + case WalletType.ethereum: + accuracyMinutes = 360; // 6 hours + break; + case WalletType.bitcoin: + case WalletType.bitcoinCash: + case WalletType.litecoin: + accuracyMinutes = 180; // 3 hours + break; + default: + accuracyMinutes = 180; // 3 hours + } + + final historicalRateStorageDurationMinutes = 86400; // 2 months + final intervalCount = historicalRateStorageDurationMinutes ~/ accuracyMinutes; + final totalAllowedAgeMinutes = historicalRateStorageDurationMinutes + accuracyMinutes; + final currentTime = DateTime.now(); + + final result = await FiatConversionService.fetchHistoricalPrice( + crypto: wallet.currency, + fiat: settingsStore.fiatCurrency, + torOnly: settingsStore.fiatApiMode == FiatApiMode.torOnly, + intervalCount: intervalCount, + intervalMinutes: accuracyMinutes); + + if (result == null) return; + + Map convertedRates = {}; + + result.forEach((key, value) { + DateTime keyAsDateTime = DateTime.parse(key).toUtc(); + convertedRates[keyAsDateTime] = value as double; + }); + final transactions = wallet.transactionHistory.transactions.values.toList(); - const int batchSize = 10; - const Duration delayBetweenBatches = Duration(milliseconds: 2); + for (var tx in transactions) { + final txAgeMinutes = currentTime.difference(tx.date).inMinutes; + if (txAgeMinutes > totalAllowedAgeMinutes) continue; - int nextBatchStart = 0; + var description = transactionDescription.get(tx.id); + final fiatName = settingsStore.fiatCurrency.toString(); - for (int i = 0; i < transactions.length; i += batchSize) { - final batch = transactions.skip(i).take(batchSize); + if (description == null || + description.historicalRates.isEmpty || + !description.historicalRates.containsKey(fiatName)) { + try { + List historyTimestamps = convertedRates.keys.toList(); + final txHistoryTimestamps = tx.date.toUtc(); - bool needsProcessing = batch.any((tx) { - var description = transactionDescription.get(tx.id); - final fiatName = settingsStore.fiatCurrency.toString(); - return description == null || - description.historicalRates.isEmpty || - !description.historicalRates.containsKey(fiatName); - }); + final closestTimestamp = findClosestTimestamp(historyTimestamps, txHistoryTimestamps); - if (needsProcessing) { - await Future.wait(batch.map((tx) async { - var description = transactionDescription.get(tx.id); - final fiatName = settingsStore.fiatCurrency.toString(); - - if (description == null || - description.historicalRates.isEmpty || - !description.historicalRates.containsKey(fiatName)) { - try { - final result = await FiatConversionService.fetchHistoricalPrice( - crypto: wallet.currency, - fiat: settingsStore.fiatCurrency, - torOnly: settingsStore.fiatApiMode == FiatApiMode.torOnly, - date: tx.date); - - if (result == null) return; + if (closestTimestamp != null && + txHistoryTimestamps.difference(closestTimestamp).abs() <= + Duration(minutes: accuracyMinutes)) { + final rate = convertedRates[closestTimestamp]; + if (rate != null) { description ??= TransactionDescription(id: tx.id); Map rates = description.historicalRates; - rates[fiatName] = (result * tx.amount).toString(); + rates[fiatName] = + (rate * AmountConverter.amountIntToDouble(wallet.currency, tx.amount)).toString(); description.historicalRates = rates; await transactionDescription.put(tx.id, description); - } catch (e) { - print("Error fetching historical price: $e"); } } - })); - - nextBatchStart = i + batchSize; - if (nextBatchStart < transactions.length) { - await Future.delayed(delayBetweenBatches); + } catch (e) { + print("Error fetching historical price: $e"); } } } } + +DateTime? findClosestTimestamp(List timestamps, DateTime target) { + DateTime? closest; + for (var timestamp in timestamps) { + if (closest == null || + (target.difference(timestamp).abs() < target.difference(closest).abs())) { + closest = timestamp; + } + } + return closest; +}