fix: Already Known Error (#2606)

* fix: Already made error and update transactions two seconds after successful send for evm, tron and sol * feat: Add updateTransactionsHistory method on WalletBase to handle transaction update immediately after sending * Update lib/view_model/send/send_view_model.dart [skip ci] --------- Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>

David Adegoke committed Oct 25, 2025 at 12:34 UTC b0b053a19dbf59efeeb654cb9e1da9cf474b2889
5 files changed +80 -44
cw_core/lib/wallet_base.dart
+2 -1
@@ -102,6 +102,7 @@ abstract class WalletBase<BalanceType extends Balance, HistoryType extends Trans
102 String get password;
103
104 Future<void>? updateBalance();
105 + Future<void> updateTransactionsHistory() async {}
106
107 void setExceptionHandler(void Function(FlutterErrorDetails) onError) => null;
108
@@ -119,7 +120,7 @@ abstract class WalletBase<BalanceType extends Balance, HistoryType extends Trans
120 /// Returns true if the connection is alive, false otherwise.
121 /// Default implementation returns true (no-op for wallets without socket connections).
122 Future<bool> checkSocketHealth() async => true;
122 -
123 +
124 /// This is used to check if the current node is healthy by making a lightweight RPC call
125 /// Each wallet implementation should override this to make a single, efficient call
126 /// Returns true if the node is healthy, false otherwise
cw_evm/lib/evm_chain_wallet.dart
+3
@@ -894,6 +894,9 @@ abstract class EVMChainWalletBase
894
895 @override
896 Future<void>? updateBalance() async => await _updateBalance();
897 + @override
898 + Future<void> updateTransactionsHistory() async => await _updateTransactions();
899 +
900
901 List<Erc20Token> get erc20Currencies => evmChainErc20TokensBox.values.toList();
902
cw_solana/lib/solana_wallet.dart
+8
@@ -292,6 +292,14 @@ abstract class SolanaWalletBase
292 @override
293 Future<Map<String, SolanaTransactionInfo>> fetchTransactions() async => {};
294
295 + @override
296 + Future<void> updateTransactionsHistory() async {
297 + await Future.wait([
298 + _updateNativeSOLTransactions(),
299 + _updateSPLTokenTransactions(),
300 + ]);
301 + }
302 +
303 void updateTransactions(List<SolanaTransactionModel> updatedTx) {
304 _addTransactionsToTransactionHistory(updatedTx);
305 }
cw_tron/lib/tron_wallet.dart
+15 -5
@@ -180,9 +180,11 @@ abstract class TronWalletBase
180 for (var token in initialTronTokens) {
181 if (!tronTokensBox.containsKey(token.contractAddress)) {
182 tronTokensBox.put(token.contractAddress, token);
183 - } else { // update existing token
183 + } else {
184 + // update existing token
185 final existingToken = tronTokensBox.get(token.contractAddress);
185 - tronTokensBox.put(token.contractAddress, TronToken.copyWith(token, enabled: existingToken!.enabled));
186 + tronTokensBox.put(
187 + token.contractAddress, TronToken.copyWith(token, enabled: existingToken!.enabled));
188 }
189 }
190 }
@@ -281,7 +283,7 @@ abstract class TronWalletBase
283 Future<void> startSync() async {
284 try {
285 syncStatus = AttemptingSyncStatus();
284 -
286 +
287 // Verify node health before attempting to sync
288 final isHealthy = await checkNodeHealth();
289 if (!isHealthy) {
@@ -359,6 +361,14 @@ abstract class TronWalletBase
361 return pendingTransaction;
362 }
363
364 + @override
365 + Future<void> updateTransactionsHistory() async {
366 + await Future.wait([
367 + fetchTransactions(),
368 + fetchTrc20ExcludedTransactions(),
369 + ]);
370 + }
371 +
372 @override
373 Future<Map<String, TronTransactionInfo>> fetchTransactions() async {
374 final address = _tronAddress;
@@ -522,11 +532,11 @@ abstract class TronWalletBase
532 try {
533 // Check native balance
534 await _client.getBalance(_tronPublicKey.toAddress(), throwOnError: true);
525 -
535 +
536 // Check USDT token balance
537 const usdtContractAddress = "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t";
538 await _client.fetchTronTokenBalances(_tronAddress, usdtContractAddress, throwOnError: true);
529 -
539 +
540 return true;
541 } catch (e) {
542 return false;
lib/view_model/send/send_view_model.dart
+52 -38
@@ -139,8 +139,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
139
140 bool get isMwebEnabled => balanceViewModel.mwebEnabled;
141
142 - bool get isEVMWallet => walletType == WalletType.ethereum || walletType == WalletType.polygon ||
143 - walletType == WalletType.base;
142 + bool get isEVMWallet => isEVMCompatibleChain(walletType);
143
144 @action
145 void setShowAddressBookPopup(bool value) {
@@ -514,7 +513,6 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
513 });
514 }
515
517 -
516 // Swaps.xyz (EVM) path
517
518 if (isEVMWallet && trade != null && provider is SwapsXyzExchangeProvider) {
@@ -524,7 +522,6 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
522 BigInt.tryParse((trade.routerValue ?? '0').toString()) ?? BigInt.zero;
523
524 if (routerTo?.isNotEmpty == true && routerData?.isNotEmpty == true) {
527 -
525 // detect prepared ERC-20 transfer(...) (alt-vm deposit pattern)
526 String _selector(String s) =>
527 (s.startsWith('0x') && s.length >= 10) ? s.substring(0, 10) : '';
@@ -538,11 +535,13 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
535 // Optionally prebuild approval (SKIP for prepared transfer)
536 final tokenContract = trade.sourceTokenAddress ?? '';
537 final requiredAmount = BigInt.tryParse(
541 - (trade.sourceTokenAmountRaw ?? '0').replaceAll('n', ''),
542 - ) ?? BigInt.zero;
538 + (trade.sourceTokenAmountRaw ?? '0').replaceAll('n', ''),
539 + ) ??
540 + BigInt.zero;
541
542 // Only do approval when NOT a prepared transfer, and only if the API hinted we might need it
545 - final requiresTokenApproval = (trade.requiresTokenApproval ?? false) && !_isPreparedTransfer;
543 + final requiresTokenApproval =
544 + (trade.requiresTokenApproval ?? false) && !_isPreparedTransfer;
545
546 if (requiresTokenApproval && tokenContract.isNotEmpty && requiredAmount > BigInt.zero) {
547 if (walletType == WalletType.ethereum) {
@@ -656,7 +655,6 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
655 }
656 }
657
659 -
658 // Regular flow
659
660 pendingTransaction = await wallet.createTransaction(_credentials(provider));
@@ -772,21 +770,6 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
770 return; // skip the regular flow below
771 }
772
775 - // Regular flow (non-Swaps)
776 - if (pendingTransaction!.shouldCommitUR()) {
777 - final urstr = await pendingTransaction!.commitUR();
778 - final result = await Navigator.of(context).pushNamed(
779 - Routes.urqrAnimatedPage,
780 - arguments: urstr,
781 - );
782 - if (result == null) {
783 - state = FailureState("Canceled by user");
784 - return;
785 - }
786 - } else {
787 - await pendingTransaction!.commit();
788 - }
789 -
773 String address = outputs.fold('', (acc, value) {
774 return value.isParsedAddress
775 ? '$acc${value.address}\n${value.extractedAddress}\n\n'
@@ -818,6 +801,17 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
801 nano!.updateTransactions(wallet);
802 }
803
804 + // Immediate transaction update for EVM chains, Solana, and Tron
805 + if (isEVMWallet || walletType == WalletType.solana || walletType == WalletType.tron) {
806 + Future.delayed(Duration(seconds: 4), () async {
807 + try {
808 + await wallet.updateTransactionsHistory();
809 + } catch (e) {
810 + printV('Failed to update transactions after send: $e');
811 + }
812 + });
813 + }
814 +
815 if (pendingTransaction!.id.isNotEmpty) {
816 TransactionInfo? tx;
817 if (walletType == WalletType.monero) {
@@ -1124,7 +1118,6 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
1118 required BigInt requiredAmount,
1119 int? sourceTokenDecimals,
1120 }) async {
1127 -
1121 // Only EVM chains support ERC20 approvals
1122 if (!isEVMWallet) return null;
1123
@@ -1138,15 +1131,24 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
1131 bool needsApproval = false;
1132 if (walletType == WalletType.ethereum) {
1133 needsApproval = await ethereum!.isApprovalRequired(
1141 - wallet, tokenContract, spender, requiredAmount,
1134 + wallet,
1135 + tokenContract,
1136 + spender,
1137 + requiredAmount,
1138 );
1139 } else if (walletType == WalletType.polygon) {
1140 needsApproval = await polygon!.isApprovalRequired(
1145 - wallet, tokenContract, spender, requiredAmount,
1141 + wallet,
1142 + tokenContract,
1143 + spender,
1144 + requiredAmount,
1145 );
1146 } else if (walletType == WalletType.base) {
1147 needsApproval = await base!.isApprovalRequired(
1149 - wallet, tokenContract, spender, requiredAmount,
1148 + wallet,
1149 + tokenContract,
1150 + spender,
1151 + requiredAmount,
1152 );
1153 }
1154
@@ -1154,29 +1156,41 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
1156
1157 final erc20Token = wallet.balance.keys.whereType<Erc20Token>().firstWhere(
1158 (t) => t.contractAddress.toLowerCase() == tokenLc,
1157 - orElse: () => Erc20Token(
1158 - name: '',
1159 - symbol: '',
1160 - contractAddress: tokenContract,
1161 - decimal: sourceTokenDecimals ?? 18,
1162 - enabled: true,
1163 - ),
1164 - );
1159 + orElse: () => Erc20Token(
1160 + name: '',
1161 + symbol: '',
1162 + contractAddress: tokenContract,
1163 + decimal: sourceTokenDecimals ?? 18,
1164 + enabled: true,
1165 + ),
1166 + );
1167
1168 if (walletType == WalletType.ethereum) {
1169 final priority = _settingsStore.priority[WalletType.ethereum]!;
1170 return await ethereum!.createTokenApproval(
1169 - wallet, requiredAmount, spender, erc20Token, priority,
1171 + wallet,
1172 + requiredAmount,
1173 + spender,
1174 + erc20Token,
1175 + priority,
1176 );
1177 } else if (walletType == WalletType.polygon) {
1178 final priority = _settingsStore.priority[WalletType.polygon]!;
1179 return await polygon!.createTokenApproval(
1174 - wallet, requiredAmount, spender, erc20Token, priority,
1180 + wallet,
1181 + requiredAmount,
1182 + spender,
1183 + erc20Token,
1184 + priority,
1185 );
1186 } else if (walletType == WalletType.base) {
1187 final priority = _settingsStore.priority[WalletType.base]!;
1188 return await base!.createTokenApproval(
1179 - wallet, requiredAmount, spender, erc20Token, priority,
1189 + wallet,
1190 + requiredAmount,
1191 + spender,
1192 + erc20Token,
1193 + priority,
1194 );
1195 }
1196