Cw 1294 fix xo swaps bug (#2645)
What changed, and why it matters
This commit fixes how the Cake Wallet app figures out which cryptocurrency a user is sending or receiving during swaps. Before the fix, the app could confuse coins that share the same name but live on different blockchains (for example, USD Coin on Ethereum versus USD Coin on Polygon). That confusion could lead to the app picking the wrong wallet balance, building an incorrect transaction, or showing the user the wrong asset. The fix adds 'tag' support so the app can tell these similar coins apart, and it also improves error handling when a coin isn't enabled in the wallet.
Treat this as a functional bug fix with possible security side effects. Review whether any prior swap could have been constructed with mismatched currency tags, and consider adding automated tests that exercise multi-network tickers (e.g., USDC on ETH vs POL) across all exchange providers. Verify that the new orElse exceptions are surfaced to users clearly and do not crash silently.
Security signals we found
Currency confusion / wrong-asset selection when same ticker exists on multiple networks
Balance lookup by title only could select wrong token balance
Missing orElse on firstWhere could throw NoSuchMethodError / StateError instead of actionable message
Exchange trade metadata (userCurrencyFromRaw / userCurrencyToRaw) now includes network tag
XOSwap special-cases BASE and USDT default tags
Evidence from the diff
The patch updates CryptoCurrency.safeParseCurrencyFromString to accept an optional network/tag parameter and disambiguates currencies by both title and tag. It then updates multiple exchange providers (ChangeNow, Exolix, LetsExchange, StealthEx, Trocador, XOSwap) to parse from/to currencies using the network field as a tag. Wallet code for EVM, Solana, and Tron chains now matches both currency.title and currency.tag when selecting a balance, and throws a clear exception if the currency is not enabled. XOSwap gets additional normalization logic for asset strings and special-case handling for USDT and BASE.
Changed components
cw_core/lib/crypto_currency.dartcw_evm/lib/evm_chain_wallet.dartcw_solana/lib/solana_wallet.dartcw_tron/lib/tron_wallet.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.dartlib/exchange/provider/xoswap_exchange_provider.dartInspect captured patch +218 / −54
diff --git a/cw_core/lib/crypto_currency.dart b/cw_core/lib/crypto_currency.dart
index 0857c00..952d7c0 100644
--- a/cw_core/lib/crypto_currency.dart
+++ b/cw_core/lib/crypto_currency.dart
@@ -1,5 +1,6 @@
import 'package:cw_core/currency.dart';
import 'package:cw_core/enumerable_item.dart';
+import 'package:collection/collection.dart';
class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implements Currency {
const CryptoCurrency({
@@ -336,9 +337,23 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
return CryptoCurrency._fullNameCurrencyMap[name.split("(").first.trim().toLowerCase()]!;
}
- static CryptoCurrency? safeParseCurrencyFromString(String? raw, {CryptoCurrency? walletCurrency}) {
+ static CryptoCurrency? safeParseCurrencyFromString(
+ String? raw, {
+ String? tag,
+ CryptoCurrency? walletCurrency,
+ }) {
if (raw == null || raw.isEmpty) return null;
+ if (tag != null && tag.isNotEmpty) {
+ final match = CryptoCurrency.all.firstWhereOrNull(
+ (e) =>
+ e.title.toUpperCase() == raw.toUpperCase() &&
+ e.tag?.toUpperCase() == tag.toUpperCase(),
+ );
+ if (match != null) return match;
+ return null;
+ }
+
try {
return CryptoCurrency.fromString(raw, walletCurrency: walletCurrency);
} catch (_) {}
diff --git a/cw_evm/lib/evm_chain_wallet.dart b/cw_evm/lib/evm_chain_wallet.dart
index 0db8781..b03b180 100644
--- a/cw_evm/lib/evm_chain_wallet.dart
+++ b/cw_evm/lib/evm_chain_wallet.dart
@@ -476,8 +476,12 @@ abstract class EVMChainWalletBase
'0x${opReturnMemo.codeUnits.map((char) => char.toRadixString(16).padLeft(2, '0')).join()}';
}
- final CryptoCurrency transactionCurrency =
- balance.keys.firstWhere((element) => element.title == _credentials.currency.title);
+ final transactionCurrency = balance.keys.firstWhere(
+ (currency) =>
+ currency.title == _credentials.currency.title &&
+ currency.tag == _credentials.currency.tag,
+ orElse: () => throw Exception(
+ 'Currency ${_credentials.currency.title} ${_credentials.currency.tag} is not accessible in the wallet, try to enable it first.'));
final currencyBalance = balance[transactionCurrency]!;
BigInt totalAmount = BigInt.zero;
diff --git a/cw_solana/lib/solana_wallet.dart b/cw_solana/lib/solana_wallet.dart
index 49af6de..8bb094e 100644
--- a/cw_solana/lib/solana_wallet.dart
+++ b/cw_solana/lib/solana_wallet.dart
@@ -226,8 +226,12 @@ abstract class SolanaWalletBase
await _updateBalance();
- final CryptoCurrency transactionCurrency =
- balance.keys.firstWhere((element) => element.title == solCredentials.currency.title);
+ final transactionCurrency = balance.keys.firstWhere(
+ (currency) =>
+ currency.title == credentials.currency.title &&
+ currency.tag == credentials.currency.tag,
+ orElse: () => throw Exception(
+ 'Currency ${credentials.currency.title} ${credentials.currency.tag} is not accessible in the wallet, try to enable it first.'));
final walletBalanceForCurrency = balance[transactionCurrency]!.balance;
diff --git a/cw_tron/lib/tron_wallet.dart b/cw_tron/lib/tron_wallet.dart
index 80afb2f..393c8ff 100644
--- a/cw_tron/lib/tron_wallet.dart
+++ b/cw_tron/lib/tron_wallet.dart
@@ -312,8 +312,13 @@ abstract class TronWalletBase
final hasMultiDestination = outputs.length > 1;
- final CryptoCurrency transactionCurrency =
- balance.keys.firstWhere((element) => element.title == tronCredentials.currency.title);
+ final transactionCurrency = balance.keys.firstWhere(
+ (currency) =>
+ currency.title == tronCredentials.currency.title &&
+ currency.tag == tronCredentials.currency.tag,
+ orElse: () => throw Exception(
+ 'Currency ${tronCredentials.currency.title} ${tronCredentials.currency.tag} is not accessible in the wallet, try to enable it first.'));
+
final walletBalanceForCurrency = balance[transactionCurrency]!.balance;
diff --git a/lib/exchange/provider/changenow_exchange_provider.dart b/lib/exchange/provider/changenow_exchange_provider.dart
index 8f6e7bc..fef639b 100644
--- a/lib/exchange/provider/changenow_exchange_provider.dart
+++ b/lib/exchange/provider/changenow_exchange_provider.dart
@@ -278,12 +278,21 @@ class ChangeNowExchangeProvider extends ExchangeProvider {
throw Exception('Unexpected http status: ${response.statusCode}');
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
+
+ // Parsing 'from' currency
final fromCurrency = responseJSON['fromCurrency'] as String;
final fromNetwork = responseJSON['fromNetwork'] as String?;
- final from = CryptoCurrency.safeParseCurrencyFromString(fromCurrency);
+ final _normalizedFromNetwork = _normalizeNetworkType(fromNetwork ?? '');
+ final fromTag = fromCurrency == _normalizedFromNetwork ? 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 to = CryptoCurrency.safeParseCurrencyFromString(toCurrency);
+ final _normalizedToNetwork = _normalizeNetworkType(toNetwork ?? '');
+ final toTag = toCurrency == _normalizedToNetwork ? null : _normalizedToNetwork;
+ final to = CryptoCurrency.safeParseCurrencyFromString(toCurrency, tag: toTag);
+
final inputAddress = responseJSON['payinAddress'] as String;
final expectedSendAmount = responseJSON['expectedAmountFrom'].toString();
final status = responseJSON['status'] as String;
@@ -294,9 +303,6 @@ class ChangeNowExchangeProvider extends ExchangeProvider {
final payoutAddress = responseJSON['payoutAddress'] as String;
final expiredAt = DateTime.tryParse(expiredAtRaw ?? '')?.toLocal();
- final _normalizedFromNetwork = _normalizeNetworkType(fromNetwork ?? '');
- final _normalizedToNetwork = _normalizeNetworkType(toNetwork ?? '');
-
return Trade(
id: id,
from: from,
@@ -309,8 +315,8 @@ class ChangeNowExchangeProvider extends ExchangeProvider {
expiredAt: expiredAt,
outputTransaction: outputTransaction,
payoutAddress: payoutAddress,
- userCurrencyFromRaw: '${fromCurrency.toUpperCase()}' + '_' + '${_normalizedFromNetwork.toUpperCase()}',
- userCurrencyToRaw: '${toCurrency.toUpperCase()}' + '_' + '${_normalizedToNetwork.toUpperCase()}',
+ userCurrencyFromRaw: '${fromCurrency.toUpperCase()}' + '_' + '${fromTag?.toUpperCase() ?? ''}',
+ userCurrencyToRaw: '${toCurrency.toUpperCase()}' + '_' + '${toTag?.toUpperCase() ?? ''}',
);
}
diff --git a/lib/exchange/provider/exolix_exchange_provider.dart b/lib/exchange/provider/exolix_exchange_provider.dart
index 252222b..645426d 100644
--- a/lib/exchange/provider/exolix_exchange_provider.dart
+++ b/lib/exchange/provider/exolix_exchange_provider.dart
@@ -360,10 +360,19 @@ class ExolixExchangeProvider extends ExchangeProvider {
throw Exception('Unexpected http status: ${response.statusCode}');
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
+
+ // Parsing 'from' currency
final coinFrom = responseJSON['coinFrom']['coinCode'] as String;
final coinFromNetwork = responseJSON['coinFrom']['network'] as String?;
+ final fromTag = coinFrom == coinFromNetwork ? 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 to = CryptoCurrency.safeParseCurrencyFromString(coinTo, tag: toTag);
+
final inputAddress = responseJSON['depositAddress'] as String;
final amount = responseJSON['amount'].toString();
final status = responseJSON['status'] as String;
@@ -373,8 +382,8 @@ class ExolixExchangeProvider extends ExchangeProvider {
return Trade(
id: id,
- from: CryptoCurrency.safeParseCurrencyFromString(coinFrom),
- to: CryptoCurrency.safeParseCurrencyFromString(coinTo),
+ from: from,
+ to: to,
provider: description,
inputAddress: inputAddress,
amount: amount,
@@ -382,8 +391,8 @@ class ExolixExchangeProvider extends ExchangeProvider {
extraId: extraId,
outputTransaction: outputTransaction,
payoutAddress: payoutAddress,
- userCurrencyFromRaw: '${coinFrom.toUpperCase()}' + '_' + '${coinFromNetwork ?? ''}',
- userCurrencyToRaw: '${coinTo.toUpperCase()}' + '_' + '${coinToNetwork ?? ''}',
+ userCurrencyFromRaw: '${coinFrom.toUpperCase()}' + '_' + '${fromTag?.toUpperCase() ?? ''}',
+ userCurrencyToRaw: '${coinTo.toUpperCase()}' + '_' + '${toTag?.toUpperCase() ?? ''}',
);
}
diff --git a/lib/exchange/provider/letsexchange_exchange_provider.dart b/lib/exchange/provider/letsexchange_exchange_provider.dart
index d37c6a2..fe9aebd 100644
--- a/lib/exchange/provider/letsexchange_exchange_provider.dart
+++ b/lib/exchange/provider/letsexchange_exchange_provider.dart
@@ -348,10 +348,22 @@ class LetsExchangeExchangeProvider extends ExchangeProvider {
throw Exception('LetsExchange fetch trade failed: ${response.body}');
}
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
- final from = responseJSON['coin_from'] as String;
+
+ // Parsing 'from' currency
+ final fromCurrency = responseJSON['coin_from'] as String;
final fromNetwork = responseJSON['coin_from_network'] as String?;
- final to = responseJSON['coin_to'] as String;
+ final normalizedFromNetwork = _normalizeNetworkType(fromNetwork ?? '');
+ final fromTag = fromCurrency == normalizedFromNetwork ? null : normalizedFromNetwork;
+ final from = CryptoCurrency.safeParseCurrencyFromString(fromCurrency, tag: fromTag);
+
+ // Parsing 'to' currency
+ final toCurrency = responseJSON['coin_to'] as String;
final toNetwork = responseJSON['coin_to_network'] as String?;
+ final normalizedToNetwork = _normalizeNetworkType(toNetwork ?? '');
+ final toTag = toCurrency == normalizedToNetwork ? null : normalizedToNetwork;
+ final to = CryptoCurrency.safeParseCurrencyFromString(toCurrency, tag: toTag);
+
+
final payoutAddress = responseJSON['withdrawal'] as String;
final depositAddress = responseJSON['deposit'] as String;
final refundAddress = responseJSON['return'] as String;
@@ -365,13 +377,10 @@ class LetsExchangeExchangeProvider extends ExchangeProvider {
final createdAt = DateTime.parse(createdAtString).toLocal();
final expiredAt = DateTime.fromMillisecondsSinceEpoch(expiredAtTimestamp * 1000).toLocal();
- final normalizedFromNetwork = _normalizeNetworkType(fromNetwork ?? '');
- final normalizedToNetwork = _normalizeNetworkType(toNetwork ?? '');
-
return Trade(
id: id,
- from: CryptoCurrency.safeParseCurrencyFromString(from),
- to: CryptoCurrency.safeParseCurrencyFromString(to),
+ from: from,
+ to: to,
provider: description,
inputAddress: depositAddress,
payoutAddress: payoutAddress,
@@ -383,8 +392,8 @@ class LetsExchangeExchangeProvider extends ExchangeProvider {
expiredAt: expiredAt,
isRefund: status == 'refund',
extraId: extraId,
- userCurrencyFromRaw: '$from' + '_' + normalizedFromNetwork,
- userCurrencyToRaw: '$to' + '_' + '$normalizedToNetwork',
+ userCurrencyFromRaw: '${fromCurrency.toUpperCase()}' + '_' + '${fromTag?.toUpperCase() ?? ''}',
+ userCurrencyToRaw: '${toCurrency.toUpperCase()}' + '_' + '${toTag?.toUpperCase() ?? ''}',
);
}
diff --git a/lib/exchange/provider/stealth_ex_exchange_provider.dart b/lib/exchange/provider/stealth_ex_exchange_provider.dart
index d23c099..b87903f 100644
--- a/lib/exchange/provider/stealth_ex_exchange_provider.dart
+++ b/lib/exchange/provider/stealth_ex_exchange_provider.dart
@@ -74,8 +74,8 @@ class StealthExExchangeProvider extends ExchangeProvider {
throw Exception('StealthEx fetch limits failed: ${response.body}');
}
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
- final min = toDouble(responseJSON['min_amount']);
- final max = responseJSON['max_amount'] as double?;
+ final min = _toDouble(responseJSON['min_amount']);
+ final max = _toDouble(responseJSON['max_amount']);
return Limits(min: min, max: max);
} catch (e) {
log(e.toString());
@@ -230,8 +230,8 @@ class StealthExExchangeProvider extends ExchangeProvider {
final payoutAddress = withdrawal['address'] as String;
final depositAddress = deposit['address'] as String;
final refundAddress = responseJSON['refund_address'] as String;
- final depositAmount = toDouble(deposit['amount']);
- final receiveAmount = toDouble(withdrawal['amount']);
+ final depositAmount = _toDouble(deposit['amount']);
+ final receiveAmount = _toDouble(withdrawal['amount']);
final status = responseJSON['status'] as String;
final createdAtString = responseJSON['created_at'] as String;
final extraId = deposit['extra_id'] as String?;
@@ -342,15 +342,24 @@ class StealthExExchangeProvider extends ExchangeProvider {
final withdrawal = responseJSON['withdrawal'] as Map<String, dynamic>;
final respId = responseJSON['id'] as String;
- final from = deposit['symbol'] as String;
+
+ // Parsing 'from' currency with network tag
+ final fromCurrency = deposit['symbol'] as String;
final fromNetwork = deposit['network'] as String?;
- final to = withdrawal['symbol'] as String;
+ final fromTag = fromNetwork == '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 to = CryptoCurrency.safeParseCurrencyFromString(toCurrency, tag: toTag);
+
final payoutAddress = withdrawal['address'] as String;
final depositAddress = deposit['address'] as String;
final refundAddress = responseJSON['refund_address'] as String;
- final depositAmount = toDouble(deposit['amount']);
- final receiveAmount = toDouble(withdrawal['amount']);
+ final depositAmount = _toDouble(deposit['amount']);
+ final receiveAmount = _toDouble(withdrawal['amount']);
final status = responseJSON['status'] as String;
final createdAtString = responseJSON['created_at'] as String;
final createdAt = DateTime.parse(createdAtString).toLocal();
@@ -358,8 +367,8 @@ class StealthExExchangeProvider extends ExchangeProvider {
return Trade(
id: respId,
- from: CryptoCurrency.safeParseCurrencyFromString(from),
- to: CryptoCurrency.safeParseCurrencyFromString(to),
+ from: from,
+ to: to,
provider: description,
inputAddress: depositAddress,
payoutAddress: payoutAddress,
@@ -370,8 +379,8 @@ class StealthExExchangeProvider extends ExchangeProvider {
createdAt: createdAt,
isRefund: status == 'refunded',
extraId: extraId,
- userCurrencyFromRaw: '${from.toUpperCase()}' + '_' + '${fromNetwork?.toUpperCase() ?? ''}',
- userCurrencyToRaw: '${to.toUpperCase()}' + '_' + '${toNetwork?.toUpperCase() ?? ''}',
+ userCurrencyFromRaw: '${fromCurrency.toUpperCase()}' + '_' + '${fromTag?.toUpperCase() ?? ''}',
+ userCurrencyToRaw: '${toCurrency.toUpperCase()}' + '_' + '${toTag?.toUpperCase() ?? ''}',
);
}
@@ -414,16 +423,18 @@ class StealthExExchangeProvider extends ExchangeProvider {
}
}
- double toDouble(dynamic value) {
+ static double? _toDouble(dynamic value) {
if (value is int) {
return value.toDouble();
} else if (value is double) {
return value;
- } else {
- return 0.0;
+ } else if (value is String) {
+ return double.tryParse(value);
}
+ return null;
}
+
String _getName(CryptoCurrency currency) {
if (currency == CryptoCurrency.usdcEPoly) return 'usdce';
return currency.title.toLowerCase();
diff --git a/lib/exchange/provider/trocador_exchange_provider.dart b/lib/exchange/provider/trocador_exchange_provider.dart
index 65dcfe0..fbb8687 100644
--- a/lib/exchange/provider/trocador_exchange_provider.dart
+++ b/lib/exchange/provider/trocador_exchange_provider.dart
@@ -420,15 +420,22 @@ class TrocadorExchangeProvider extends ExchangeProvider {
final providerName = responseJSON['provider'] as String;
final addressProviderMemo = responseJSON['address_provider_memo'] as String?;
- final from = responseJSON['ticker_from'] as String;
- final networkFrom = responseJSON['network_from'] as String?;
- final to = responseJSON['ticker_to'] as String;
+ final fromCurrency = responseJSON['ticker_from'] as String;
+ final fromNetwork = responseJSON['network_from'] as String?;
+ final _normalizedFromNetwork = _normalizeNetworkType(fromNetwork ?? '');
+ final fromTag = _normalizedFromNetwork.isEmpty || _normalizedFromNetwork == 'Mainnet' ? null : _normalizedFromNetwork;
+ final from = CryptoCurrency.safeParseCurrencyFromString(fromCurrency, tag: fromTag);
+
+ final toCurrency = responseJSON['ticker_to'] as String;
final networkTo = responseJSON['network_to'] as String?;
+ final _normalizedToNetwork = _normalizeNetworkType(networkTo ?? '');
+ final toTag = _normalizedToNetwork.isEmpty || _normalizedToNetwork == 'Mainnet' ? null : _normalizedToNetwork;
+ final to = CryptoCurrency.safeParseCurrencyFromString(toCurrency, tag: toTag);
return Trade(
id: id,
- from: CryptoCurrency.safeParseCurrencyFromString(from),
- to: CryptoCurrency.safeParseCurrencyFromString(to),
+ from: from,
+ to: to,
provider: description,
inputAddress: inputAddress,
refundAddress: refundAddress,
@@ -440,8 +447,8 @@ class TrocadorExchangeProvider extends ExchangeProvider {
providerId: providerId,
providerName: providerName,
extraId: addressProviderMemo,
- userCurrencyFromRaw: '${from.toUpperCase()}' + '_' + _normalizeNetworkType(networkFrom ?? ''),
- userCurrencyToRaw: '${to.toUpperCase()}' + '_' + _normalizeNetworkType(networkTo ?? ''), // Handle null network
+ userCurrencyFromRaw: '${fromCurrency.toUpperCase()}' + '_' + '${fromTag?.toUpperCase() ?? ''}',
+ userCurrencyToRaw: '${toCurrency.toUpperCase()}' + '_' + '${toTag?.toUpperCase() ?? ''}',
);
});
}
diff --git a/lib/exchange/provider/xoswap_exchange_provider.dart b/lib/exchange/provider/xoswap_exchange_provider.dart
index b9d4aed..35e0d91 100644
--- a/lib/exchange/provider/xoswap_exchange_provider.dart
+++ b/lib/exchange/provider/xoswap_exchange_provider.dart
@@ -43,8 +43,41 @@ class XOSwapExchangeProvider extends ExchangeProvider {
'LTC': 'litecoin',
'EOS': 'eosio',
'XLM': 'stellar',
+ 'BASE': 'basemainnet',
};
-
+
+ static const supportedTags = [
+ 'POL',
+ 'ETH',
+ 'BTC',
+ 'BSC',
+ 'SOL',
+ 'TRX',
+ 'ZEC',
+ 'ADA',
+ 'DOGE',
+ 'XMR',
+ 'BCH',
+ 'BSV',
+ 'XRP',
+ 'LTC',
+ 'EOS',
+ 'XLM',
+ 'BASE',
+ ];
+
+
+ String _normalizeXOSwapsNetwork(String string) {
+ final lower = string.toLowerCase();
+
+ if (lower.endsWith('matic0a883d9b')) return string.replaceFirst(RegExp(r'matic0a883d9b$', caseSensitive: false), 'POL');
+ if (lower.endsWith('matic86e249c1')) return string.replaceFirst(RegExp(r'matic86e249c1$', caseSensitive: false), 'POL');
+ if (lower.endsWith('bscddedf0f8')) return string.replaceFirst(RegExp(r'bscddedf0f8$', caseSensitive: false), 'BSC');
+ if (lower.endsWith('basemainnetb5a52617')) return string.replaceFirst(RegExp(r'basemainnetb5a52617$', caseSensitive: false), 'BASE');
+
+ return string;
+ }
+
@override
String get title => 'XOSwap';
@@ -422,9 +455,46 @@ class XOSwapExchangeProvider extends ExchangeProvider {
final pairId = responseJSON['pairId'] as String;
final pairParts = pairId.split('_');
final fromAsset = pairParts.isNotEmpty ? pairParts[0] : '';
+ final normalizedFromAsset = _normalizeXOSwapsNetwork(fromAsset);
+ String? fromAssetTag = _extractTagFromAsset(normalizedFromAsset);
+
+ String fromAssetBase = fromAssetTag != null
+ ? normalizedFromAsset.substring(0, normalizedFromAsset.length - fromAssetTag.length)
+ : normalizedFromAsset;
+
+ // Special case for USDT defaulting to ETH tag
+ if (fromAssetBase == 'USDT' && fromAssetTag == null) {
+ fromAssetTag = 'ETH';
+ }
+
+ // Special case for BASE defaulting to BASE tag
+ if (fromAssetBase == 'BASE' && fromAssetTag == null) {
+ fromAssetTag = 'BASE';
+ fromAssetBase = 'ETH';
+ }
+
final toAsset = pairParts.length > 1 ? pairParts[1] : '';
- final fromCurrency = CryptoCurrency.safeParseCurrencyFromString(fromAsset);
- final toCurrency = CryptoCurrency.safeParseCurrencyFromString(toAsset);
+ final normalizedToAsset = _normalizeXOSwapsNetwork(toAsset);
+ String? toAssetTag = _extractTagFromAsset(normalizedToAsset);
+
+ String toAssetBase = toAssetTag != null
+ ? normalizedToAsset.substring(0, normalizedToAsset.length - toAssetTag.length)
+ : normalizedToAsset;
+
+ // Special case for USDT defaulting to ETH tag
+ if (toAssetBase == 'USDT' && toAssetTag == null) {
+ toAssetTag = 'ETH';
+ }
+
+ // Special case for BASE defaulting to BASE tag
+ if (toAssetBase == 'BASE' && toAssetTag == null) {
+ toAssetTag = 'ETH';
+ toAssetBase = 'BASE';
+ }
+
+ final fromCurrency = CryptoCurrency.safeParseCurrencyFromString(fromAssetBase,tag: fromAssetTag);
+ final toCurrency = CryptoCurrency.safeParseCurrencyFromString(toAssetBase,tag: toAssetTag);
+
final amount = responseJSON['amount'] as Map<String, dynamic>;
final toAmount = responseJSON['toAmount'] as Map<String, dynamic>;
@@ -439,6 +509,14 @@ class XOSwapExchangeProvider extends ExchangeProvider {
final createdAt = DateTime.parse(createdAtString).toLocal();
final extraId = responseJSON['payInAddressTag'] as String?;
+ final userCurrencyFromRaw = fromCurrency != null
+ ? '${fromCurrency.title}' + '_' + '${fromCurrency.tag ?? ''}'
+ : '${fromAssetBase}' + '_' + '${fromAssetTag ?? ''}';
+
+ final userCurrencyToRaw = toCurrency != null
+ ? '${toCurrency.title}' + '_' + '${toCurrency.tag ?? ''}'
+ : '${toAssetBase}' + '_' + '${toAssetTag ?? ''}';
+
return Trade(
id: orderId,
from: fromCurrency,
@@ -452,8 +530,8 @@ class XOSwapExchangeProvider extends ExchangeProvider {
receiveAmount: receiveAmount,
payoutAddress: payoutAddress,
extraId: extraId,
- userCurrencyFromRaw: '$fromAsset' + '_',
- userCurrencyToRaw: '$toAsset' + '_',
+ userCurrencyFromRaw: userCurrencyFromRaw,
+ userCurrencyToRaw: userCurrencyToRaw,
);
} catch (e) {
printV(e.toString());
@@ -461,6 +539,22 @@ class XOSwapExchangeProvider extends ExchangeProvider {
}
}
+ // ensure something remains before tag (at least 2 chars)
+ String? _extractTagFromAsset(String asset) {
+
+
+
+ for (final tag in supportedTags) {
+ if (asset.endsWith(tag)) {
+ final prefixLength = asset.length - tag.length;
+ if (prefixLength >= 2) {
+ return tag;
+ }
+ }
+ }
+ return null;
+ }
+
double _toDouble(dynamic value) {
if (value is int) {
return value.toDouble();
Why this scored 58/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.