CW-1260: Enable Swap all for EVM wallets and persist amount when currency changes (#2828)

* feat: Enable Swap all for EVM wallets and persist amount when currency changes * Update local * ffeat: Recalculate amounts when swapping all and currency changes * revert formatting * fix: Review comments * refactor: streamline currency change logic * refactor: simplify currency change handling * fix: handle amount and gas estimation for swap xyz trades for swap all * Revert "fix: handle amount and gas estimation for swap xyz trades for swap all" This reverts commit 408c9dbeffdc82e08e9349d0f14b229031060c62. * feat: exclude specific providers for swap all --------- Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>

David Adegoke committed Apr 30, 2026 at 19:17 UTC 2651260fe4a7ba054de9a27336920a5e8570c4ae
3 files changed +138 -23
lib/new-ui/pages/swap_page.dart
-2
@@ -401,8 +401,6 @@ class _NewSwapPageState extends State<NewSwapPage> {
401 address: isCurrentTypeWallet
402 ? exchangeViewModel.wallet.walletAddresses.addressForExchange
403 : '');
404 -
405 - key.currentState!.changeAmount(amount: '');
404 }
405
406 void _onWalletNameChange(ExchangeViewModel exchangeViewModel, CryptoCurrency currency,
lib/src/screens/exchange/exchange_page.dart
-2
@@ -636,8 +636,6 @@ class ExchangePage extends BasePage {
636 key.currentState!.changeAddress(
637 address:
638 isCurrentTypeWallet ? exchangeViewModel.wallet.walletAddresses.addressForExchange : '');
639 -
640 - key.currentState!.changeAmount(amount: '');
639 }
640
641 void _onWalletNameChange(ExchangeViewModel exchangeViewModel, CryptoCurrency currency,
lib/view_model/exchange/exchange_view_model.dart
+138 -19
@@ -48,6 +48,7 @@ import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
48 import 'package:cake_wallet/store/dashboard/trades_store.dart';
49 import 'package:cake_wallet/store/settings_store.dart';
50 import 'package:cake_wallet/store/templates/exchange_template_store.dart';
51 +import 'package:cake_wallet/evm/evm.dart';
52 import 'package:cake_wallet/reactions/wallet_connect.dart';
53 import 'package:cake_wallet/utils/feature_flag.dart';
54 import 'package:cake_wallet/utils/token_utilities.dart';
@@ -250,6 +251,20 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
251 }
252 return true;
253 });
254 + } else if (isEVMCompatibleChain(wallet.type)) {
255 + final currency = depositCurrency;
256 + final balanceCurrency = wallet.balance.keys.firstWhereOrNull(
257 + (c) =>
258 + c.title == depositCurrency.title &&
259 + (c.tag == depositCurrency.tag || c.tag == depositCurrency.title),
260 + );
261 + final balanceForCurrency =
262 + balanceCurrency != null ? wallet.balance[balanceCurrency] : null;
263 + if (depositCurrency == currency && balanceForCurrency != null) {
264 + depositAvailableAmount = _appStore.amountParsingProxy
265 + .getDisplayCryptoStringFromBigInt(
266 + balanceForCurrency.fullAvailableBalance, balanceCurrency!);
267 + }
268 } else {
269 final currency = depositCurrency;
270 final amount = _appStore.amountParsingProxy.getDisplayCryptoString(
@@ -478,9 +493,20 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
493
494 @computed
495 String? get balanceDisplay {
481 - final bal = wallet.balance[depositCurrency]?.fullAvailableBalance;
482 - if(bal == null) return null;
483 - return amountParsingProxy.getDisplayCryptoStringFromBigInt(bal, depositCurrency);
496 + CryptoCurrency? balanceCurrency;
497 + if (isEVMCompatibleChain(wallet.type)) {
498 + balanceCurrency = wallet.balance.keys.firstWhereOrNull(
499 + (c) =>
500 + c.title == depositCurrency.title &&
501 + (c.tag == depositCurrency.tag || c.tag == depositCurrency.title),
502 + );
503 + } else {
504 + balanceCurrency = depositCurrency;
505 + }
506 + final bal = balanceCurrency != null ? wallet.balance[balanceCurrency]?.fullAvailableBalance : null;
507 + if (bal == null) return null;
508 + return amountParsingProxy.getDisplayCryptoStringFromBigInt(
509 + bal, balanceCurrency ?? depositCurrency);
510 }
511
512 //* Still open to further optimize these checks
@@ -515,15 +541,23 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
541 return priority;
542 }
543
518 - bool get hasAllAmount =>
519 - [
520 - WalletType.monero,
521 - WalletType.bitcoin,
522 - WalletType.litecoin,
523 - WalletType.bitcoinCash,
524 - WalletType.dogecoin,
525 - ].contains(wallet.type) &&
526 - (depositCurrency == wallet.currency);
544 + bool get hasAllAmount {
545 + if ([
546 + WalletType.monero,
547 + WalletType.bitcoin,
548 + WalletType.litecoin,
549 + WalletType.bitcoinCash,
550 + WalletType.dogecoin,
551 + ].contains(wallet.type)) return (depositCurrency == wallet.currency);
552 +
553 + if (!isEVMCompatibleChain(wallet.type)) return false;
554 +
555 + if (depositCurrency == wallet.currency) return true;
556 +
557 + return wallet.balance.keys.any((c) =>
558 + c.title == depositCurrency.title &&
559 + (c.tag == depositCurrency.tag || c.tag == depositCurrency.title));
560 + }
561
562 bool get isMoneroWallet => wallet.type == WalletType.monero;
563
@@ -706,26 +740,38 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
740
741 @action
742 void changeDepositCurrency({required CryptoCurrency currency}) {
743 + final previousCurrency = depositCurrency;
744 + final wasSendAllEnabled = isSendAllEnabled;
745 +
746 depositCurrency = currency;
747 isFixedRateMode = false;
711 - _onPairChange();
748 isDepositAddressEnabled = !useSameWalletAddress(depositCurrency);
749 +
750 + if (previousCurrency != currency) {
751 + _onPairChange(clearBoth: wasSendAllEnabled);
752 + }
753 fetchFiatPrice(currency);
754 updateDepositAvailableAmount();
755 }
756
757 @action
758 void changeReceiveCurrency({required CryptoCurrency currency}) {
759 + final previousCurrency = receiveCurrency;
760 +
761 if (!(currency.tag == receiveCurrency.tag ||
762 currency.tag == receiveCurrency.title ||
763 currency.title == receiveCurrency.tag ||
764 currency == receiveCurrency)) {
765 receiveAddress = "";
766 }
767 +
768 receiveCurrency = currency;
769 isFixedRateMode = false;
727 - _onPairChange();
770 isDepositAddressEnabled = !useSameWalletAddress(depositCurrency);
771 +
772 + if (previousCurrency != currency) {
773 + _onPairChange();
774 + }
775 fetchFiatPrice(currency);
776 }
777
@@ -836,6 +882,11 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
882 isReceiveAmount: isFixedRateMode);
883 }
884
885 + bool _excludeProviderForSwapAll(ExchangeProvider provider) =>
886 + isSendAllEnabled &&
887 + (provider.description == ExchangeProviderDescription.swapsXyz ||
888 + provider.description == ExchangeProviderDescription.nearIntents);
889 +
890 Future<void> calculateBestRate() async {
891 if (depositCurrency == receiveCurrency) {
892 bestRate = 0.0;
@@ -846,6 +897,8 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
897 initialAmountByAssets(isFixedRateMode ? receiveCurrency : depositCurrency);
898
899 final validProvidersForAmount = _tradeAvailableProviders.where((provider) {
900 + if (_excludeProviderForSwapAll(provider)) return false;
901 +
902 final limits = _providerLimits[provider];
903
904 if (limits == null) return false;
@@ -962,7 +1015,6 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
1015
1016 if (lowestMin != double.maxFinite) {
1017 limits = Limits(min: lowestMin, max: highestMax);
965 -
1018 limitsState = LimitsLoadedSuccessfully(limits: limits);
1019 } else {
1020 limitsState = LimitsLoadedFailure(error: 'Limits loading failed');
@@ -1081,6 +1133,10 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
1133
1134 printV('createTrade: trying provider=${provider.title}');
1135
1136 + if (_excludeProviderForSwapAll(provider)) {
1137 + printV('Skipping Swaps.xyz or Near Intents for swap all');
1138 + continue;
1139 + }
1140 // should not happen but just as an extra check
1141 if (isFixedRateMode && provider.supportsFixedRate == false) {
1142 continue;
@@ -1254,6 +1310,56 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
1310 final amount = await unspentCoinsListViewModel.getSendingBalance(UnspentCoinType.any);
1311
1312 changeDepositAmount(amount: wallet.currency.formatAmount(BigInt.from(amount)), isCanonical: true);
1313 + } else if (isEVMCompatibleChain(wallet.type)) {
1314 + final balanceCurrency = wallet.balance.keys.firstWhereOrNull(
1315 + (currency) =>
1316 + currency.title == depositCurrency.title &&
1317 + (currency.tag == depositCurrency.tag || currency.tag == depositCurrency.title),
1318 + );
1319 +
1320 + final balanceForCurrency = balanceCurrency != null ? wallet.balance[balanceCurrency] : null;
1321 + if (balanceForCurrency == null) {
1322 + changeDepositAmount(amount: wallet.currency.formatAmount(BigInt.zero), isCanonical: true);
1323 + return;
1324 + }
1325 +
1326 + final balanceAmount = _appStore.amountParsingProxy.getDisplayCryptoStringFromBigInt(balanceForCurrency.fullAvailableBalance, balanceCurrency!);
1327 + final balanceDouble = double.tryParse(balanceAmount.replaceAll(',', '.')) ?? 0.0;
1328 + if (balanceDouble <= 0) {
1329 + changeDepositAmount(amount: wallet.currency.formatAmount(BigInt.zero), isCanonical: true);
1330 + return;
1331 + }
1332 +
1333 + final isNative = depositCurrency == wallet.currency;
1334 +
1335 + if (!isNative) {
1336 + changeDepositAmount(amount: balanceAmount, isCanonical: true);
1337 + return;
1338 + }
1339 +
1340 + try {
1341 + final priority = _settingsStore.getPriority(wallet.type, chainId: wallet.chainId);
1342 + await wallet.updateEstimatedFeesParams(priority);
1343 +
1344 + String? feeString;
1345 + if (isEVMCompatibleChain(wallet.type)) {
1346 + feeString = evm!.getEVMNativeEstimatedFee(wallet);
1347 + }
1348 +
1349 + if (feeString == null || feeString.isEmpty) {
1350 + changeDepositAmount(amount: balanceAmount, isCanonical: true);
1351 + return;
1352 + }
1353 +
1354 + final balanceWei = balanceForCurrency.fullAvailableBalance;
1355 + final feeWei = BigInt.parse(feeString);
1356 + final amountAfterFeeWei = balanceWei > feeWei ? balanceWei - feeWei : BigInt.zero;
1357 + changeDepositAmount(
1358 + amount: wallet.currency.formatAmount(amountAfterFeeWei), isCanonical: true);
1359 + } catch (e) {
1360 + printV('Error calculating send all for EVM: $e');
1361 + changeDepositAmount(amount: balanceAmount, isCanonical: true);
1362 + }
1363 }
1364 }
1365
@@ -1293,13 +1399,26 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
1399 void removeTemplate({required ExchangeTemplate template}) =>
1400 _exchangeTemplateStore.remove(template: template);
1401
1296 - void _onPairChange() {
1297 - _depositAmount = '';
1298 - _receiveAmount = '';
1402 + void _onPairChange({bool clearBoth = false}) {
1403 + if (clearBoth) {
1404 + _depositAmount = '';
1405 + _receiveAmount = '';
1406 + if (!hasAllAmount) isSendAllEnabled = false;
1407 + } else {
1408 + if (isFixedRateMode) {
1409 + _depositAmount = '';
1410 + } else {
1411 + _receiveAmount = '';
1412 + }
1413 + }
1414 bestRate = 0.0;
1415 bestRateProvider = null;
1416 _sortedAvailableProviders.clear();
1302 - loadLimits();
1417 + loadLimits().then((_) {
1418 + if (clearBoth && hasAllAmount) {
1419 + calculateDepositAllAmount();
1420 + }
1421 + });
1422 _setAvailableProviders();
1423 }
1424