update fiat conversion service

This commit is contained in:
Serhii 2024-05-08 14:16:44 +03:00
parent cbedd8e2c6
commit 97877c4c7f
2 changed files with 97 additions and 56 deletions

View file

@ -50,19 +50,19 @@ Future<double> _fetchPrice(Map<String, dynamic> args) async {
} }
} }
Future<double?> _fetchHistoricalPrice(Map<String, dynamic> args) async { Future<Map<String, dynamic>?> _fetchHistoricalPrice(Map<String, dynamic> args) async {
final crypto = args['crypto'] as CryptoCurrency; final crypto = args['crypto'] as CryptoCurrency;
final fiat = args['fiat'] as FiatCurrency; final fiat = args['fiat'] as FiatCurrency;
final torOnly = args['torOnly'] as bool; final torOnly = args['torOnly'] as bool;
final date = args['date'] as DateTime; final intervalCount = args['intervalCount'] as int;
final intervalFromNow = DateTime.now().difference(date).inMinutes; final intervalMinutes = args['intervalMinutes'] as int;
final Map<String, String> queryParams = { final Map<String, String> queryParams = {
'interval_count': '2', 'interval_count': intervalCount.toString(),
'base': crypto.toString(), 'base': crypto.toString(),
'quote': fiat.toString(), 'quote': fiat.toString(),
'key': secrets.fiatApiKey, 'key': secrets.fiatApiKey,
'interval_minutes': intervalFromNow.toString() 'interval_minutes': intervalMinutes.toString()
}; };
try { try {
@ -78,19 +78,12 @@ Future<double?> _fetchHistoricalPrice(Map<String, dynamic> args) async {
if (response.statusCode != 200) return null; if (response.statusCode != 200) return null;
final data = json.decode(response.body) as Map<String, dynamic>; final data = json.decode(response.body) as Map<String, dynamic>;
final errors = data['errors'] as Map<String, dynamic>;
if (errors.isNotEmpty) return null;
final results = data['results'] as Map<String, dynamic>; final results = data['results'] as Map<String, dynamic>;
if (results.isNotEmpty) { if (results.isNotEmpty) return results;
return (results.values.first as double) / 100000000;
} else {
return null; return null;
}
} catch (e) { } catch (e) {
print(e.toString()); print(e.toString());
return null; return null;
@ -104,10 +97,15 @@ Future<double> _fetchPriceAsync(CryptoCurrency crypto, FiatCurrency fiat, bool t
'torOnly': torOnly, 'torOnly': torOnly,
}); });
Future<double?> _fetchHistoricalAsync( Future<Map<String, dynamic>?> _fetchHistoricalAsync(CryptoCurrency crypto, FiatCurrency fiat, bool torOnly,
CryptoCurrency crypto, FiatCurrency fiat, bool torOnly, DateTime date) async => int intervalCount, int intervalMinutes) async =>
compute( compute(_fetchHistoricalPrice, {
_fetchHistoricalPrice, {'fiat': fiat, 'crypto': crypto, 'torOnly': torOnly, 'date': date}); 'fiat': fiat,
'crypto': crypto,
'torOnly': torOnly,
'intervalCount': intervalCount,
'intervalMinutes': intervalMinutes
});
class FiatConversionService { class FiatConversionService {
static Future<double> fetchPrice({ static Future<double> fetchPrice({
@ -117,11 +115,12 @@ class FiatConversionService {
}) async => }) async =>
await _fetchPriceAsync(crypto, fiat, torOnly); await _fetchPriceAsync(crypto, fiat, torOnly);
static Future<double?> fetchHistoricalPrice({ static Future<Map<String, dynamic>?> fetchHistoricalPrice({
required CryptoCurrency crypto, required CryptoCurrency crypto,
required FiatCurrency fiat, required FiatCurrency fiat,
required bool torOnly, required bool torOnly,
required DateTime date, required int intervalCount,
required int intervalMinutes,
}) async => }) async =>
await _fetchHistoricalAsync(crypto, fiat, torOnly, date); await _fetchHistoricalAsync(crypto, fiat, torOnly, intervalCount, intervalMinutes);
} }

View file

@ -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/entities/transaction_description.dart';
import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart'; import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
import 'package:cake_wallet/store/settings_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_base.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
Future<void> historicalRateUpdate( Future<void> historicalRateUpdate(
@ -12,56 +14,96 @@ Future<void> historicalRateUpdate(
SettingsStore settingsStore, SettingsStore settingsStore,
FiatConversionStore fiatConversionStore, FiatConversionStore fiatConversionStore,
Box<TransactionDescription> transactionDescription) async { Box<TransactionDescription> 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<DateTime, double> convertedRates = {};
result.forEach((key, value) {
DateTime keyAsDateTime = DateTime.parse(key).toUtc();
convertedRates[keyAsDateTime] = value as double;
});
final transactions = wallet.transactionHistory.transactions.values.toList(); final transactions = wallet.transactionHistory.transactions.values.toList();
const int batchSize = 10; for (var tx in transactions) {
const Duration delayBetweenBatches = Duration(milliseconds: 2); 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) { if (description == null ||
final batch = transactions.skip(i).take(batchSize); description.historicalRates.isEmpty ||
!description.historicalRates.containsKey(fiatName)) {
try {
List<DateTime> historyTimestamps = convertedRates.keys.toList();
final txHistoryTimestamps = tx.date.toUtc();
bool needsProcessing = batch.any((tx) { final closestTimestamp = findClosestTimestamp(historyTimestamps, txHistoryTimestamps);
var description = transactionDescription.get(tx.id);
final fiatName = settingsStore.fiatCurrency.toString();
return description == null ||
description.historicalRates.isEmpty ||
!description.historicalRates.containsKey(fiatName);
});
if (needsProcessing) { if (closestTimestamp != null &&
await Future.wait(batch.map((tx) async { txHistoryTimestamps.difference(closestTimestamp).abs() <=
var description = transactionDescription.get(tx.id); Duration(minutes: accuracyMinutes)) {
final fiatName = settingsStore.fiatCurrency.toString(); final rate = convertedRates[closestTimestamp];
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 (rate != null) {
description ??= TransactionDescription(id: tx.id); description ??= TransactionDescription(id: tx.id);
Map<String, String> rates = description.historicalRates; Map<String, String> rates = description.historicalRates;
rates[fiatName] = (result * tx.amount).toString(); rates[fiatName] =
(rate * AmountConverter.amountIntToDouble(wallet.currency, tx.amount)).toString();
description.historicalRates = rates; description.historicalRates = rates;
await transactionDescription.put(tx.id, description); await transactionDescription.put(tx.id, description);
} catch (e) {
print("Error fetching historical price: $e");
} }
} }
})); } catch (e) {
print("Error fetching historical price: $e");
nextBatchStart = i + batchSize;
if (nextBatchStart < transactions.length) {
await Future.delayed(delayBetweenBatches);
} }
} }
} }
} }
DateTime? findClosestTimestamp(List<DateTime> 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;
}