fix: stop resetting coin control on swap (#3433)
What changed, and why it matters
This commit fixes a bug where the user's coin-control choices were being reset when swapping between send screens. Coin control lets a user choose exactly which coins (UTXOs) are spent. Previously, the app called a reset function after initial setup, clearing any selections the user had made. The patch removes that reset and also makes the code that tracks coins compare them by their unique transaction hash and output index rather than by a looser object equality, which should make coin tracking more reliable.
Treat as a functional bug fix with minor security relevance. Review whether any other flows still unexpectedly reset coin-control selections, and verify that removing the reset does not leave stale selected UTXOs in contexts where the selection should be cleared (e.g., after a successful transaction or wallet switch).
Security signals we found
Behavior change in coin-control state management
Removal of automatic reset of UTXO selections
More precise UTXO identity matching by txid/vout
No explicit security framing by vendor
Evidence from the diff
In lib/view_model/send/send_view_model.dart the call to unspentCoinsListViewModel.resetUnspentCoinsInfoSelections() after initialSetup() is removed/commented out, preventing selected UTXOs from being cleared when the send view is initialized (e.g., during swap flows). In cw_bitcoin/lib/electrum_wallet.dart, addCoinInfo now matches existing coin info by walletId + hash + vout instead of relying on element == coin, which is a more precise identity check. The diff also removes several unused imports and a default case in a switch statement, which are cleanup changes.
Changed components
lib/view_model/send/send_view_model.dartcw_bitcoin/lib/electrum_wallet.dartBitcoin/Electrum wallet coin control / UTXO selectionSend/swap flow initializationInspect captured patch +8 / −13
diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart
index 3ebbfa49..641feb67 100644
--- a/cw_bitcoin/lib/electrum_wallet.dart
+++ b/cw_bitcoin/lib/electrum_wallet.dart
@@ -18,7 +18,6 @@ import 'package:cw_bitcoin/bitcoin_address_record.dart';
import 'package:cw_bitcoin/bitcoin_transaction_credentials.dart';
import 'package:cw_bitcoin/bitcoin_transaction_priority.dart';
import 'package:cw_bitcoin/bitcoin_unspent.dart';
-import 'package:cw_bitcoin/bitcoin_wallet.dart';
import 'package:cw_bitcoin/bitcoin_wallet_keys.dart';
import 'package:cw_bitcoin/electrum.dart' as electrum;
import 'package:cw_bitcoin/electrum_balance.dart';
@@ -27,25 +26,20 @@ import 'package:cw_bitcoin/electrum_transaction_history.dart';
import 'package:cw_bitcoin/electrum_transaction_info.dart';
import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
import 'package:cw_bitcoin/exceptions.dart';
-import 'package:cw_bitcoin/litecoin_wallet.dart';
import 'package:cw_bitcoin/pending_bitcoin_transaction.dart';
import 'package:cw_bitcoin/utils.dart';
import 'package:cw_core/amount/money.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/encryption_file_utils.dart';
import 'package:cw_core/get_height_by_date.dart';
-import 'package:cw_core/hardware/hardware_wallet_service.dart';
import 'package:cw_core/node.dart';
import 'package:cw_core/output_info.dart';
import 'package:cw_core/pending_transaction.dart';
-import 'package:cw_core/root_dir.dart';
import 'package:cw_core/sync_status.dart';
import 'package:cw_core/transaction_direction.dart';
import 'package:cw_core/transaction_priority.dart';
import 'package:cw_core/unspent_coin_type.dart';
import 'package:cw_core/unspent_coins_info.dart';
-import 'package:cw_core/utils/print_verbose.dart';
-import 'package:cw_core/utils/proxy_wrapper.dart';
import 'package:cw_core/utils/socket_health_logger.dart';
import 'package:cw_core/utils/tor/abstract.dart';
import 'package:cw_core/wallet_base.dart';
@@ -57,7 +51,6 @@ import 'package:hex/hex.dart';
import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart';
import 'package:rxdart/subjects.dart';
-import 'package:shared_preferences/shared_preferences.dart';
import 'package:sp_scanner/sp_scanner.dart';
part 'electrum_wallet.g.dart';
@@ -1984,8 +1977,12 @@ abstract class ElectrumWalletBase
@action
Future<void> addCoinInfo(BitcoinUnspent coin) async {
// Check if the coin is already in the unspentCoinsInfo for the wallet
- final existingCoinInfo = unspentCoinsInfo.values
- .firstWhereOrNull((element) => element.walletId == walletInfo.id && element == coin);
+ final existingCoinInfo = unspentCoinsInfo.values.firstWhereOrNull(
+ (element) =>
+ element.walletId == walletInfo.id &&
+ element.hash == coin.hash &&
+ element.vout == coin.vout,
+ );
if (existingCoinInfo == null) {
final newInfo = UnspentCoinsInfo(
@@ -3681,7 +3678,6 @@ abstract class ElectrumWalletBase
syncStatus = ConnectingSyncStatus();
}
break;
- default:
}
}
diff --git a/lib/view_model/send/send_view_model.dart b/lib/view_model/send/send_view_model.dart
index a652619b..ae0f56a6 100644
--- a/lib/view_model/send/send_view_model.dart
+++ b/lib/view_model/send/send_view_model.dart
@@ -3,7 +3,6 @@ import 'dart:async';
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/core/address_resolver/parsed_address.dart';
import 'package:cake_wallet/core/address_resolver/address_resolver_service.dart';
-import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/core/address_validator.dart';
import 'package:cake_wallet/core/amount_parsing_proxy.dart';
import 'package:cake_wallet/core/amount_validator.dart';
@@ -128,8 +127,8 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
outputs.add(Output(wallet, _appStore, _fiatConversationStore, _outputCryptoCurrencyHandler));
unspentCoinsListViewModel
- .initialSetup()
- .then((_) => unspentCoinsListViewModel.resetUnspentCoinsInfoSelections());
+ .initialSetup();
+ // .then((_) => unspentCoinsListViewModel.resetUnspentCoinsInfoSelections());
reaction((_) {
if (isEVMCompatibleChain(wallet.type)) {
Why this scored 26/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.