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;