CW-1548: Solana wallet performance improvements (#3404)

* fix android CI * add incremental sync, parallel fetch for native and spl token, and other improvments on perf and display regarding lagging

David Adegoke committed Jul 16, 2026 at 17:57 UTC 0822c7d976c0d5a9daa18a69da3e12655206028a
10 files changed +284 -108
cw_core/lib/amount/money.dart
+4
@@ -191,6 +191,10 @@ class Money implements Comparable<Money> {
191 @override
192 int get hashCode => amount.hashCode ^ currency.hashCode;
193
194 + // Added this to reduce the hops we do to convert Money to double
195 + // for fiat conversion and display
196 + double toDouble() => amount / multiplierOf(currency.decimals);
197 +
198 @override
199 String toString() => formatFixed(amount, currency.decimals);
200
cw_core/lib/format_fixed.dart
+4 -6
@@ -4,13 +4,11 @@ String formatFixed(BigInt value, int? decimals, {int? fractionalDigits, bool tri
4 decimals ??= 0;
5 fractionalDigits ??= decimals;
6
7 - var multiplier = getMultiplier(decimals);
8 - // Make sure wei is a big number (convert as necessary)
7 + final multiplier = multiplierOf(decimals);
8 var negative = value.isNegative;
10 - if (negative) value = value * BigInt.from(-1);
9 + if (negative) value = -value;
10
12 - var fraction =
13 - value.modPow(BigInt.one, BigInt.parse(multiplier)).toString().padLeft(decimals, "0");
11 + var fraction = (value % multiplier).toString().padLeft(decimals, "0");
12
13 if (fractionalDigits < 0) fractionalDigits = 0;
14 if (fractionalDigits > decimals) fractionalDigits = decimals;
@@ -20,7 +18,7 @@ String formatFixed(BigInt value, int? decimals, {int? fractionalDigits, bool tri
18 fraction = removeTrailing("0", fraction);
19 }
20
23 - final whole = (value ~/ BigInt.parse(multiplier));
21 + final whole = value ~/ multiplier;
22
23 final valString = fraction.isEmpty ? "$whole" : "$whole.$fraction";
24
cw_core/lib/parse_fixed.dart
+6 -1
@@ -58,7 +58,7 @@ BigInt parseFixed(String value, int decimals) {
58
59 final wholeValue = BigInt.parse(whole);
60 final fractionValue = BigInt.parse(fraction);
61 - final multiplierValue = BigInt.parse(multiplier);
61 + final multiplierValue = multiplierOf(decimals);
62
63 var wei = (wholeValue * multiplierValue) + fractionValue;
64
@@ -69,3 +69,8 @@ BigInt parseFixed(String value, int decimals) {
69
70 // Returns a string "1" followed by decimal "0"s
71 String getMultiplier(int decimals) => "1".padRight(decimals + 1, "0");
72 +
73 +final _multipliers = <int, BigInt>{};
74 +
75 +// this is more direct and faster than having it as string then parsing everytime to get the number
76 +BigInt multiplierOf(int decimals) => _multipliers[decimals] ??= BigInt.from(10).pow(decimals);
cw_solana/lib/solana_client.dart
+105 -44
@@ -32,14 +32,29 @@ class TransactionFetchResult {
32 });
33 }
34
35 +class TransactionSyncResult {
36 + final List<SolanaTransactionModel> transactions;
37 + final String? newestSignature;
38 +
39 + TransactionSyncResult({
40 + required this.transactions,
41 + this.newestSignature,
42 + });
43 +}
44 +
45 class SolanaWalletClient {
46 // Minimum amount in SOL to consider a transaction valid (to filter spam)
47 static Money minValidAmount = Money.parse("0.00000003", CryptoCurrency.sol);
48 +
49 + static const int _signaturePageSize = 1000;
50 +
51 late final client = ProxyWrapper().getHttpIOClient();
52 SolanaRPC? _provider;
53 + bool _isStopped = false;
54
55 bool connect(Node node) {
56 try {
57 + _isStopped = false;
58 String formattedUrl;
59 String protocolUsed = node.isSSL ? "https" : "http";
60
@@ -625,10 +640,7 @@ class SolanaWalletClient {
640 incomingAmount = diff.toDouble();
641 incomingMintAddress = mint;
642 final token = await getTokenInfo(mint);
628 - printV(token?.symbol);
629 - printV(token?.decimals);
630 - incomingToken =
631 - token ?? const CryptoCurrency(name: "TOKEN", title: "TOKEN", decimals: 6);
643 + incomingToken = token ?? const CryptoCurrency(name: "TOKEN", title: "TOKEN", decimals: 6);
644 incomingTo = walletAddress;
645 // We find the intermediate account
646 if (instructions.isNotEmpty && instructions[0].accounts.isNotEmpty) {
@@ -951,27 +963,67 @@ class SolanaWalletClient {
963 return mints.toList();
964 }
965
954 - /// Load the Address's transactions into the account
955 - Future<List<SolanaTransactionModel>> fetchTransactions(
966 + Future<List<Map<String, dynamic>>> _getAllSignaturesSinceLastFetch(
967 + SolAddress address,
968 + String? until,
969 + Commitment? commitment,
970 + ) async {
971 + final signatures = <Map<String, dynamic>>[];
972 + String? before;
973 +
974 + while (true) {
975 + final currentPageSignatureResults = await _provider!.request(
976 + SolanaRPCGetSignaturesForAddress(
977 + account: address,
978 + commitment: commitment,
979 + until: until,
980 + before: before,
981 + limit: _signaturePageSize,
982 + ),
983 + );
984 +
985 + if (currentPageSignatureResults.isEmpty) break;
986 +
987 + signatures.addAll(currentPageSignatureResults);
988 +
989 + if (currentPageSignatureResults.length < _signaturePageSize) break;
990 +
991 + if (until == null) break;
992 +
993 + final lastSignatureOnPage = currentPageSignatureResults.last['signature'] as String;
994 +
995 + if (lastSignatureOnPage == before) break;
996 +
997 + before = lastSignatureOnPage;
998 + }
999 +
1000 + return signatures;
1001 + }
1002 +
1003 + Future<TransactionSyncResult> fetchTransactions(
1004 SolAddress address, {
1005 SPLToken? splToken,
1006 Commitment? commitment,
1007 SolAddress? walletAddress,
1008 + String? untilSignature,
1009 required void Function(List<SolanaTransactionModel>) onUpdate,
1010 }) async {
962 - List<SolanaTransactionModel> transactions = [];
1011 + final transactions = <SolanaTransactionModel>[];
1012 +
1013 try {
964 - final signatures = await _provider!.request(
965 - SolanaRPCGetSignaturesForAddress(
966 - account: address,
967 - commitment: commitment,
968 - ),
969 - );
1014 + final signatures =
1015 + await _getAllSignaturesSinceLastFetch(address, untilSignature, commitment);
1016 +
1017 + if (signatures.isEmpty) return TransactionSyncResult(transactions: transactions);
1018
1019 // The maximum concurrent batch size.
1020 const int batchSize = 10;
1021
1022 + bool hasFailures = false;
1023 +
1024 for (int i = 0; i < signatures.length; i += batchSize) {
1025 + if (_isStopped) return TransactionSyncResult(transactions: transactions);
1026 +
1027 final batch = signatures.skip(i).take(batchSize).toList();
1028
1029 final batchResponses = await Future.wait(batch.map((signature) async {
@@ -985,6 +1037,7 @@ class SolanaWalletClient {
1037 ),
1038 );
1039 } catch (e) {
1040 + hasFailures = true;
1041 return null;
1042 }
1043 }));
@@ -999,16 +1052,16 @@ class SolanaWalletClient {
1052
1053 final parsedTransactionsLists = await Future.wait(parsedTransactionsFutures);
1054
1002 - // We flatten the list of lists into a single list
1055 + final batchTransactions = <SolanaTransactionModel>[];
1056 for (final parsedList in parsedTransactionsLists) {
1057 if (parsedList != null) {
1005 - transactions.addAll(parsedList);
1058 + batchTransactions.addAll(parsedList);
1059 }
1060 }
1061
1009 - // Only update UI if we have new valid transactions
1010 - if (transactions.isNotEmpty) {
1011 - onUpdate(List<SolanaTransactionModel>.from(transactions));
1062 + if (batchTransactions.isNotEmpty) {
1063 + transactions.addAll(batchTransactions);
1064 + onUpdate(batchTransactions);
1065 }
1066
1067 if (i + batchSize < signatures.length) {
@@ -1016,38 +1069,53 @@ class SolanaWalletClient {
1069 }
1070 }
1071
1019 - return transactions;
1072 + return TransactionSyncResult(
1073 + transactions: transactions,
1074 + newestSignature: hasFailures ? null : signatures.first['signature'] as String,
1075 + );
1076 } catch (err, s) {
1077 printV('Error fetching transactions: $err \n$s');
1022 - return [];
1078 + return TransactionSyncResult(transactions: transactions);
1079 }
1080 }
1081
1026 - Future<List<SolanaTransactionModel>> getSPLTokenTransfers({
1082 + final Map<String, ProgramDerivedAddress> associatedTokenAccountCache = {};
1083 +
1084 + Future<TransactionSyncResult> getSPLTokenTransfers({
1085 required String mintAddress,
1086 required SPLToken splToken,
1087 required SolanaPrivateKey privateKey,
1088 + String? untilSignature,
1089 required void Function(List<SolanaTransactionModel>) onUpdate,
1090 }) async {
1032 - ProgramDerivedAddress? associatedTokenAccount;
1091 final ownerWalletAddress = privateKey.publicKey().toAddress();
1034 - try {
1035 - associatedTokenAccount = await _getOrCreateAssociatedTokenAccount(
1036 - payerPrivateKey: privateKey,
1037 - mintAddress: SolAddress(mintAddress),
1038 - ownerAddress: ownerWalletAddress,
1039 - shouldCreateATA: false,
1040 - );
1041 - } catch (e, s) {
1042 - printV('$e \n $s');
1043 - }
1092
1045 - if (associatedTokenAccount == null) return [];
1093 + var associatedTokenAccount = associatedTokenAccountCache[mintAddress];
1094 +
1095 + if (associatedTokenAccount == null) {
1096 + try {
1097 + associatedTokenAccount = await _getOrCreateAssociatedTokenAccount(
1098 + payerPrivateKey: privateKey,
1099 + mintAddress: SolAddress(mintAddress),
1100 + ownerAddress: ownerWalletAddress,
1101 + shouldCreateATA: false,
1102 + );
1103 + } catch (e, s) {
1104 + printV('$e \n $s');
1105 + }
1106 +
1107 + if (associatedTokenAccount == null) {
1108 + return TransactionSyncResult(transactions: <SolanaTransactionModel>[]);
1109 + }
1110 +
1111 + associatedTokenAccountCache[mintAddress] = associatedTokenAccount;
1112 + }
1113
1114 return fetchTransactions(
1115 associatedTokenAccount.address,
1116 splToken: splToken,
1117 walletAddress: ownerWalletAddress,
1118 + untilSignature: untilSignature,
1119 onUpdate: onUpdate,
1120 );
1121 }
@@ -1055,16 +1123,9 @@ class SolanaWalletClient {
1123 final Map<String, SPLToken?> tokenInfoCache = {};
1124
1125 Future<SPLToken?> getTokenInfo(String mintAddress) async {
1058 - if (tokenInfoCache.containsKey(mintAddress)) {
1059 - printV("Cached");
1060 - return tokenInfoCache[mintAddress];
1061 - } else {
1062 - final token = await fetchSPLTokenInfo(mintAddress);
1063 - if (token != null) {
1064 - tokenInfoCache[mintAddress] = token;
1065 - }
1066 - return token;
1067 - }
1126 + if (tokenInfoCache.containsKey(mintAddress)) return tokenInfoCache[mintAddress];
1127 +
1128 + return tokenInfoCache[mintAddress] = await fetchSPLTokenInfo(mintAddress);
1129 }
1130
1131 Future<SPLToken?> fetchSPLTokenInfo(String mintAddress) async {
@@ -1139,7 +1200,7 @@ class SolanaWalletClient {
1200 }
1201 }
1202
1142 - void stop() {}
1203 + void stop() => _isStopped = true;
1204
1205 SolanaRPC? get getSolanaProvider => _provider;
1206
cw_solana/lib/solana_transaction_history.dart
+29 -11
@@ -31,18 +31,36 @@ abstract class SolanaTransactionHistoryBase extends TransactionHistoryBase<Solan
31 await _load();
32 }
33
34 + Future<void> _saveQueue = Future.value();
35 +
36 @override
35 - Future<void> save() async {
36 - try {
37 - final dirPath = await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
38 - final path = '$dirPath/$transactionsHistoryFileName';
39 - final transactionMaps = transactions.map((key, value) => MapEntry(key, value.toJson()));
40 - final data = json.encode({'transactions': transactionMaps});
41 - await encryptionFileUtils.write(path: path, password: _password, data: data);
42 - } catch (e, s) {
43 - printV('Error while saving solana transaction history: ${e.toString()}');
44 - printV(s);
45 - }
37 + Future<void> save() => saveAndConfirm();
38 +
39 + Future<bool> saveAndConfirm() {
40 + final write = _saveQueue.then((_) async {
41 + try {
42 + await _write();
43 +
44 + return true;
45 + } catch (e, s) {
46 + printV('Error while saving solana transaction history: ${e.toString()}');
47 + printV(s);
48 +
49 + return false;
50 + }
51 + });
52 +
53 + _saveQueue = write;
54 +
55 + return write;
56 + }
57 +
58 + Future<void> _write() async {
59 + final dirPath = await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
60 + final path = '$dirPath/$transactionsHistoryFileName';
61 + final transactionMaps = transactions.map((key, value) => MapEntry(key, value.toJson()));
62 + final data = json.encode({'transactions': transactionMaps});
63 + await encryptionFileUtils.write(path: path, password: _password, data: data);
64 }
65
66 @override
cw_solana/lib/solana_wallet.dart
+106 -29
@@ -88,6 +88,8 @@ abstract class SolanaWalletBase
88
89 Timer? _transactionsUpdateTimer;
90
91 + Future<void>? _currentRefresh;
92 +
93 late final Box<SPLToken> splTokensBox;
94
95 @override
@@ -321,6 +323,33 @@ abstract class SolanaWalletBase
323 ]);
324 }
325
326 + static const _nativeSource = 'native';
327 +
328 + String _lastSyncedSignatureKey(String source) =>
329 + 'solana_last_synced_signature_${walletInfo.name}_$source';
330 +
331 + Future<String?> _lastSyncedSignature(String source) async {
332 + if (transactionHistory.transactions.isEmpty) return null;
333 +
334 + final prefs = await _sharedPrefs.future;
335 +
336 + return prefs.getString(_lastSyncedSignatureKey(source));
337 + }
338 +
339 + Future<void> _saveLastSyncedSignature(String source, String? signature) async {
340 + if (signature == null) return;
341 +
342 + final prefs = await _sharedPrefs.future;
343 +
344 + await prefs.setString(_lastSyncedSignatureKey(source), signature);
345 + }
346 +
347 + Future<void> _clearLastSyncedSignature(String source) async {
348 + final prefs = await _sharedPrefs.future;
349 +
350 + await prefs.remove(_lastSyncedSignatureKey(source));
351 + }
352 +
353 /// Polls for a specific transaction by signature with exponential backoff
354 /// I'm using this in case we make the call to fetch the transaction and it has not finished its confirmations on the solana network and been indexed by the node networks we use.
355 Future<void> pollForTransaction({
@@ -365,20 +394,34 @@ abstract class SolanaWalletBase
394 await updateTransactionsHistory();
395 }
396
368 - void updateTransactions(List<SolanaTransactionModel> updatedTx) {
369 - addTransactionsToTransactionHistory(updatedTx);
370 - }
397 + void updateTransactions(List<SolanaTransactionModel> updatedTx) => _addTransactions(updatedTx);
398
399 /// Fetches the native SOL transactions linked to the wallet Public Key
400 Future<void> _updateNativeSOLTransactions() async {
374 - final transactions =
375 - await _client.fetchTransactions(_solanaPublicKey.toAddress(), onUpdate: updateTransactions);
401 + final result = await _client.fetchTransactions(
402 + _solanaPublicKey.toAddress(),
403 + untilSignature: await _lastSyncedSignature(_nativeSource),
404 + onUpdate: updateTransactions,
405 + );
406
377 - await addTransactionsToTransactionHistory(transactions);
407 + await _updateStateWhenSyncForTheSourceEnds(_nativeSource, result);
408 + }
409 +
410 + Future<void> _updateStateWhenSyncForTheSourceEnds(
411 + String source,
412 + TransactionSyncResult result,
413 + ) async {
414 + if (result.transactions.isNotEmpty) {
415 + final isSaved = await transactionHistory.saveAndConfirm();
416 +
417 + if (!isSaved) return;
418 + }
419 +
420 + await _saveLastSyncedSignature(source, result.newestSignature);
421 }
422
423 Future<void> updateSPLTokenTransactions({List<String>? specificMints}) async {
381 - final allTokens = balance.keys.whereType<SPLToken>().toList(growable: false);
424 + final allTokens = splTokensBox.values.where((t) => t.enabled).toList(growable: false);
425
426 // Filter to specific mints if provided
427 final tokens = specificMints != null
@@ -394,30 +437,28 @@ abstract class SolanaWalletBase
437 i,
438 i + batchSize > tokens.length ? tokens.length : i + batchSize,
439 );
397 - final results = await Future.wait(
440 +
441 + await Future.wait(
442 batch.map((token) async {
443 try {
400 - return await _client.getSPLTokenTransfers(
444 + final result = await _client.getSPLTokenTransfers(
445 mintAddress: token.mintAddress,
446 splToken: token,
447 privateKey: _solanaPrivateKey,
448 + untilSignature: await _lastSyncedSignature(token.mintAddress),
449 onUpdate: updateTransactions,
450 );
406 - } catch (_) {
407 - return <SolanaTransactionModel>[];
451 +
452 + await _updateStateWhenSyncForTheSourceEnds(token.mintAddress, result);
453 + } catch (e) {
454 + printV('Error fetching spl token (${token.symbol}) transfers ${e.toString()}');
455 }
456 }),
457 );
411 -
412 - for (final list in results) {
413 - await addTransactionsToTransactionHistory(list);
414 - }
458 }
459 }
460
418 - Future<void> addTransactionsToTransactionHistory(
419 - List<SolanaTransactionModel> transactions,
420 - ) async {
461 + void _addTransactions(List<SolanaTransactionModel> transactions) {
462 final Map<String, SolanaTransactionInfo> result = {};
463
464 for (var transactionModel in transactions) {
@@ -436,6 +477,12 @@ abstract class SolanaWalletBase
477 }
478
479 transactionHistory.addMany(result);
480 + }
481 +
482 + Future<void> addTransactionsToTransactionHistory(
483 + List<SolanaTransactionModel> transactions,
484 + ) async {
485 + _addTransactions(transactions);
486
487 await transactionHistory.save();
488 }
@@ -456,6 +503,17 @@ abstract class SolanaWalletBase
503 await transactionHistory.save();
504 }
505
506 + // we want to handle the case where multiple refresh triggers (our users can swipe down
507 + // multiple times), so we track the currrent refresh and join it instead of starting
508 + // another one
509 + Future<void> _refresh() {
510 + return _currentRefresh ??= Future.wait([
511 + updateTokenBalance(),
512 + updateTransactionsHistory(),
513 + _getEstimatedFees(),
514 + ]).whenComplete(() => _currentRefresh = null);
515 + }
516 +
517 @action
518 @override
519 Future<void> startSync() async {
@@ -469,12 +527,7 @@ abstract class SolanaWalletBase
527 return;
528 }
529
472 - await Future.wait([
473 - updateTokenBalance(),
474 - _updateNativeSOLTransactions(),
475 - updateSPLTokenTransactions(),
476 - _getEstimatedFees(),
477 - ]);
530 + await _refresh();
531
532 syncStatus = SyncedSyncStatus();
533 } catch (e) {
@@ -626,6 +679,14 @@ abstract class SolanaWalletBase
679
680 List<SPLToken> get splTokenCurrencies => splTokensBox.values.toList();
681
682 + SPLToken? splTokenBySymbol(String symbol) {
683 + for (final token in splTokensBox.values) {
684 + if (token.symbol == symbol) return token;
685 + }
686 +
687 + return null;
688 + }
689 +
690 void addInitialTokens() {
691 final initialSPLTokens = DefaultSPLTokens().initialSPLTokens;
692
@@ -802,12 +863,27 @@ abstract class SolanaWalletBase
863 }
864
865 Future<void> deleteSPLToken(SPLToken token) async {
866 + final sources = <String>{token.mintAddress};
867 +
868 + if (token.symbol == CryptoCurrency.sol.symbol) {
869 + sources.add(_nativeSource);
870 + }
871 +
872 if (splTokensBox.isOpen) {
873 + sources.addAll(splTokensBox.values
874 + .where((t) => t.symbol == token.symbol)
875 + .map((t) => t.mintAddress));
876 +
877 await splTokensBox.delete(token.mintAddress);
878 }
879
880 balance.remove(token);
881 await _removeTokenTransactionsInHistory(token);
882 +
883 + for (final source in sources) {
884 + await _clearLastSyncedSignature(source);
885 + }
886 +
887 updateTokenBalance();
888 }
889
@@ -831,11 +907,12 @@ abstract class SolanaWalletBase
907 _transactionsUpdateTimer!.cancel();
908 }
909
834 - _transactionsUpdateTimer = Timer.periodic(const Duration(seconds: 30), (_) {
835 - updateTokenBalance();
836 - _updateNativeSOLTransactions();
837 - updateSPLTokenTransactions();
838 - _getEstimatedFees();
910 + _transactionsUpdateTimer = Timer.periodic(const Duration(seconds: 30), (_) async {
911 + try {
912 + await _refresh();
913 + } catch (e) {
914 + printV('Error on periodic solana refresh: $e');
915 + }
916 });
917 }
918
lib/solana/cw_solana.dart
+16 -3
@@ -122,9 +122,13 @@ class CWSolana extends Solana {
122 return CryptoCurrency.sol;
123 }
124
125 - return (wallet as SolanaWallet).splTokenCurrencies.firstWhere(
126 - (element) => transaction.amount.currency.symbol == element.symbol,
127 - );
125 + final token = (wallet as SolanaWallet).splTokenBySymbol(transaction.amount.currency.symbol);
126 +
127 + if (token == null) {
128 + throw StateError('No SPL token for symbol ${transaction.amount.currency.symbol}');
129 + }
130 +
131 + return token;
132 }
133
134 @override
@@ -401,6 +405,15 @@ class CWSolana extends Solana {
405 }
406
407 await Future.wait(tokenChecks);
408 +
409 + final discoveredMints = result.newTokens
410 + .where((item) => item.token.enabled)
411 + .map((item) => item.token.mintAddress)
412 + .toList();
413 +
414 + if (discoveredMints.isNotEmpty) {
415 + await wallet.updateSPLTokenTransactions(specificMints: discoveredMints);
416 + }
417 } catch (_) {}
418 }
419
lib/view_model/dashboard/balance_view_model.dart
-2
@@ -14,7 +14,6 @@ import 'package:cake_wallet/zano/zano.dart';
14 import 'package:cw_core/amount/money.dart';
15 import 'package:cw_core/crypto_amount_format.dart';
16 import 'package:cw_core/transaction_history.dart';
17 -import 'package:cw_core/utils/print_verbose.dart';
17 import 'package:cw_core/wallet_base.dart';
18 import 'package:cake_wallet/store/app_store.dart';
19 import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
@@ -498,7 +497,6 @@ abstract class BalanceViewModelBase with Store {
497 final record = wallet.balance[curr]!;
498 final available = record.available - (record.secondAvailable ?? Money.zero(curr));
499 final price = fiatConversionStore.prices[curr] ?? 0;
501 - printV(record.available);
500 ret += double.tryParse(calculateFiatAmount(price: price, cryptoAmount: available.toString())
501 .replaceAll(",", "")) ??
502 0;
lib/view_model/dashboard/dashboard_view_model.dart
+8 -6
@@ -227,9 +227,10 @@ abstract class DashboardViewModelBase with Store {
227 1;
228 } catch (_) {}
229 } else {
230 - confirmations = appStore.wallet!.transactionHistory.transactions.values
231 - .map((item) => item.isPending)
232 - .fold(0, (val, pending) => pending ? val + 1 : val);
230 + final pendingCount = appStore.wallet!.transactionHistory.transactions.values
231 + .where((item) => item.isPending)
232 + .length;
233 + confirmations = pendingCount + 1;
234 }
235 return length * confirmations;
236 }, _transactionDisposerCallback, delay: 300);
@@ -1331,9 +1332,10 @@ abstract class DashboardViewModelBase with Store {
1332 1;
1333 } catch (_) {}
1334 } else {
1334 - confirmations = appStore.wallet!.transactionHistory.transactions.values
1335 - .map((item) => item.isPending)
1336 - .fold(0, (val, pending) => pending ? val + 1 : val);
1335 + final pendingCount = appStore.wallet!.transactionHistory.transactions.values
1336 + .where((item) => item.isPending)
1337 + .length;
1338 + confirmations = pendingCount + 1;
1339 }
1340 return length * confirmations;
1341 }, _transactionDisposerCallback, delay: 300);
lib/view_model/dashboard/transaction_list_item.dart
+6 -6
@@ -195,7 +195,7 @@ class TransactionListItem extends ActionListItem with Keyable {
195 case WalletType.decred:
196 case WalletType.zcash:
197 amount = calculateFiatAmountRaw(
198 - cryptoAmount: double.parse(transaction.amount.toString()),
198 + cryptoAmount: transaction.amount.toDouble(),
199 price: price,
200 ).withLocalSeperator(_appStore.settingsStore.languageCode);
201 case WalletType.ethereum:
@@ -206,15 +206,15 @@ class TransactionListItem extends ActionListItem with Keyable {
206 final asset = assetOfTransaction;
207 final price = balanceViewModel.fiatConversionStore.prices[asset];
208 amount = calculateFiatAmountRaw(
209 - cryptoAmount: double.parse(transaction.amount.toString()),
209 + cryptoAmount: transaction.amount.toDouble(),
210 price: price,
211 ).withLocalSeperator(_appStore.settingsStore.languageCode);
212 break;
213 case WalletType.solana:
214 - final asset = solana!.assetOfTransaction(balanceViewModel.wallet, transaction);
214 + final asset = assetOfTransaction;
215 final price = balanceViewModel.fiatConversionStore.prices[asset];
216 amount = calculateFiatAmountRaw(
217 - cryptoAmount: double.parse(transaction.amount.toString()),
217 + cryptoAmount: transaction.amount.toDouble(),
218 price: price,
219 ).withLocalSeperator(_appStore.settingsStore.languageCode);
220 break;
@@ -222,7 +222,7 @@ class TransactionListItem extends ActionListItem with Keyable {
222 final asset = tron!.assetOfTransaction(balanceViewModel.wallet, transaction);
223 final price = balanceViewModel.fiatConversionStore.prices[asset];
224 amount = calculateFiatAmountRaw(
225 - cryptoAmount: double.parse(transaction.amount.toString()),
225 + cryptoAmount: transaction.amount.toDouble(),
226 price: price,
227 ).withLocalSeperator(_appStore.settingsStore.languageCode);
228 break;
@@ -234,7 +234,7 @@ class TransactionListItem extends ActionListItem with Keyable {
234 }
235 final price = balanceViewModel.fiatConversionStore.prices[asset];
236 amount = calculateFiatAmountRaw(
237 - cryptoAmount: double.parse(transaction.amount.toString()),
237 + cryptoAmount: transaction.amount.toDouble(),
238 price: price,
239 ).withLocalSeperator(_appStore.settingsStore.languageCode);
240 break;