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>
This commit is contained in:
David Adegoke
2025-10-25 13:34:29 +02:00
committed by GitHub
co-authored by Omar Hatem
parent 6388ae8af8
commit b0b053a19d
5 changed files with 80 additions and 44 deletions
+2 -1
View File
@@ -102,6 +102,7 @@ abstract class WalletBase<BalanceType extends Balance, HistoryType extends Trans
String get password;
Future<void>? updateBalance();
Future<void> updateTransactionsHistory() async {}
void setExceptionHandler(void Function(FlutterErrorDetails) onError) => null;
@@ -119,7 +120,7 @@ abstract class WalletBase<BalanceType extends Balance, HistoryType extends Trans
/// Returns true if the connection is alive, false otherwise.
/// Default implementation returns true (no-op for wallets without socket connections).
Future<bool> 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
+3
View File
@@ -894,6 +894,9 @@ abstract class EVMChainWalletBase
@override
Future<void>? updateBalance() async => await _updateBalance();
@override
Future<void> updateTransactionsHistory() async => await _updateTransactions();
List<Erc20Token> get erc20Currencies => evmChainErc20TokensBox.values.toList();
+8
View File
@@ -292,6 +292,14 @@ abstract class SolanaWalletBase
@override
Future<Map<String, SolanaTransactionInfo>> fetchTransactions() async => {};
@override
Future<void> updateTransactionsHistory() async {
await Future.wait([
_updateNativeSOLTransactions(),
_updateSPLTokenTransactions(),
]);
}
void updateTransactions(List<SolanaTransactionModel> updatedTx) {
_addTransactionsToTransactionHistory(updatedTx);
}
+15 -5
View File
@@ -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<void> 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<void> updateTransactionsHistory() async {
await Future.wait([
fetchTransactions(),
fetchTrc20ExcludedTransactions(),
]);
}
@override
Future<Map<String, TronTransactionInfo>> 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;
+52 -38
View File
@@ -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<Erc20Token>().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,
);
}