diff --git a/cw_core/lib/crypto_currency.dart b/cw_core/lib/crypto_currency.dart index 2f90e2cb3..2b944f63a 100644 --- a/cw_core/lib/crypto_currency.dart +++ b/cw_core/lib/crypto_currency.dart @@ -287,6 +287,12 @@ class CryptoCurrency extends EnumerableItem with Serializable implemen return CryptoCurrency._rawCurrencyMap[raw]!; } + static CryptoCurrency? safeDeserialize({int? raw}) { + if (raw == null || raw < 0) return null; + return _rawCurrencyMap[raw]; + } + + // TODO: refactor this static CryptoCurrency fromString(String name, {CryptoCurrency? walletCurrency}) { diff --git a/cw_polygon/lib/polygon_wallet.dart b/cw_polygon/lib/polygon_wallet.dart index 991552068..8f6a0ad2e 100644 --- a/cw_polygon/lib/polygon_wallet.dart +++ b/cw_polygon/lib/polygon_wallet.dart @@ -78,7 +78,7 @@ class PolygonWallet extends EVMChainWallet { contractAddress: token.contractAddress, decimal: token.decimal, enabled: token.enabled, - tag: token.tag ?? "MATIC", + tag: token.tag ?? 'POL', iconPath: iconPath, isPotentialScam: token.isPotentialScam, ); diff --git a/integration_test/robots/transactions_page_robot.dart b/integration_test/robots/transactions_page_robot.dart index 5daccf8d4..e659626ea 100644 --- a/integration_test/robots/transactions_page_robot.dart +++ b/integration_test/robots/transactions_page_robot.dart @@ -385,18 +385,20 @@ class TransactionsPageRobot { Future _verifyTradeListItemDisplay(TradeListItem item) async { final keyId = 'trade_list_item_${item.trade.id}_key'; + final from = item.trade.from?.toString() ?? item.trade.userCurrencyFrom.toString(); + final to = item.trade.to?.toString() ?? item.trade.userCurrencyTo.toString(); //* ==============Confirm it has the right key for this item ======== commonTestCases.hasValueKey(keyId); //* ==============Confirm it displays the correct provider ========================= - final conversionFlow = '${item.trade.from.toString()} → ${item.trade.to.toString()}'; + final conversionFlow = '$from → $to'; commonTestCases.hasText(conversionFlow); //* ===========Confirm it displays the properly formatted amount with its crypto tag ======== - final amountCryptoText = item.tradeFormattedAmount + ' ' + item.trade.from.toString(); + final amountCryptoText = item.tradeFormattedAmount + ' ' + from; commonTestCases.hasText(amountCryptoText); diff --git a/lib/exchange/provider/chainflip_exchange_provider.dart b/lib/exchange/provider/chainflip_exchange_provider.dart index 4a1f9e664..5e9b85f29 100644 --- a/lib/exchange/provider/chainflip_exchange_provider.dart +++ b/lib/exchange/provider/chainflip_exchange_provider.dart @@ -69,6 +69,8 @@ class ChainflipExchangeProvider extends ExchangeProvider { {required CryptoCurrency from, required CryptoCurrency to, required bool isFixedRateMode}) async { + + try { final assetId = _normalizeCurrency(from); final assetsResponse = await _getAssets(); @@ -78,7 +80,13 @@ class ChainflipExchangeProvider extends ExchangeProvider { (asset) => asset['id'] == assetId, orElse: () => null)?['minimalAmountNative'] ?? '0'; + if (minAmount == '0') throw Exception('No rates found for $from to $to'); + return Limits(min: _amountFromNative(minAmount.toString(), from)); + } catch (e) { + printV(e.toString()); + throw Exception('Chainflip failed to fetch limits'); + } } @override diff --git a/lib/exchange/provider/changenow_exchange_provider.dart b/lib/exchange/provider/changenow_exchange_provider.dart index 42a1e4693..562f9e0c9 100644 --- a/lib/exchange/provider/changenow_exchange_provider.dart +++ b/lib/exchange/provider/changenow_exchange_provider.dart @@ -242,8 +242,10 @@ class ChangeNowExchangeProvider extends ExchangeProvider { final responseJSON = json.decode(response.body) as Map; final fromCurrency = responseJSON['fromCurrency'] as String; + final fromNetwork = responseJSON['fromNetwork'] as String?; final from = CryptoCurrency.safeParseCurrencyFromString(fromCurrency); final toCurrency = responseJSON['toCurrency'] as String; + final toNetwork = responseJSON['toNetwork'] as String?; final to = CryptoCurrency.safeParseCurrencyFromString(toCurrency); final inputAddress = responseJSON['payinAddress'] as String; final expectedSendAmount = responseJSON['expectedAmountFrom'].toString(); @@ -255,6 +257,9 @@ 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, @@ -267,8 +272,8 @@ class ChangeNowExchangeProvider extends ExchangeProvider { expiredAt: expiredAt, outputTransaction: outputTransaction, payoutAddress: payoutAddress, - userCurrencyFromRaw: '${fromCurrency.toUpperCase()}' + '_', - userCurrencyToRaw: '${toCurrency.toUpperCase()}' + '_', + userCurrencyFromRaw: '${fromCurrency.toUpperCase()}' + '_' + '${_normalizedFromNetwork.toUpperCase()}', + userCurrencyToRaw: '${toCurrency.toUpperCase()}' + '_' + '${_normalizedToNetwork.toUpperCase()}', ); } @@ -307,4 +312,14 @@ class ChangeNowExchangeProvider extends ExchangeProvider { return tag.toLowerCase(); } } + + String _normalizeNetworkType(String network) { + return switch (network.toUpperCase()) { + 'POLY' => 'MATIC', + 'AVAXC' => 'CCHAIN', + _ => network, + }; + } + + } diff --git a/lib/exchange/provider/exolix_exchange_provider.dart b/lib/exchange/provider/exolix_exchange_provider.dart index a3b1b896c..e4e009528 100644 --- a/lib/exchange/provider/exolix_exchange_provider.dart +++ b/lib/exchange/provider/exolix_exchange_provider.dart @@ -241,7 +241,9 @@ class ExolixExchangeProvider extends ExchangeProvider { final responseJSON = json.decode(response.body) as Map; final coinFrom = responseJSON['coinFrom']['coinCode'] as String; + final coinFromNetwork = responseJSON['coinFrom']['network'] as String?; final coinTo = responseJSON['coinTo']['coinCode'] as String; + final coinToNetwork = responseJSON['coinTo']['network'] as String?; final inputAddress = responseJSON['depositAddress'] as String; final amount = responseJSON['amount'].toString(); final status = responseJSON['status'] as String; @@ -260,8 +262,8 @@ class ExolixExchangeProvider extends ExchangeProvider { extraId: extraId, outputTransaction: outputTransaction, payoutAddress: payoutAddress, - userCurrencyFromRaw: '${coinFrom.toUpperCase()}' + '_', - userCurrencyToRaw: '${coinTo.toUpperCase()}' + '_', + userCurrencyFromRaw: '${coinFrom.toUpperCase()}' + '_' + '${coinFromNetwork ?? ''}', + userCurrencyToRaw: '${coinTo.toUpperCase()}' + '_' + '${coinToNetwork ?? ''}', ); } diff --git a/lib/exchange/provider/letsexchange_exchange_provider.dart b/lib/exchange/provider/letsexchange_exchange_provider.dart index 7aa18aba1..7b6665303 100644 --- a/lib/exchange/provider/letsexchange_exchange_provider.dart +++ b/lib/exchange/provider/letsexchange_exchange_provider.dart @@ -237,9 +237,9 @@ class LetsExchangeExchangeProvider extends ExchangeProvider { } final responseJSON = json.decode(response.body) as Map; final from = responseJSON['coin_from'] as String; - final fromNetwork = responseJSON['coin_from_network'] as String; + final fromNetwork = responseJSON['coin_from_network'] as String?; final to = responseJSON['coin_to'] as String; - final toNetwork = responseJSON['coin_to_network'] as String; + final toNetwork = responseJSON['coin_to_network'] as String?; final payoutAddress = responseJSON['withdrawal'] as String; final depositAddress = responseJSON['deposit'] as String; final refundAddress = responseJSON['return'] as String; @@ -253,6 +253,9 @@ 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), @@ -268,8 +271,8 @@ class LetsExchangeExchangeProvider extends ExchangeProvider { expiredAt: expiredAt, isRefund: status == 'refund', extraId: extraId, - userCurrencyFromRaw: '$from' + '_' + '$fromNetwork', - userCurrencyToRaw: '$to' + '_' + '$toNetwork', + userCurrencyFromRaw: '$from' + '_' + normalizedFromNetwork, + userCurrencyToRaw: '$to' + '_' + '$normalizedToNetwork', ); } @@ -313,6 +316,15 @@ class LetsExchangeExchangeProvider extends ExchangeProvider { return currency.title; } + String _normalizeNetworkType(String network) { + return switch (network.toUpperCase()) { + 'ERC20' => 'ETH', + 'TRC20' => 'TRX', + 'BEP20' => 'BSC', + _ => network, + }; + } + String _normalizeBchAddress(String address) => address.startsWith('bitcoincash:') ? address.substring(12) : address; } diff --git a/lib/exchange/provider/sideshift_exchange_provider.dart b/lib/exchange/provider/sideshift_exchange_provider.dart index 2c592e246..33cd2af3d 100644 --- a/lib/exchange/provider/sideshift_exchange_provider.dart +++ b/lib/exchange/provider/sideshift_exchange_provider.dart @@ -254,7 +254,9 @@ class SideShiftExchangeProvider extends ExchangeProvider { final responseJSON = json.decode(response.body) as Map; final fromCurrency = responseJSON['depositCoin'] as String; + final fromNetwork = responseJSON['depositNetwork'] as String?; final toCurrency = responseJSON['settleCoin'] as String; + final toNetwork = responseJSON['settleNetwork'] as String?; final inputAddress = responseJSON['depositAddress'] as String; final expectedSendAmount = responseJSON['depositAmount'] as String?; final status = responseJSON['status'] as String?; @@ -275,8 +277,8 @@ class SideShiftExchangeProvider extends ExchangeProvider { expiredAt: expiredAt, payoutAddress: settleAddress, extraId: depositMemo, - userCurrencyFromRaw: '${fromCurrency.toUpperCase()}' + '_', - userCurrencyToRaw: '${toCurrency.toUpperCase()}' + '_', + userCurrencyFromRaw: '${fromCurrency.toUpperCase()}' + '_' + _normalizeNetworkType(fromNetwork ?? ''), + userCurrencyToRaw: '${toCurrency.toUpperCase()}' + '_' + _normalizeNetworkType(toNetwork ?? ''), ); } @@ -335,7 +337,7 @@ class SideShiftExchangeProvider extends ExchangeProvider { return 'tron'; case 'LN': return 'lightning'; - case 'POLY': + case 'POL': return 'polygon'; case 'ZEC': return 'zcash'; @@ -345,4 +347,16 @@ class SideShiftExchangeProvider extends ExchangeProvider { return tag.toLowerCase(); } } + + String _normalizeNetworkType(String network) { + return switch (network) { + 'ethereum' => 'ETH', + 'tron' => 'TRX', + 'lightning' => 'LN', + 'polygon' => 'POL', + 'zcash' => 'ZEC', + 'avax' => 'AVAXC', + _ => network, + }; + } } diff --git a/lib/exchange/provider/stealth_ex_exchange_provider.dart b/lib/exchange/provider/stealth_ex_exchange_provider.dart index 554116def..47f09ba6b 100644 --- a/lib/exchange/provider/stealth_ex_exchange_provider.dart +++ b/lib/exchange/provider/stealth_ex_exchange_provider.dart @@ -224,7 +224,9 @@ class StealthExExchangeProvider extends ExchangeProvider { final respId = responseJSON['id'] as String; final from = deposit['symbol'] as String; + final fromNetwork = deposit['network'] as String?; final to = withdrawal['symbol'] as String; + final toNetwork = withdrawal['network'] as String?; final payoutAddress = withdrawal['address'] as String; final depositAddress = deposit['address'] as String; final refundAddress = responseJSON['refund_address'] as String; @@ -249,8 +251,8 @@ class StealthExExchangeProvider extends ExchangeProvider { createdAt: createdAt, isRefund: status == 'refunded', extraId: extraId, - userCurrencyFromRaw: '${from.toUpperCase()}' + '_', - userCurrencyToRaw: '${to.toUpperCase()}' + '_', + userCurrencyFromRaw: '${from.toUpperCase()}' + '_' + '${fromNetwork?.toUpperCase() ?? ''}', + userCurrencyToRaw: '${to.toUpperCase()}' + '_' + '${toNetwork?.toUpperCase() ?? ''}', ); } diff --git a/lib/exchange/provider/swaptrade_exchange_provider.dart b/lib/exchange/provider/swaptrade_exchange_provider.dart index 2ebbfe790..3c2a9d271 100644 --- a/lib/exchange/provider/swaptrade_exchange_provider.dart +++ b/lib/exchange/provider/swaptrade_exchange_provider.dart @@ -81,20 +81,20 @@ class SwapTradeExchangeProvider extends ExchangeProvider { final coinsInfo = responseJSON['data'] as List; - for (var coin in coinsInfo) { - if (coin['id'].toString().toUpperCase() == _normalizeCurrency(from)) { - return Limits( - min: double.parse(coin['min'].toString()), - max: double.parse(coin['max'].toString()), - ); - } - } + final coin = coinsInfo.firstWhere( + (coin) => coin['id'].toString().toUpperCase() == _normalizeCurrency(from), + orElse: () => null, + ); - // coin not found: - return Limits(min: 0, max: 0); + if (coin == null) throw Exception('Coin not found: ${_normalizeCurrency(from)}'); + + return Limits( + min: double.parse(coin['min'].toString()), + max: double.parse(coin['max'].toString()), + ); } catch (e) { printV(e.toString()); - return Limits(min: 0, max: 0); + throw Exception('Error fetching limits: ${e.toString()}'); } } diff --git a/lib/exchange/provider/trocador_exchange_provider.dart b/lib/exchange/provider/trocador_exchange_provider.dart index f0d2bb140..5bb577b17 100644 --- a/lib/exchange/provider/trocador_exchange_provider.dart +++ b/lib/exchange/provider/trocador_exchange_provider.dart @@ -277,7 +277,9 @@ class TrocadorExchangeProvider extends ExchangeProvider { 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 networkTo = responseJSON['network_to'] as String?; return Trade( id: id, @@ -294,8 +296,8 @@ class TrocadorExchangeProvider extends ExchangeProvider { providerId: providerId, providerName: providerName, extraId: addressProviderMemo, - userCurrencyFromRaw: '${from.toUpperCase()}' + '_', - userCurrencyToRaw: '${to.toUpperCase()}' + '_', + userCurrencyFromRaw: '${from.toUpperCase()}' + '_' + _normalizeNetworkType(networkFrom ?? ''), + userCurrencyToRaw: '${to.toUpperCase()}' + '_' + _normalizeNetworkType(networkTo ?? ''), // Handle null network ); }); } @@ -362,6 +364,18 @@ class TrocadorExchangeProvider extends ExchangeProvider { } } + String _normalizeNetworkType(String network) { + return switch (network.toUpperCase()) { + 'ERC20' => 'ETH', + 'TRC20' => 'TRX', + 'BEP20' => 'BSC', + 'LIGHTNING' => 'LN', + _ => network, + }; + } + + + Future _getUri(String path, Map queryParams) async { final uri = Uri.http(onionApiAuthority, path, queryParams); diff --git a/lib/exchange/trade.dart b/lib/exchange/trade.dart index 9085b3247..70cf39eb7 100644 --- a/lib/exchange/trade.dart +++ b/lib/exchange/trade.dart @@ -36,9 +36,8 @@ class Trade extends HiveObject { }) { if (provider != null) providerRaw = provider.raw; - if (from != null) fromRaw = from.raw; - - if (to != null) toRaw = to.raw; + fromRaw = from?.raw ?? -1; + toRaw = to?.raw ?? -1; if (state != null) stateRaw = state.raw; } @@ -56,15 +55,15 @@ class Trade extends HiveObject { ExchangeProviderDescription get provider => ExchangeProviderDescription.deserialize(raw: providerRaw); - @HiveField(2, defaultValue: 0) - late int fromRaw; + @HiveField(2, defaultValue: -1) + int fromRaw = -1; - CryptoCurrency get from => CryptoCurrency.deserialize(raw: fromRaw); + CryptoCurrency? get from => CryptoCurrency.safeDeserialize(raw: fromRaw); - @HiveField(3, defaultValue: 0) - late int toRaw; + @HiveField(3, defaultValue: -1) + int toRaw = -1; - CryptoCurrency get to => CryptoCurrency.deserialize(raw: toRaw); + CryptoCurrency? get to => CryptoCurrency.safeDeserialize(raw: toRaw); @HiveField(4, defaultValue: '') late String stateRaw; @@ -193,8 +192,8 @@ class Trade extends HiveObject { return { 'id': id, 'provider': provider.serialize(), - 'input': from.serialize(), - 'output': to.serialize(), + 'input': fromRaw, + 'output': toRaw, 'date': createdAt != null ? createdAt!.millisecondsSinceEpoch : null, 'amount': amount, 'receive_amount': receiveAmount, @@ -252,8 +251,8 @@ class TradeAdapter extends TypeAdapter { userCurrencyToRaw: fields[25] as String?, ) ..providerRaw = fields[1] == null ? 0 : fields[1] as int - ..fromRaw = fields[2] == null ? 0 : fields[2] as int - ..toRaw = fields[3] == null ? 0 : fields[3] as int + ..fromRaw = (fields[2] as int?) ?? -1 + ..toRaw = (fields[3] as int?) ?? -1 ..stateRaw = fields[4] == null ? '' : fields[4] as String; } diff --git a/lib/src/screens/exchange/exchange_page.dart b/lib/src/screens/exchange/exchange_page.dart index 00798c94d..172b210c4 100644 --- a/lib/src/screens/exchange/exchange_page.dart +++ b/lib/src/screens/exchange/exchange_page.dart @@ -368,8 +368,12 @@ class ExchangePage extends BasePage { void applyTemplate( BuildContext context, ExchangeViewModel exchangeViewModel, ExchangeTemplate template) async { - final depositCryptoCurrency = CryptoCurrency.fromString(template.depositCurrency); - final receiveCryptoCurrency = CryptoCurrency.fromString(template.receiveCurrency); + final depositCryptoCurrency = CryptoCurrency.safeParseCurrencyFromString(template.depositCurrency); + final receiveCryptoCurrency = CryptoCurrency.safeParseCurrencyFromString(template.receiveCurrency); + + if (depositCryptoCurrency == null || receiveCryptoCurrency == null) { ///TO DO: add support for user tokens + return; + } exchangeViewModel.changeDepositCurrency(currency: depositCryptoCurrency); exchangeViewModel.changeReceiveCurrency(currency: receiveCryptoCurrency); diff --git a/lib/src/screens/exchange_trade/exchange_trade_page.dart b/lib/src/screens/exchange_trade/exchange_trade_page.dart index a3ab59be5..17b6fd71c 100644 --- a/lib/src/screens/exchange_trade/exchange_trade_page.dart +++ b/lib/src/screens/exchange_trade/exchange_trade_page.dart @@ -28,10 +28,12 @@ void showInformation(ExchangeTradeViewModel exchangeTradeViewModel, BuildContext final trade = exchangeTradeViewModel.trade; final walletName = exchangeTradeViewModel.wallet.name; + final from = trade.from?.toString() ?? trade.userCurrencyFrom.toString(); + final information = exchangeTradeViewModel.isSendable - ? S.current.exchange_trade_result_confirm(trade.amount, trade.from.toString(), walletName) + + ? S.current.exchange_trade_result_confirm(trade.amount, from, walletName) + exchangeTradeViewModel.extraInfo - : S.current.exchange_result_description(trade.amount, trade.from.toString()) + + : S.current.exchange_result_description(trade.amount, from) + exchangeTradeViewModel.extraInfo; showPopUp( diff --git a/lib/view_model/exchange/exchange_trade_view_model.dart b/lib/view_model/exchange/exchange_trade_view_model.dart index 76198c59d..ac8bba173 100644 --- a/lib/view_model/exchange/exchange_trade_view_model.dart +++ b/lib/view_model/exchange/exchange_trade_view_model.dart @@ -128,7 +128,15 @@ abstract class ExchangeTradeViewModelBase with Store { @action Future confirmSending() async { if (!isSendable) return; - sendViewModel.selectedCryptoCurrency = trade.from; + + final selected = trade.from ?? trade.userCurrencyFrom; + if (selected == null) { + printV('No selectable currency for trade ${trade.id}'); + return; + } + + sendViewModel.selectedCryptoCurrency = selected; + final pendingTransaction = await sendViewModel.createTransaction(provider: _provider); if (_provider is ThorChainExchangeProvider) { trade.id = pendingTransaction?.id ?? ''; @@ -251,7 +259,7 @@ abstract class ExchangeTradeViewModelBase with Store { bool _isEthToken() => wallet.currency == CryptoCurrency.eth && - tradesStore.trade!.from.tag == CryptoCurrency.eth.title; + tradeFrom?.tag == CryptoCurrency.eth.title; bool _isPolygonToken() => wallet.currency == CryptoCurrency.maticpoly &&