fix: dismissed transaction that finishes completing async (#3687)

can overwrite new one

GHSA-5p55-jf6f-c7wx
This commit is contained in:
cyan
2026-10-08 12:49:34 +02:00
committed by GitHub
parent 91738ea792
commit a5dc3a4add
+65 -5
View File
@@ -336,6 +336,12 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
@observable
PendingTransaction? pendingTransaction;
// Bumped when a preparation is dismissed or replaced.
//
// Finished call sets pendingTransaction only if it matches this value to
// its own value.
int _sendPreparationId = 0;
String? get pendingTransactionAdditionalCostNotice {
final additionalCost = pendingTransaction?.additionalCost;
@@ -596,6 +602,9 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
@action
Future<void> dismissTransaction() async {
_sendPreparationId++;
_ledgerTxStateTimer?.cancel();
_ledgerTxStateTimer = null;
state = InitialExecutionState();
if (ocpRequest != null) {
clearOutputs();
@@ -688,19 +697,36 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
Future<PendingTransaction?> createTransaction({ExchangeProvider? provider, Trade? trade}) async {
_currentTrade = trade;
_currentProvider = provider;
final preparationId = ++_sendPreparationId;
_ledgerTxStateTimer?.cancel();
_ledgerTxStateTimer = null;
pendingTransaction = null;
Timer? preparationTimer;
bool replaced() {
if (preparationId == _sendPreparationId) {
return false;
}
preparationTimer?.cancel();
return true;
}
try {
if (!(state is IsExecutingState)) state = IsExecutingState();
if (wallet.isHardwareWallet) {
if (walletType == WalletType.monero) {
_ledgerTxStateTimer = Timer.periodic(Duration(seconds: 1), (timer) {
preparationTimer = Timer.periodic(Duration(seconds: 1), (timer) {
if (preparationId != _sendPreparationId) {
timer.cancel();
return;
}
if (monero!.getLastLedgerCommand() == "INS_CLSAG") {
timer.cancel();
state = IsDeviceSigningResponseState();
}
});
_ledgerTxStateTimer = preparationTimer;
} else {
state = IsAwaitingDeviceResponseState();
}
@@ -728,7 +754,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
// Direct Transfer (Simple routing, no approval needed)
if (selector == transferSig) {
pendingTransaction = await evm!.createRawCallDataTransaction(
final prepared = await evm!.createRawCallDataTransaction(
wallet,
routerTo,
routerData,
@@ -738,6 +764,10 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
? _settingsStore.useBlinkProtection
: false,
);
if (replaced()) {
return null;
}
pendingTransaction = prepared;
state = ExecutedSuccessfullyState();
return pendingTransaction;
}
@@ -784,6 +814,9 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
requiredAmount: BigInt.zero, // Wait until it equals 0
waitForExactMatch: true,
);
if (replaced()) {
return null;
}
if (!resetConfirmed) {
state = FailureState('Failed to reset USDT allowance. Please try again.');
@@ -803,6 +836,9 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
sourceTokenDecimals: trade.sourceTokenDecimals,
priority: priority);
if (replaced()) {
return null;
}
if (approvalTx == null) {
state = FailureState('Failed to build approval transaction');
return null;
@@ -821,6 +857,9 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
spender: routerTo,
requiredAmount: requiredAmount,
);
if (replaced()) {
return null;
}
if (!isApproved) {
state = FailureState(
@@ -831,6 +870,9 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
'[Swaps.xyz sending flow] Approval transaction confirmed on-chain. Proceeding with swap execution.');
} catch (e, s) {
printV('[Swaps.xyz sending flow] Approval transaction error: $e\n$s');
if (replaced()) {
return null;
}
state = FailureState(translateErrorMessage(e, wallet.type, wallet.currency));
return null;
}
@@ -838,7 +880,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
// Construct Final Swap Transaction
printV('[Swaps.xyz sending flow] Building swap transaction');
pendingTransaction = await evm!.createRawCallDataTransaction(
final prepared = await evm!.createRawCallDataTransaction(
wallet,
routerTo,
routerData,
@@ -850,6 +892,10 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
? _settingsStore.useBlinkProtection
: false,
);
if (replaced()) {
return null;
}
pendingTransaction = prepared;
state = ExecutedSuccessfullyState();
return pendingTransaction;
@@ -859,6 +905,9 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
return null;
} catch (e, s) {
printV('Swaps.xyz transaction error: $e\n$s');
if (replaced()) {
return null;
}
state = FailureState(
'Failed to create Swaps.xyz transaction - ${translateErrorMessage(e, wallet.type, wallet.currency)}');
return null;
@@ -886,7 +935,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
strictParsing: false,
) ?? Money.zero(fromCurrency);
pendingTransaction = await solana!.signAndPrepareJupiterSwapTransaction(
final prepared = await solana!.signAndPrepareJupiterSwapTransaction(
wallet,
swapTransactionBase64!,
requestId!,
@@ -894,6 +943,10 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
amount,
Money.tryParse(fee.toString(), CryptoCurrency.sol) ?? Money.zero(CryptoCurrency.sol),
);
if (replaced()) {
return null;
}
pendingTransaction = prepared;
state = ExecutedSuccessfullyState();
return pendingTransaction;
@@ -916,7 +969,11 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
}
}
pendingTransaction = await wallet.createTransaction(_credentials(provider));
final prepared = await wallet.createTransaction(_credentials(provider));
if (replaced()) {
return null;
}
pendingTransaction = prepared;
final txAmountDouble = double.tryParse(pendingTransaction?.amountFormatted ?? '0') ?? 0.0;
final bool isTradeTx = trade != null && provider != null;
@@ -964,6 +1021,9 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
state = ExecutedSuccessfullyState();
return pendingTransaction;
} catch (e) {
if (replaced()) {
return null;
}
_ledgerTxStateTimer?.cancel();
// if (e is LedgerException) {
// final errorCode = e.errorCode.toRadixString(16);