diff --git a/cw_core/lib/crypto_currency.dart b/cw_core/lib/crypto_currency.dart index 0857c0072..952d7c08a 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 with Serializable implements Currency { const CryptoCurrency({ @@ -336,9 +337,23 @@ class CryptoCurrency extends EnumerableItem with Serializable 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 0db8781c7..b03b180df 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 49af6deca..8bb094ea7 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 80afb2f8e..393c8ff97 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 8f6e7bc7c..fef639b42 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; + + // 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 252222b31..645426de0 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; + + // 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 d37c6a207..fe9aebd12 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; - 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 d23c0999c..b87903fe7 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; - 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; 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 65dcfe0a7..fbb86872e 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 b9d4aed13..35e0d9184 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; final toAmount = responseJSON['toAmount'] as Map; @@ -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();