mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2025-01-08 20:09:24 +00:00
update fiat conversion service
This commit is contained in:
parent
cbedd8e2c6
commit
97877c4c7f
2 changed files with 97 additions and 56 deletions
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue