quick fixes for swap logic across providers (#2702)
What changed, and why it matters
This commit fixes how Cake Wallet matches currencies and blockchain networks when displaying swap/exchange transactions from several third-party exchange providers. Before the fix, mismatched capitalization or inconsistent network names (for example 'Arbitrum' vs 'ARB') could cause the app to fail to identify the correct currency or network. This is a bug-fix patch that reduces the chance of user confusion or incorrect transaction details, but it does not appear to be a direct exploit fix. The commit also adds support for the Arbitrum network.
Treat as a routine functional bug-fix with secondary security benefit. Review whether any prior swap transactions could have been misidentified due to these parsing issues, and consider adding unit tests for currency/network normalization across providers. No urgent security response is indicated by the diff alone.
Security signals we found
Currency/network parsing normalization reduces risk of mismatched swap asset identification
Case-insensitive comparison added for currency/network matching
New network mapping for Arbitrum added across multiple providers
No explicit security advisory, CVE, or exploit disclosure present in commit or references
Evidence from the diff
The patch normalizes currency and network identifier comparisons to be case-insensitive across ChangeNow, Exolix, LetsExchange, StealthEx, and Trocador exchange providers. It introduces or updates _normalizeNetworkType helpers, adds a fallback in CryptoCurrency.safeParseCurrencyFromString to match native currencies by title and tag, and adds Arbitrum (ARB) network mappings. The changes are defensive: they prevent parse failures and mismatched currency/network tags when provider APIs return mixed-case or differently named network strings.
Changed components
cw_core/lib/crypto_currency.dartlib/exchange/provider/changenow_exchange_provider.dartlib/exchange/provider/exolix_exchange_provider.dartlib/exchange/provider/letsexchange_exchange_provider.dartlib/exchange/provider/stealth_ex_exchange_provider.dartlib/exchange/provider/trocador_exchange_provider.dartInspect captured patch +50 / −7
diff --git a/cw_core/lib/crypto_currency.dart b/cw_core/lib/crypto_currency.dart
index 68066b12..d3f9faf2 100644
--- a/cw_core/lib/crypto_currency.dart
+++ b/cw_core/lib/crypto_currency.dart
@@ -357,6 +357,17 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
return null;
}
+
+ // Try for native currency with same title and tag
+ if (tag == null || tag.isEmpty) {
+ final match = CryptoCurrency.all.firstWhereOrNull(
+ (e) =>
+ e.title.toUpperCase() == raw.toUpperCase() && (e.tag == raw.toUpperCase()),
+ );
+
+ if (match != null) return match;
+ }
+
try {
return CryptoCurrency.fromString(raw, walletCurrency: walletCurrency);
} catch (_) {}
diff --git a/lib/exchange/provider/changenow_exchange_provider.dart b/lib/exchange/provider/changenow_exchange_provider.dart
index fef639b4..ca003b43 100644
--- a/lib/exchange/provider/changenow_exchange_provider.dart
+++ b/lib/exchange/provider/changenow_exchange_provider.dart
@@ -283,14 +283,14 @@ class ChangeNowExchangeProvider extends ExchangeProvider {
final fromCurrency = responseJSON['fromCurrency'] as String;
final fromNetwork = responseJSON['fromNetwork'] as String?;
final _normalizedFromNetwork = _normalizeNetworkType(fromNetwork ?? '');
- final fromTag = fromCurrency == _normalizedFromNetwork ? null : _normalizedFromNetwork;
+ final fromTag = fromCurrency.toUpperCase() == _normalizedFromNetwork.toUpperCase() ? null : _normalizedFromNetwork;
final from = CryptoCurrency.safeParseCurrencyFromString(fromCurrency, tag: fromTag);
// Parsing 'to' currency
final toCurrency = responseJSON['toCurrency'] as String;
final toNetwork = responseJSON['toNetwork'] as String?;
final _normalizedToNetwork = _normalizeNetworkType(toNetwork ?? '');
- final toTag = toCurrency == _normalizedToNetwork ? null : _normalizedToNetwork;
+ final toTag = toCurrency.toUpperCase() == _normalizedToNetwork.toUpperCase() ? null : _normalizedToNetwork;
final to = CryptoCurrency.safeParseCurrencyFromString(toCurrency, tag: toTag);
final inputAddress = responseJSON['payinAddress'] as String;
@@ -326,6 +326,8 @@ class ChangeNowExchangeProvider extends ExchangeProvider {
switch (currency) {
case CryptoCurrency.usdt:
return 'btc';
+ case CryptoCurrency.arb:
+ return 'arbitrum';
default:
return currency.tag != null ? _normalizeTag(currency.tag!) : currency.title.toLowerCase();
}
@@ -360,6 +362,7 @@ class ChangeNowExchangeProvider extends ExchangeProvider {
return switch (network.toUpperCase()) {
'POLY' => 'MATIC',
'AVAXC' => 'CCHAIN',
+ 'ARBITRUM' => 'ARB',
_ => network,
};
}
diff --git a/lib/exchange/provider/exolix_exchange_provider.dart b/lib/exchange/provider/exolix_exchange_provider.dart
index 645426de..09b30562 100644
--- a/lib/exchange/provider/exolix_exchange_provider.dart
+++ b/lib/exchange/provider/exolix_exchange_provider.dart
@@ -364,13 +364,15 @@ class ExolixExchangeProvider extends ExchangeProvider {
// Parsing 'from' currency
final coinFrom = responseJSON['coinFrom']['coinCode'] as String;
final coinFromNetwork = responseJSON['coinFrom']['network'] as String?;
- final fromTag = coinFrom == coinFromNetwork ? null : coinFromNetwork;
+ final _normalizedFromNetwork = _normalizeNetworkType(coinFromNetwork ?? '');
+ final fromTag = coinFrom.toUpperCase() == _normalizedFromNetwork.toUpperCase() ? null : coinFromNetwork;
final from = CryptoCurrency.safeParseCurrencyFromString(coinFrom, tag: fromTag);
// Parsing 'to' currency
final coinTo = responseJSON['coinTo']['coinCode'] as String;
final coinToNetwork = responseJSON['coinTo']['network'] as String?;
- final toTag = coinTo == coinToNetwork ? null : coinToNetwork;
+ final _normalizedToNetwork = _normalizeNetworkType(coinToNetwork ?? '');
+ final toTag = coinTo.toUpperCase() == _normalizedToNetwork.toUpperCase() ? null : coinToNetwork;
final to = CryptoCurrency.safeParseCurrencyFromString(coinTo, tag: toTag);
final inputAddress = responseJSON['depositAddress'] as String;
@@ -417,6 +419,13 @@ class ExolixExchangeProvider extends ExchangeProvider {
}
}
+ String _normalizeNetworkType(String network) {
+ return switch (network.toUpperCase()) {
+ 'ARBITRUM' => 'ARB',
+ _ => network,
+ };
+ }
+
String _normalizeCurrency(CryptoCurrency currency) {
switch (currency) {
case CryptoCurrency.nano:
diff --git a/lib/exchange/provider/letsexchange_exchange_provider.dart b/lib/exchange/provider/letsexchange_exchange_provider.dart
index fe9aebd1..d8ea8d6a 100644
--- a/lib/exchange/provider/letsexchange_exchange_provider.dart
+++ b/lib/exchange/provider/letsexchange_exchange_provider.dart
@@ -434,7 +434,8 @@ class LetsExchangeExchangeProvider extends ExchangeProvider {
return currency.tag!;
}
}
- return currency.title;
+
+ return _normalizeTitleToNetwork(currency.title);
}
String _normalizeNetworkType(String network) {
@@ -442,10 +443,18 @@ class LetsExchangeExchangeProvider extends ExchangeProvider {
'ERC20' => 'ETH',
'TRC20' => 'TRX',
'BEP20' => 'BSC',
+ 'ARBITRUM' => 'ARB',
_ => network,
};
}
+ String _normalizeTitleToNetwork(String title) {
+ return switch (title.toUpperCase()) {
+ 'ARB' => 'ARBITRUM',
+ _ => title,
+ };
+ }
+
String _normalizeBchAddress(String address) =>
address.startsWith('bitcoincash:') ? address.substring(12) : address;
}
diff --git a/lib/exchange/provider/stealth_ex_exchange_provider.dart b/lib/exchange/provider/stealth_ex_exchange_provider.dart
index b87903fe..cd639b6e 100644
--- a/lib/exchange/provider/stealth_ex_exchange_provider.dart
+++ b/lib/exchange/provider/stealth_ex_exchange_provider.dart
@@ -346,13 +346,15 @@ class StealthExExchangeProvider extends ExchangeProvider {
// Parsing 'from' currency with network tag
final fromCurrency = deposit['symbol'] as String;
final fromNetwork = deposit['network'] as String?;
- final fromTag = fromNetwork == 'mainnet' ? null : fromNetwork;
+ final _normalizedFromNetwork = _normalizeNetworkType(fromNetwork ?? '');
+ final fromTag = _normalizedFromNetwork == 'mainnet' ? null : fromNetwork;
final from = CryptoCurrency.safeParseCurrencyFromString(fromCurrency, tag: fromTag);
// Parsing 'to' currency with network tag
final toCurrency = withdrawal['symbol'] as String;
final toNetwork = withdrawal['network'] as String?;
- final toTag = toNetwork == 'mainnet' ? null : toNetwork;
+ final _normalizedToNetwork = _normalizeNetworkType(toNetwork ?? '');
+ final toTag = _normalizedToNetwork == 'mainnet' ? null : toNetwork;
final to = CryptoCurrency.safeParseCurrencyFromString(toCurrency, tag: toTag);
final payoutAddress = withdrawal['address'] as String;
@@ -440,7 +442,15 @@ class StealthExExchangeProvider extends ExchangeProvider {
return currency.title.toLowerCase();
}
+ String _normalizeNetworkType(String network) {
+ return switch (network.toUpperCase()) {
+ 'ARBITRUM' => 'mainnet',
+ _ => network,
+ };
+ }
+
String _getNetwork(CryptoCurrency currency) {
+ if (currency == CryptoCurrency.arb) return 'arbitrum';
if (currency.tag == null) return 'mainnet';
if (currency == CryptoCurrency.maticpoly) return 'mainnet';
diff --git a/lib/exchange/provider/trocador_exchange_provider.dart b/lib/exchange/provider/trocador_exchange_provider.dart
index d4d89a21..f1e0d2ab 100644
--- a/lib/exchange/provider/trocador_exchange_provider.dart
+++ b/lib/exchange/provider/trocador_exchange_provider.dart
@@ -523,6 +523,7 @@ class TrocadorExchangeProvider extends ExchangeProvider {
'TRC20' => 'TRX',
'BEP20' => 'BSC',
'LIGHTNING' => 'LN',
+ 'MATIC' => 'POL',
_ => network,
};
}
Why this scored 35/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.