feat: allow all for no-fixed monero swaps (#2353)

* feat: allow all for no-fixed monero swaps * fix: deposit amount calculation logic and remove unused print statements * feat: integrate UnspentCoinsListViewModel into ExchangeViewModel for more streamlined unspent coins management * fix: unpopulating the fiat field due to a race condition * fix: ReceiveAmount not updating on all if bestRate changes --------- Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>

Konstantin Ullrich committed Jul 9, 2025 at 16:08 UTC 0a3c51206c99557ddafec1b2b37ee819a6000858
4 files changed +58 -44
lib/di.dart
+1
@@ -521,6 +521,7 @@ Future<void> setup({
521 getIt.get<AppStore>().settingsStore,
522 getIt.get<SharedPreferences>(),
523 getIt.get<ContactListViewModel>(),
524 + getIt.get<UnspentCoinsListViewModel>(),
525 getIt.get<FeesViewModel>(),
526 ),
527 );
lib/src/screens/exchange/exchange_page.dart
+11 -8
@@ -6,7 +6,6 @@ import 'package:cake_wallet/di.dart';
6 import 'package:cake_wallet/src/screens/exchange/widgets/desktop_exchange_cards_section.dart';
7 import 'package:cake_wallet/src/screens/exchange/widgets/mobile_exchange_cards_section.dart';
8 import 'package:cake_wallet/src/widgets/add_template_button.dart';
9 -import 'package:cake_wallet/themes/core/material_base_theme.dart';
9 import 'package:cake_wallet/utils/debounce.dart';
10 import 'package:cake_wallet/utils/payment_request.dart';
11 import 'package:cake_wallet/utils/responsive_layout_util.dart';
@@ -417,12 +416,12 @@ class ExchangePage extends BasePage {
416
417 reaction(
418 (_) => exchangeViewModel.wallet.name,
420 - (String _) =>
419 + (_) =>
420 _onWalletNameChange(exchangeViewModel, exchangeViewModel.receiveCurrency, receiveKey));
421
422 reaction(
423 (_) => exchangeViewModel.wallet.name,
425 - (String _) =>
424 + (_) =>
425 _onWalletNameChange(exchangeViewModel, exchangeViewModel.depositCurrency, depositKey));
426
427 reaction((_) => exchangeViewModel.receiveCurrency,
@@ -432,8 +431,10 @@ class ExchangePage extends BasePage {
431 (CryptoCurrency currency) => _onCurrencyChange(currency, exchangeViewModel, depositKey));
432
433 reaction((_) => exchangeViewModel.depositAmount, (String amount) {
435 - if (depositKey.currentState!.amountController.text != amount && amount != S.of(context).all) {
436 - depositKey.currentState!.amountController.text = amount;
434 + if (exchangeViewModel.isSendAllEnabled) {
435 + depositAmountController.text = S.of(context).all;
436 + } else if (depositAmountController.text != amount && amount != S.of(context).all) {
437 + depositAmountController.text = amount;
438 }
439 });
440
@@ -521,7 +522,10 @@ class ExchangePage extends BasePage {
522 if (exchangeViewModel.isFixedRateMode) {
523 exchangeViewModel.changeReceiveAmount(amount: receiveAmountController.text);
524 } else {
524 - exchangeViewModel.changeDepositAmount(amount: depositAmountController.text);
525 + if (depositAmountController.text == S.current.all)
526 + exchangeViewModel.changeDepositAmount(amount: exchangeViewModel.depositAmount);
527 + else
528 + exchangeViewModel.changeDepositAmount(amount: depositAmountController.text);
529 }
530 });
531
@@ -642,8 +646,7 @@ class ExchangePage extends BasePage {
646 Future<String> fetchParsedAddress(
647 BuildContext context, String domain, CryptoCurrency currency) async {
648 final parsedAddress = await getIt.get<AddressResolver>().resolve(context, domain, currency);
645 - final address = await extractAddressFromParsed(context, parsedAddress);
646 - return address;
649 + return extractAddressFromParsed(context, parsedAddress);
650 }
651
652 void _showFeeAlert(BuildContext context) async {
lib/src/screens/send/widgets/send_card.dart
+1 -4
@@ -528,10 +528,7 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
528 });
529
530 reaction((_) => output.sendAll, (bool all) {
531 - if (all) {
532 - cryptoAmountController.text = S.current.all;
533 - fiatAmountController.text = '';
534 - }
531 + if (all) cryptoAmountController.text = S.current.all;
532 });
533
534 reaction((_) => sendViewModel.selectedCryptoCurrency, (Currency currency) async {
lib/view_model/exchange/exchange_view_model.dart
+45 -32
@@ -3,25 +3,9 @@ import 'dart:collection';
3 import 'dart:convert';
4
5 import 'package:bitcoin_base/bitcoin_base.dart';
6 -import 'package:cake_wallet/core/create_trade_result.dart';
7 -import 'package:cake_wallet/exchange/provider/chainflip_exchange_provider.dart';
8 -import 'package:cake_wallet/exchange/provider/stealth_ex_exchange_provider.dart';
9 -import 'package:cw_core/utils/proxy_wrapper.dart';
10 -import 'package:cake_wallet/view_model/send/fees_view_model.dart';
11 -import 'package:cake_wallet/exchange/provider/xoswap_exchange_provider.dart';
12 -import 'package:cw_core/crypto_currency.dart';
13 -import 'package:cw_core/sync_status.dart';
14 -import 'package:cw_core/transaction_priority.dart';
15 -import 'package:cw_core/unspent_coin_type.dart';
16 -import 'package:cw_core/utils/print_verbose.dart';
17 -import 'package:cw_core/wallet_type.dart';
18 -import 'package:hive/hive.dart';
19 -import 'package:intl/intl.dart';
20 -import 'package:mobx/mobx.dart';
21 -import 'package:shared_preferences/shared_preferences.dart';
22 -
6 import 'package:cake_wallet/.secrets.g.dart' as secrets;
7 import 'package:cake_wallet/bitcoin/bitcoin.dart';
8 +import 'package:cake_wallet/core/create_trade_result.dart';
9 import 'package:cake_wallet/core/wallet_change_listener_view_model.dart';
10 import 'package:cake_wallet/entities/exchange_api_mode.dart';
11 import 'package:cake_wallet/entities/preferences_key.dart';
@@ -31,13 +15,17 @@ import 'package:cake_wallet/exchange/exchange_template.dart';
15 import 'package:cake_wallet/exchange/exchange_trade_state.dart';
16 import 'package:cake_wallet/exchange/limits.dart';
17 import 'package:cake_wallet/exchange/limits_state.dart';
18 +import 'package:cake_wallet/exchange/provider/chainflip_exchange_provider.dart';
19 import 'package:cake_wallet/exchange/provider/changenow_exchange_provider.dart';
20 import 'package:cake_wallet/exchange/provider/exchange_provider.dart';
21 import 'package:cake_wallet/exchange/provider/exolix_exchange_provider.dart';
37 -import 'package:cake_wallet/exchange/provider/swaptrade_exchange_provider.dart';
22 +import 'package:cake_wallet/exchange/provider/letsexchange_exchange_provider.dart';
23 import 'package:cake_wallet/exchange/provider/sideshift_exchange_provider.dart';
24 +import 'package:cake_wallet/exchange/provider/stealth_ex_exchange_provider.dart';
25 +import 'package:cake_wallet/exchange/provider/swaptrade_exchange_provider.dart';
26 import 'package:cake_wallet/exchange/provider/thorchain_exchange.provider.dart';
27 import 'package:cake_wallet/exchange/provider/trocador_exchange_provider.dart';
28 +import 'package:cake_wallet/exchange/provider/xoswap_exchange_provider.dart';
29 import 'package:cake_wallet/exchange/trade.dart';
30 import 'package:cake_wallet/exchange/trade_request.dart';
31 import 'package:cake_wallet/generated/i18n.dart';
@@ -47,6 +35,19 @@ import 'package:cake_wallet/store/settings_store.dart';
35 import 'package:cake_wallet/store/templates/exchange_template_store.dart';
36 import 'package:cake_wallet/utils/feature_flag.dart';
37 import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart';
38 +import 'package:cake_wallet/view_model/send/fees_view_model.dart';
39 +import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_list_view_model.dart';
40 +import 'package:cw_core/crypto_currency.dart';
41 +import 'package:cw_core/sync_status.dart';
42 +import 'package:cw_core/transaction_priority.dart';
43 +import 'package:cw_core/unspent_coin_type.dart';
44 +import 'package:cw_core/utils/print_verbose.dart';
45 +import 'package:cw_core/utils/proxy_wrapper.dart';
46 +import 'package:cw_core/wallet_type.dart';
47 +import 'package:hive/hive.dart';
48 +import 'package:intl/intl.dart';
49 +import 'package:mobx/mobx.dart';
50 +import 'package:shared_preferences/shared_preferences.dart';
51
52 part 'exchange_view_model.g.dart';
53
@@ -67,6 +68,7 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
68 this._settingsStore,
69 this.sharedPreferences,
70 this.contactListViewModel,
71 + this.unspentCoinsListViewModel,
72 this.feesViewModel,
73 ) : _cryptoNumberFormat = NumberFormat(),
74 isSendAllEnabled = false,
@@ -100,6 +102,10 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
102 ];
103 _initialPairBasedOnWallet();
104
105 + unspentCoinsListViewModel.initialSetup().then((_) {
106 + unspentCoinsListViewModel.resetUnspentCoinsInfoSelections();
107 + });
108 +
109 final Map<String, dynamic> exchangeProvidersSelection =
110 json.decode(sharedPreferences.getString(PreferencesKey.exchangeProvidersSelection) ?? "{}")
111 as Map<String, dynamic>;
@@ -160,13 +166,10 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
166 }
167
168 bool get isElectrumWallet =>
163 - wallet.type == WalletType.bitcoin ||
164 - wallet.type == WalletType.litecoin ||
165 - wallet.type == WalletType.bitcoinCash;
169 + [WalletType.bitcoin, WalletType.litecoin, WalletType.bitcoinCash].contains(wallet.type);
170
171 bool get hideAddressAfterExchange =>
168 - wallet.type == WalletType.monero ||
169 - wallet.type == WalletType.wownero;
172 + [WalletType.monero, WalletType.wownero].contains(wallet.type);
173
174 bool _useTorOnly;
175 final Box<Trade> trades;
@@ -187,7 +190,6 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
190 useTorOnly: _useTorOnly, providerStates: _settingsStore.trocadorProviderStates),
191 ];
192
190 -
193 @observable
194 ExchangeProvider? provider;
195
@@ -306,9 +308,12 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
308 }
309
310 bool get hasAllAmount =>
309 - (wallet.type == WalletType.bitcoin ||
310 - wallet.type == WalletType.litecoin ||
311 - wallet.type == WalletType.bitcoinCash) &&
311 + [
312 + WalletType.monero,
313 + WalletType.bitcoin,
314 + WalletType.litecoin,
315 + WalletType.bitcoinCash,
316 + ].contains(wallet.type) &&
317 depositCurrency == wallet.currency;
318
319 bool get isMoneroWallet => wallet.type == WalletType.monero;
@@ -323,6 +328,8 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
328
329 final ContactListViewModel contactListViewModel;
330
331 + final UnspentCoinsListViewModel unspentCoinsListViewModel;
332 +
333 final FeesViewModel feesViewModel;
334
335 @observable
@@ -468,7 +475,7 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
475 if (depositCurrency == receiveCurrency) {
476 limitsState = LimitsLoadedSuccessfully(limits: Limits(min: 0, max: 0));
477 return;
471 - };
478 + }
479 if (selectedProviders.isEmpty) return;
480
481 limitsState = LimitsIsLoading();
@@ -661,9 +668,11 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
668
669 @action
670 Future<void> calculateDepositAllAmount() async {
664 - if (wallet.type == WalletType.litecoin ||
665 - wallet.type == WalletType.bitcoin ||
666 - wallet.type == WalletType.bitcoinCash) {
671 + if ([
672 + WalletType.litecoin,
673 + WalletType.bitcoin,
674 + WalletType.bitcoinCash,
675 + ].contains(wallet.type)) {
676 final priority = _settingsStore.priority[wallet.type]!;
677
678 final amount = await bitcoin!.estimateFakeSendAllTxAmount(
@@ -673,7 +682,11 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
682 wallet.type == WalletType.litecoin ? UnspentCoinType.nonMweb : UnspentCoinType.any,
683 );
684
676 - changeDepositAmount(amount: bitcoin!.formatterBitcoinAmountToString(amount: amount));
685 + changeDepositAmount(amount: wallet.formatCryptoAmount(amount.toString()));
686 + } else if (wallet.type == WalletType.monero) {
687 + final amount = await unspentCoinsListViewModel.getSendingBalance(UnspentCoinType.any);
688 +
689 + changeDepositAmount(amount: wallet.formatCryptoAmount(amount.toString()));
690 }
691 }
692