From b0b053a19dbf59efeeb654cb9e1da9cf474b2889 Mon Sep 17 00:00:00 2001 From: David Adegoke <64401859+Blazebrain@users.noreply.github.com> Date: Sat, 25 Oct 2025 12:34:29 +0100 Subject: [PATCH] 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 --- cw_core/lib/wallet_base.dart | 3 +- cw_evm/lib/evm_chain_wallet.dart | 3 + cw_solana/lib/solana_wallet.dart | 8 +++ cw_tron/lib/tron_wallet.dart | 20 ++++-- lib/view_model/send/send_view_model.dart | 90 ++++++++++++++---------- 5 files changed, 80 insertions(+), 44 deletions(-) diff --git a/cw_core/lib/wallet_base.dart b/cw_core/lib/wallet_base.dart index 769b14c3d..5ac3e9b45 100644 --- a/cw_core/lib/wallet_base.dart +++ b/cw_core/lib/wallet_base.dart @@ -102,6 +102,7 @@ abstract class WalletBase? updateBalance(); + Future updateTransactionsHistory() async {} void setExceptionHandler(void Function(FlutterErrorDetails) onError) => null; @@ -119,7 +120,7 @@ abstract class WalletBase checkSocketHealth() async => true; - + /// This is used to check if the current node is healthy by making a lightweight RPC call /// Each wallet implementation should override this to make a single, efficient call /// Returns true if the node is healthy, false otherwise diff --git a/cw_evm/lib/evm_chain_wallet.dart b/cw_evm/lib/evm_chain_wallet.dart index c1c375482..3bf066c9d 100644 --- a/cw_evm/lib/evm_chain_wallet.dart +++ b/cw_evm/lib/evm_chain_wallet.dart @@ -894,6 +894,9 @@ abstract class EVMChainWalletBase @override Future? updateBalance() async => await _updateBalance(); + @override + Future updateTransactionsHistory() async => await _updateTransactions(); + List get erc20Currencies => evmChainErc20TokensBox.values.toList(); diff --git a/cw_solana/lib/solana_wallet.dart b/cw_solana/lib/solana_wallet.dart index 3085dbb3e..581ba5622 100644 --- a/cw_solana/lib/solana_wallet.dart +++ b/cw_solana/lib/solana_wallet.dart @@ -292,6 +292,14 @@ abstract class SolanaWalletBase @override Future> fetchTransactions() async => {}; + @override + Future updateTransactionsHistory() async { + await Future.wait([ + _updateNativeSOLTransactions(), + _updateSPLTokenTransactions(), + ]); + } + void updateTransactions(List updatedTx) { _addTransactionsToTransactionHistory(updatedTx); } diff --git a/cw_tron/lib/tron_wallet.dart b/cw_tron/lib/tron_wallet.dart index 8bd1f97e4..6a6b55ac9 100644 --- a/cw_tron/lib/tron_wallet.dart +++ b/cw_tron/lib/tron_wallet.dart @@ -180,9 +180,11 @@ abstract class TronWalletBase for (var token in initialTronTokens) { if (!tronTokensBox.containsKey(token.contractAddress)) { tronTokensBox.put(token.contractAddress, token); - } else { // update existing token + } else { + // update existing token final existingToken = tronTokensBox.get(token.contractAddress); - tronTokensBox.put(token.contractAddress, TronToken.copyWith(token, enabled: existingToken!.enabled)); + tronTokensBox.put( + token.contractAddress, TronToken.copyWith(token, enabled: existingToken!.enabled)); } } } @@ -281,7 +283,7 @@ abstract class TronWalletBase Future startSync() async { try { syncStatus = AttemptingSyncStatus(); - + // Verify node health before attempting to sync final isHealthy = await checkNodeHealth(); if (!isHealthy) { @@ -359,6 +361,14 @@ abstract class TronWalletBase return pendingTransaction; } + @override + Future updateTransactionsHistory() async { + await Future.wait([ + fetchTransactions(), + fetchTrc20ExcludedTransactions(), + ]); + } + @override Future> fetchTransactions() async { final address = _tronAddress; @@ -522,11 +532,11 @@ abstract class TronWalletBase try { // Check native balance await _client.getBalance(_tronPublicKey.toAddress(), throwOnError: true); - + // Check USDT token balance const usdtContractAddress = "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t"; await _client.fetchTronTokenBalances(_tronAddress, usdtContractAddress, throwOnError: true); - + return true; } catch (e) { return false; diff --git a/lib/view_model/send/send_view_model.dart b/lib/view_model/send/send_view_model.dart index 4ba87f97c..83480cfed 100644 --- a/lib/view_model/send/send_view_model.dart +++ b/lib/view_model/send/send_view_model.dart @@ -139,8 +139,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor bool get isMwebEnabled => balanceViewModel.mwebEnabled; - bool get isEVMWallet => walletType == WalletType.ethereum || walletType == WalletType.polygon || - walletType == WalletType.base; + bool get isEVMWallet => isEVMCompatibleChain(walletType); @action void setShowAddressBookPopup(bool value) { @@ -514,7 +513,6 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor }); } - // Swaps.xyz (EVM) path if (isEVMWallet && trade != null && provider is SwapsXyzExchangeProvider) { @@ -524,7 +522,6 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor BigInt.tryParse((trade.routerValue ?? '0').toString()) ?? BigInt.zero; if (routerTo?.isNotEmpty == true && routerData?.isNotEmpty == true) { - // detect prepared ERC-20 transfer(...) (alt-vm deposit pattern) String _selector(String s) => (s.startsWith('0x') && s.length >= 10) ? s.substring(0, 10) : ''; @@ -538,11 +535,13 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor // Optionally prebuild approval (SKIP for prepared transfer) final tokenContract = trade.sourceTokenAddress ?? ''; final requiredAmount = BigInt.tryParse( - (trade.sourceTokenAmountRaw ?? '0').replaceAll('n', ''), - ) ?? BigInt.zero; + (trade.sourceTokenAmountRaw ?? '0').replaceAll('n', ''), + ) ?? + BigInt.zero; // Only do approval when NOT a prepared transfer, and only if the API hinted we might need it - final requiresTokenApproval = (trade.requiresTokenApproval ?? false) && !_isPreparedTransfer; + final requiresTokenApproval = + (trade.requiresTokenApproval ?? false) && !_isPreparedTransfer; if (requiresTokenApproval && tokenContract.isNotEmpty && requiredAmount > BigInt.zero) { if (walletType == WalletType.ethereum) { @@ -656,7 +655,6 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor } } - // Regular flow pendingTransaction = await wallet.createTransaction(_credentials(provider)); @@ -772,21 +770,6 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor return; // skip the regular flow below } - // Regular flow (non-Swaps) - if (pendingTransaction!.shouldCommitUR()) { - final urstr = await pendingTransaction!.commitUR(); - final result = await Navigator.of(context).pushNamed( - Routes.urqrAnimatedPage, - arguments: urstr, - ); - if (result == null) { - state = FailureState("Canceled by user"); - return; - } - } else { - await pendingTransaction!.commit(); - } - String address = outputs.fold('', (acc, value) { return value.isParsedAddress ? '$acc${value.address}\n${value.extractedAddress}\n\n' @@ -818,6 +801,17 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor nano!.updateTransactions(wallet); } + // Immediate transaction update for EVM chains, Solana, and Tron + if (isEVMWallet || walletType == WalletType.solana || walletType == WalletType.tron) { + Future.delayed(Duration(seconds: 4), () async { + try { + await wallet.updateTransactionsHistory(); + } catch (e) { + printV('Failed to update transactions after send: $e'); + } + }); + } + if (pendingTransaction!.id.isNotEmpty) { TransactionInfo? tx; if (walletType == WalletType.monero) { @@ -1124,7 +1118,6 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor required BigInt requiredAmount, int? sourceTokenDecimals, }) async { - // Only EVM chains support ERC20 approvals if (!isEVMWallet) return null; @@ -1138,15 +1131,24 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor bool needsApproval = false; if (walletType == WalletType.ethereum) { needsApproval = await ethereum!.isApprovalRequired( - wallet, tokenContract, spender, requiredAmount, + wallet, + tokenContract, + spender, + requiredAmount, ); } else if (walletType == WalletType.polygon) { needsApproval = await polygon!.isApprovalRequired( - wallet, tokenContract, spender, requiredAmount, + wallet, + tokenContract, + spender, + requiredAmount, ); } else if (walletType == WalletType.base) { needsApproval = await base!.isApprovalRequired( - wallet, tokenContract, spender, requiredAmount, + wallet, + tokenContract, + spender, + requiredAmount, ); } @@ -1154,29 +1156,41 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor final erc20Token = wallet.balance.keys.whereType().firstWhere( (t) => t.contractAddress.toLowerCase() == tokenLc, - orElse: () => Erc20Token( - name: '', - symbol: '', - contractAddress: tokenContract, - decimal: sourceTokenDecimals ?? 18, - enabled: true, - ), - ); + orElse: () => Erc20Token( + name: '', + symbol: '', + contractAddress: tokenContract, + decimal: sourceTokenDecimals ?? 18, + enabled: true, + ), + ); if (walletType == WalletType.ethereum) { final priority = _settingsStore.priority[WalletType.ethereum]!; return await ethereum!.createTokenApproval( - wallet, requiredAmount, spender, erc20Token, priority, + wallet, + requiredAmount, + spender, + erc20Token, + priority, ); } else if (walletType == WalletType.polygon) { final priority = _settingsStore.priority[WalletType.polygon]!; return await polygon!.createTokenApproval( - wallet, requiredAmount, spender, erc20Token, priority, + wallet, + requiredAmount, + spender, + erc20Token, + priority, ); } else if (walletType == WalletType.base) { final priority = _settingsStore.priority[WalletType.base]!; return await base!.createTokenApproval( - wallet, requiredAmount, spender, erc20Token, priority, + wallet, + requiredAmount, + spender, + erc20Token, + priority, ); }