diff --git a/lib/exchange/provider/exolix_exchange_provider.dart b/lib/exchange/provider/exolix_exchange_provider.dart index 49aa56195..ee5a46bbc 100644 --- a/lib/exchange/provider/exolix_exchange_provider.dart +++ b/lib/exchange/provider/exolix_exchange_provider.dart @@ -60,15 +60,17 @@ class ExolixExchangeProvider extends ExchangeProvider { Future checkIsAvailable() async => true; @override - Future fetchLimits( - {required CryptoCurrency from, - required CryptoCurrency to, - required bool isFixedRateMode}) async { + Future fetchLimits({ + required CryptoCurrency from, + required CryptoCurrency to, + required bool isFixedRateMode, + }) async { final params = { 'rateType': _getRateType(isFixedRateMode), 'amount': '1', 'apiToken': apiKey, }; + if (isFixedRateMode) { params['coinFrom'] = _normalizeCurrency(to); params['coinTo'] = _normalizeCurrency(from); @@ -80,14 +82,30 @@ class ExolixExchangeProvider extends ExchangeProvider { params['networkFrom'] = _networkFor(from); params['networkTo'] = _networkFor(to); } - final uri = Uri.https(apiBaseUrl, ratePath, params); - final response = await get(uri); - if (response.statusCode != 200) - throw Exception('Unexpected http status: ${response.statusCode}'); + // Maximum of 2 attempts to fetch limits + for (int i = 0; i < 2; i++) { + final uri = Uri.https(apiBaseUrl, ratePath, params); + final response = await get(uri); - final responseJSON = json.decode(response.body) as Map; - return Limits(min: responseJSON['minAmount'] as double?); + if (response.statusCode == 200) { + final responseJSON = json.decode(response.body) as Map; + final minAmount = responseJSON['minAmount']; + final maxAmount = responseJSON['maxAmount']; + return Limits(min: _toDouble(minAmount), max: _toDouble(maxAmount)); + } else if (response.statusCode == 422) { + final errorResponse = json.decode(response.body) as Map; + if (errorResponse.containsKey('minAmount')) { + params['amount'] = errorResponse['minAmount'].toString(); + continue; + } + throw Exception('Error 422: ${errorResponse['message'] ?? 'Unknown error'}'); + } else { + throw Exception('Unexpected HTTP status: ${response.statusCode}'); + } + } + + throw Exception('Failed to fetch limits after retrying.'); } @override @@ -279,4 +297,15 @@ class ExolixExchangeProvider extends ExchangeProvider { String _normalizeAddress(String address) => address.startsWith('bitcoincash:') ? address.replaceFirst('bitcoincash:', '') : address; + + static double? _toDouble(dynamic value) { + if (value is int) { + return value.toDouble(); + } else if (value is double) { + return value; + } else if (value is String) { + return double.tryParse(value); + } + return null; + } } diff --git a/lib/exchange/provider/letsexchange_exchange_provider.dart b/lib/exchange/provider/letsexchange_exchange_provider.dart index 0a2f81317..95520d5f0 100644 --- a/lib/exchange/provider/letsexchange_exchange_provider.dart +++ b/lib/exchange/provider/letsexchange_exchange_provider.dart @@ -168,6 +168,7 @@ class LetsExchangeExchangeProvider extends ExchangeProvider { final status = responseJSON['status'] as String; final createdAtString = responseJSON['created_at'] as String; final expiredAtTimestamp = responseJSON['expired_at'] as int; + final extraId = responseJSON['deposit_extra_id'] as String?; final createdAt = DateTime.parse(createdAtString); final expiredAt = DateTime.fromMillisecondsSinceEpoch(expiredAtTimestamp * 1000); @@ -199,6 +200,7 @@ class LetsExchangeExchangeProvider extends ExchangeProvider { state: TradeState.deserialize(raw: status), createdAt: createdAt, expiredAt: expiredAt, + extraId: extraId, ); } catch (e) { log(e.toString()); @@ -231,6 +233,7 @@ class LetsExchangeExchangeProvider extends ExchangeProvider { final status = responseJSON['status'] as String; final createdAtString = responseJSON['created_at'] as String; final expiredAtTimestamp = responseJSON['expired_at'] as int; + final extraId = responseJSON['deposit_extra_id'] as String?; final createdAt = DateTime.parse(createdAtString); final expiredAt = DateTime.fromMillisecondsSinceEpoch(expiredAtTimestamp * 1000); @@ -249,6 +252,7 @@ class LetsExchangeExchangeProvider extends ExchangeProvider { createdAt: createdAt, expiredAt: expiredAt, isRefund: status == 'refund', + extraId: extraId, ); } diff --git a/lib/exchange/provider/sideshift_exchange_provider.dart b/lib/exchange/provider/sideshift_exchange_provider.dart index f53d778ec..7373d5f2d 100644 --- a/lib/exchange/provider/sideshift_exchange_provider.dart +++ b/lib/exchange/provider/sideshift_exchange_provider.dart @@ -203,6 +203,7 @@ class SideShiftExchangeProvider extends ExchangeProvider { final inputAddress = responseJSON['depositAddress'] as String; final settleAddress = responseJSON['settleAddress'] as String; final depositAmount = responseJSON['depositAmount'] as String?; + final depositMemo = responseJSON['depositMemo'] as String?; return Trade( id: id, @@ -217,6 +218,7 @@ class SideShiftExchangeProvider extends ExchangeProvider { payoutAddress: settleAddress, createdAt: DateTime.now(), isSendAll: isSendAll, + extraId: depositMemo ); } @@ -251,6 +253,7 @@ class SideShiftExchangeProvider extends ExchangeProvider { final isVariable = (responseJSON['type'] as String) == 'variable'; final expiredAtRaw = responseJSON['expiresAt'] as String; final expiredAt = isVariable ? null : DateTime.tryParse(expiredAtRaw)?.toLocal(); + final depositMemo = responseJSON['depositMemo'] as String?; return Trade( id: id, @@ -261,7 +264,8 @@ class SideShiftExchangeProvider extends ExchangeProvider { amount: expectedSendAmount ?? '', state: TradeState.deserialize(raw: status ?? 'created'), expiredAt: expiredAt, - payoutAddress: settleAddress); + payoutAddress: settleAddress, + extraId: depositMemo); } Future _createQuote(TradeRequest request) async { diff --git a/lib/exchange/provider/stealth_ex_exchange_provider.dart b/lib/exchange/provider/stealth_ex_exchange_provider.dart index 123f759ef..53c40ee62 100644 --- a/lib/exchange/provider/stealth_ex_exchange_provider.dart +++ b/lib/exchange/provider/stealth_ex_exchange_provider.dart @@ -154,6 +154,7 @@ class StealthExExchangeProvider extends ExchangeProvider { 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?; final createdAt = DateTime.parse(createdAtString); final expiredAt = validUntil != null @@ -188,6 +189,7 @@ class StealthExExchangeProvider extends ExchangeProvider { state: TradeState.deserialize(raw: status), createdAt: createdAt, expiredAt: expiredAt, + extraId: extraId, ); } catch (e) { log(e.toString()); @@ -220,6 +222,7 @@ class StealthExExchangeProvider extends ExchangeProvider { final status = responseJSON['status'] as String; final createdAtString = responseJSON['created_at'] as String; final createdAt = DateTime.parse(createdAtString); + final extraId = deposit['extra_id'] as String?; return Trade( id: respId, @@ -234,6 +237,7 @@ class StealthExExchangeProvider extends ExchangeProvider { state: TradeState.deserialize(raw: status), createdAt: createdAt, isRefund: status == 'refunded', + extraId: extraId, ); } diff --git a/lib/exchange/provider/trocador_exchange_provider.dart b/lib/exchange/provider/trocador_exchange_provider.dart index b01ce9eae..151ded371 100644 --- a/lib/exchange/provider/trocador_exchange_provider.dart +++ b/lib/exchange/provider/trocador_exchange_provider.dart @@ -171,7 +171,8 @@ class TrocadorExchangeProvider extends ExchangeProvider { if (!isFixedRateMode) 'amount_from': request.fromAmount, if (isFixedRateMode) 'amount_to': request.toAmount, 'address': request.toAddress, - 'refund': request.refundAddress + 'refund': request.refundAddress, + 'refund_memo' : '0', }; if (isFixedRateMode) { @@ -262,6 +263,7 @@ class TrocadorExchangeProvider extends ExchangeProvider { final password = responseJSON['password'] as String; final providerId = responseJSON['id_provider'] as String; final providerName = responseJSON['provider'] as String; + final addressProviderMemo = responseJSON['address_provider_memo'] as String?; return Trade( id: id, @@ -277,6 +279,7 @@ class TrocadorExchangeProvider extends ExchangeProvider { password: password, providerId: providerId, providerName: providerName, + extraId: addressProviderMemo, ); }); } diff --git a/lib/exchange/trade.dart b/lib/exchange/trade.dart index a0c08aac7..c5738cfd9 100644 --- a/lib/exchange/trade.dart +++ b/lib/exchange/trade.dart @@ -143,6 +143,7 @@ class Trade extends HiveObject { isRefund: map['isRefund'] as bool?, isSendAll: map['isSendAll'] as bool?, router: map['router'] as String?, + extraId: map['extra_id'] as String?, ); } @@ -162,6 +163,7 @@ class Trade extends HiveObject { 'isRefund': isRefund, 'isSendAll': isSendAll, 'router': router, + 'extra_id': extraId, }; } diff --git a/lib/src/screens/trade_details/trade_details_list_card.dart b/lib/src/screens/trade_details/trade_details_list_card.dart index 9c543a964..64f727b91 100644 --- a/lib/src/screens/trade_details/trade_details_list_card.dart +++ b/lib/src/screens/trade_details/trade_details_list_card.dart @@ -8,7 +8,8 @@ class TradeDetailsListCardItem extends StandartListItem { {required this.id, required this.createdAt, required this.pair, - required this.onTap}) + required this.onTap, + this.extraId}) : super(title: '', value: ''); factory TradeDetailsListCardItem.tradeDetails( @@ -16,9 +17,19 @@ class TradeDetailsListCardItem extends StandartListItem { required String createdAt, required CryptoCurrency from, required CryptoCurrency to, - required void Function(BuildContext) onTap}) { + required void Function(BuildContext) onTap, + String? extraId}) { + + + final extraIdTitle = from == CryptoCurrency.xrp + ? S.current.destination_tag + : from == CryptoCurrency.xlm + ? S.current.memo + : S.current.extra_id; + return TradeDetailsListCardItem( id: '${S.current.trade_details_id} ${formatAsText(id)}', + extraId: extraId != null ? '$extraIdTitle $extraId' : null, createdAt: formatAsText(createdAt), pair: '${formatAsText(from)} → ${formatAsText(to)}', onTap: onTap); @@ -27,6 +38,7 @@ class TradeDetailsListCardItem extends StandartListItem { final String id; final String createdAt; final String pair; + final String? extraId; final void Function(BuildContext) onTap; static String formatAsText(T value) => value?.toString() ?? ''; diff --git a/lib/src/screens/trade_details/trade_details_page.dart b/lib/src/screens/trade_details/trade_details_page.dart index 1028c3939..40d14fa14 100644 --- a/lib/src/screens/trade_details/trade_details_page.dart +++ b/lib/src/screens/trade_details/trade_details_page.dart @@ -69,6 +69,7 @@ class TradeDetailsPageBodyState extends State { if (item is TradeDetailsListCardItem) return TradeDetailsStandardListCard( id: item.id, + extraId: item.extraId, create: item.createdAt, pair: item.pair, currentTheme: tradeDetailsViewModel.settingsStore.currentTheme.type, diff --git a/lib/src/widgets/standard_list_card.dart b/lib/src/widgets/standard_list_card.dart index 4b16e1ad4..c86df0b9d 100644 --- a/lib/src/widgets/standard_list_card.dart +++ b/lib/src/widgets/standard_list_card.dart @@ -6,12 +6,14 @@ import 'package:cake_wallet/themes/theme_base.dart'; class TradeDetailsStandardListCard extends StatelessWidget { TradeDetailsStandardListCard( {required this.id, + this.extraId, required this.create, required this.pair, required this.onTap, required this.currentTheme}); final String id; + final String? extraId; final String create; final String pair; final ThemeType currentTheme; @@ -57,6 +59,16 @@ class TradeDetailsStandardListCard extends StatelessWidget { SizedBox( height: 8, ), + if (extraId != null && extraId!.isNotEmpty) + Padding( + padding: const EdgeInsets.only(bottom: 8.0), + child: Text(extraId!, + style: TextStyle( + fontSize: 16, + fontFamily: 'Lato', + fontWeight: FontWeight.w400, + color: textColor)), + ), Text(create, style: TextStyle( fontSize: 12, diff --git a/lib/view_model/exchange/exchange_trade_view_model.dart b/lib/view_model/exchange/exchange_trade_view_model.dart index ac0456440..0c8053842 100644 --- a/lib/view_model/exchange/exchange_trade_view_model.dart +++ b/lib/view_model/exchange/exchange_trade_view_model.dart @@ -146,7 +146,7 @@ abstract class ExchangeTradeViewModelBase with Store { void _updateItems() { final tagFrom = - tradesStore.trade!.from.tag != null ? '${tradesStore.trade!.from.tag}' + ' ' : ''; + tradesStore.trade!.from.tag != null ? '${tradesStore.trade!.from.tag}' + ' ' : ''; final tagTo = tradesStore.trade!.to.tag != null ? '${tradesStore.trade!.to.tag}' + ' ' : ''; items.clear(); @@ -159,16 +159,6 @@ abstract class ExchangeTradeViewModelBase with Store { ), ); - if (trade.extraId != null) { - final title = trade.from == CryptoCurrency.xrp - ? S.current.destination_tag - : trade.from == CryptoCurrency.xlm - ? S.current.memo - : S.current.extra_id; - - items.add(ExchangeTradeItem(title: title, data: '${trade.extraId}', isCopied: false)); - } - items.addAll([ ExchangeTradeItem( title: S.current.amount, @@ -176,7 +166,7 @@ abstract class ExchangeTradeViewModelBase with Store { isCopied: true, ), ExchangeTradeItem( - title: S.current.estimated_receive_amount +':', + title: S.current.estimated_receive_amount + ':', data: '${tradesStore.trade?.receiveAmount} ${trade.to}', isCopied: true, ), @@ -185,12 +175,25 @@ abstract class ExchangeTradeViewModelBase with Store { data: trade.inputAddress ?? '', isCopied: true, ), + ]); + + if (trade.extraId != null) { + final title = trade.from == CryptoCurrency.xrp + ? S.current.destination_tag + : trade.from == CryptoCurrency.xlm + ? S.current.memo + : S.current.extra_id; + + items.add(ExchangeTradeItem(title: title, data: '${trade.extraId}', isCopied: true)); + } + + items.add( ExchangeTradeItem( title: S.current.arrive_in_this_address('${tradesStore.trade!.to}', tagTo) + ':', data: trade.payoutAddress ?? '', isCopied: true, ), - ]); + ); } static bool _checkIfCanSend(TradesStore tradesStore, WalletBase wallet) { diff --git a/lib/view_model/trade_details_view_model.dart b/lib/view_model/trade_details_view_model.dart index 59813090d..db7b979d9 100644 --- a/lib/view_model/trade_details_view_model.dart +++ b/lib/view_model/trade_details_view_model.dart @@ -153,6 +153,7 @@ abstract class TradeDetailsViewModelBase with Store { items.add(TradeDetailsListCardItem.tradeDetails( id: trade.id, + extraId: trade.extraId, createdAt: trade.createdAt != null ? dateFormat.format(trade.createdAt!) : '', from: trade.from, to: trade.to,