Cw 807 add extra fields for xrp and xlm exchanges (#1869)
Some checks are pending
Cache Dependencies / test (push) Waiting to run

* show extra id for xrp/xlm Stealth

* extra id for xrp/xlm LetsExchange

* extra id xrp/xml for Sideshift and Trocador

* fix Exolix min amount issue

* fix extra id ui
This commit is contained in:
Serhii 2024-12-13 01:24:37 +02:00 committed by GitHub
parent df6ea551aa
commit 489a409bea
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 102 additions and 27 deletions

View file

@ -60,15 +60,17 @@ class ExolixExchangeProvider extends ExchangeProvider {
Future<bool> checkIsAvailable() async => true; Future<bool> checkIsAvailable() async => true;
@override @override
Future<Limits> fetchLimits( Future<Limits> fetchLimits({
{required CryptoCurrency from, required CryptoCurrency from,
required CryptoCurrency to, required CryptoCurrency to,
required bool isFixedRateMode}) async { required bool isFixedRateMode,
}) async {
final params = <String, String>{ final params = <String, String>{
'rateType': _getRateType(isFixedRateMode), 'rateType': _getRateType(isFixedRateMode),
'amount': '1', 'amount': '1',
'apiToken': apiKey, 'apiToken': apiKey,
}; };
if (isFixedRateMode) { if (isFixedRateMode) {
params['coinFrom'] = _normalizeCurrency(to); params['coinFrom'] = _normalizeCurrency(to);
params['coinTo'] = _normalizeCurrency(from); params['coinTo'] = _normalizeCurrency(from);
@ -80,14 +82,30 @@ class ExolixExchangeProvider extends ExchangeProvider {
params['networkFrom'] = _networkFor(from); params['networkFrom'] = _networkFor(from);
params['networkTo'] = _networkFor(to); params['networkTo'] = _networkFor(to);
} }
final uri = Uri.https(apiBaseUrl, ratePath, params);
final response = await get(uri);
if (response.statusCode != 200) // Maximum of 2 attempts to fetch limits
throw Exception('Unexpected http status: ${response.statusCode}'); 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<String, dynamic>; if (response.statusCode == 200) {
return Limits(min: responseJSON['minAmount'] as double?); final responseJSON = json.decode(response.body) as Map<String, dynamic>;
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<String, dynamic>;
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 @override
@ -279,4 +297,15 @@ class ExolixExchangeProvider extends ExchangeProvider {
String _normalizeAddress(String address) => String _normalizeAddress(String address) =>
address.startsWith('bitcoincash:') ? address.replaceFirst('bitcoincash:', '') : 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;
}
} }

View file

@ -168,6 +168,7 @@ class LetsExchangeExchangeProvider extends ExchangeProvider {
final status = responseJSON['status'] as String; final status = responseJSON['status'] as String;
final createdAtString = responseJSON['created_at'] as String; final createdAtString = responseJSON['created_at'] as String;
final expiredAtTimestamp = responseJSON['expired_at'] as int; final expiredAtTimestamp = responseJSON['expired_at'] as int;
final extraId = responseJSON['deposit_extra_id'] as String?;
final createdAt = DateTime.parse(createdAtString); final createdAt = DateTime.parse(createdAtString);
final expiredAt = DateTime.fromMillisecondsSinceEpoch(expiredAtTimestamp * 1000); final expiredAt = DateTime.fromMillisecondsSinceEpoch(expiredAtTimestamp * 1000);
@ -199,6 +200,7 @@ class LetsExchangeExchangeProvider extends ExchangeProvider {
state: TradeState.deserialize(raw: status), state: TradeState.deserialize(raw: status),
createdAt: createdAt, createdAt: createdAt,
expiredAt: expiredAt, expiredAt: expiredAt,
extraId: extraId,
); );
} catch (e) { } catch (e) {
log(e.toString()); log(e.toString());
@ -231,6 +233,7 @@ class LetsExchangeExchangeProvider extends ExchangeProvider {
final status = responseJSON['status'] as String; final status = responseJSON['status'] as String;
final createdAtString = responseJSON['created_at'] as String; final createdAtString = responseJSON['created_at'] as String;
final expiredAtTimestamp = responseJSON['expired_at'] as int; final expiredAtTimestamp = responseJSON['expired_at'] as int;
final extraId = responseJSON['deposit_extra_id'] as String?;
final createdAt = DateTime.parse(createdAtString); final createdAt = DateTime.parse(createdAtString);
final expiredAt = DateTime.fromMillisecondsSinceEpoch(expiredAtTimestamp * 1000); final expiredAt = DateTime.fromMillisecondsSinceEpoch(expiredAtTimestamp * 1000);
@ -249,6 +252,7 @@ class LetsExchangeExchangeProvider extends ExchangeProvider {
createdAt: createdAt, createdAt: createdAt,
expiredAt: expiredAt, expiredAt: expiredAt,
isRefund: status == 'refund', isRefund: status == 'refund',
extraId: extraId,
); );
} }

View file

@ -203,6 +203,7 @@ class SideShiftExchangeProvider extends ExchangeProvider {
final inputAddress = responseJSON['depositAddress'] as String; final inputAddress = responseJSON['depositAddress'] as String;
final settleAddress = responseJSON['settleAddress'] as String; final settleAddress = responseJSON['settleAddress'] as String;
final depositAmount = responseJSON['depositAmount'] as String?; final depositAmount = responseJSON['depositAmount'] as String?;
final depositMemo = responseJSON['depositMemo'] as String?;
return Trade( return Trade(
id: id, id: id,
@ -217,6 +218,7 @@ class SideShiftExchangeProvider extends ExchangeProvider {
payoutAddress: settleAddress, payoutAddress: settleAddress,
createdAt: DateTime.now(), createdAt: DateTime.now(),
isSendAll: isSendAll, isSendAll: isSendAll,
extraId: depositMemo
); );
} }
@ -251,6 +253,7 @@ class SideShiftExchangeProvider extends ExchangeProvider {
final isVariable = (responseJSON['type'] as String) == 'variable'; final isVariable = (responseJSON['type'] as String) == 'variable';
final expiredAtRaw = responseJSON['expiresAt'] as String; final expiredAtRaw = responseJSON['expiresAt'] as String;
final expiredAt = isVariable ? null : DateTime.tryParse(expiredAtRaw)?.toLocal(); final expiredAt = isVariable ? null : DateTime.tryParse(expiredAtRaw)?.toLocal();
final depositMemo = responseJSON['depositMemo'] as String?;
return Trade( return Trade(
id: id, id: id,
@ -261,7 +264,8 @@ class SideShiftExchangeProvider extends ExchangeProvider {
amount: expectedSendAmount ?? '', amount: expectedSendAmount ?? '',
state: TradeState.deserialize(raw: status ?? 'created'), state: TradeState.deserialize(raw: status ?? 'created'),
expiredAt: expiredAt, expiredAt: expiredAt,
payoutAddress: settleAddress); payoutAddress: settleAddress,
extraId: depositMemo);
} }
Future<String> _createQuote(TradeRequest request) async { Future<String> _createQuote(TradeRequest request) async {

View file

@ -154,6 +154,7 @@ class StealthExExchangeProvider extends ExchangeProvider {
final receiveAmount = toDouble(withdrawal['amount']); final receiveAmount = toDouble(withdrawal['amount']);
final status = responseJSON['status'] as String; final status = responseJSON['status'] as String;
final createdAtString = responseJSON['created_at'] as String; final createdAtString = responseJSON['created_at'] as String;
final extraId = deposit['extra_id'] as String?;
final createdAt = DateTime.parse(createdAtString); final createdAt = DateTime.parse(createdAtString);
final expiredAt = validUntil != null final expiredAt = validUntil != null
@ -188,6 +189,7 @@ class StealthExExchangeProvider extends ExchangeProvider {
state: TradeState.deserialize(raw: status), state: TradeState.deserialize(raw: status),
createdAt: createdAt, createdAt: createdAt,
expiredAt: expiredAt, expiredAt: expiredAt,
extraId: extraId,
); );
} catch (e) { } catch (e) {
log(e.toString()); log(e.toString());
@ -220,6 +222,7 @@ class StealthExExchangeProvider extends ExchangeProvider {
final status = responseJSON['status'] as String; final status = responseJSON['status'] as String;
final createdAtString = responseJSON['created_at'] as String; final createdAtString = responseJSON['created_at'] as String;
final createdAt = DateTime.parse(createdAtString); final createdAt = DateTime.parse(createdAtString);
final extraId = deposit['extra_id'] as String?;
return Trade( return Trade(
id: respId, id: respId,
@ -234,6 +237,7 @@ class StealthExExchangeProvider extends ExchangeProvider {
state: TradeState.deserialize(raw: status), state: TradeState.deserialize(raw: status),
createdAt: createdAt, createdAt: createdAt,
isRefund: status == 'refunded', isRefund: status == 'refunded',
extraId: extraId,
); );
} }

View file

@ -171,7 +171,8 @@ class TrocadorExchangeProvider extends ExchangeProvider {
if (!isFixedRateMode) 'amount_from': request.fromAmount, if (!isFixedRateMode) 'amount_from': request.fromAmount,
if (isFixedRateMode) 'amount_to': request.toAmount, if (isFixedRateMode) 'amount_to': request.toAmount,
'address': request.toAddress, 'address': request.toAddress,
'refund': request.refundAddress 'refund': request.refundAddress,
'refund_memo' : '0',
}; };
if (isFixedRateMode) { if (isFixedRateMode) {
@ -262,6 +263,7 @@ class TrocadorExchangeProvider extends ExchangeProvider {
final password = responseJSON['password'] as String; final password = responseJSON['password'] as String;
final providerId = responseJSON['id_provider'] as String; final providerId = responseJSON['id_provider'] as String;
final providerName = responseJSON['provider'] as String; final providerName = responseJSON['provider'] as String;
final addressProviderMemo = responseJSON['address_provider_memo'] as String?;
return Trade( return Trade(
id: id, id: id,
@ -277,6 +279,7 @@ class TrocadorExchangeProvider extends ExchangeProvider {
password: password, password: password,
providerId: providerId, providerId: providerId,
providerName: providerName, providerName: providerName,
extraId: addressProviderMemo,
); );
}); });
} }

View file

@ -143,6 +143,7 @@ class Trade extends HiveObject {
isRefund: map['isRefund'] as bool?, isRefund: map['isRefund'] as bool?,
isSendAll: map['isSendAll'] as bool?, isSendAll: map['isSendAll'] as bool?,
router: map['router'] as String?, router: map['router'] as String?,
extraId: map['extra_id'] as String?,
); );
} }
@ -162,6 +163,7 @@ class Trade extends HiveObject {
'isRefund': isRefund, 'isRefund': isRefund,
'isSendAll': isSendAll, 'isSendAll': isSendAll,
'router': router, 'router': router,
'extra_id': extraId,
}; };
} }

View file

@ -8,7 +8,8 @@ class TradeDetailsListCardItem extends StandartListItem {
{required this.id, {required this.id,
required this.createdAt, required this.createdAt,
required this.pair, required this.pair,
required this.onTap}) required this.onTap,
this.extraId})
: super(title: '', value: ''); : super(title: '', value: '');
factory TradeDetailsListCardItem.tradeDetails( factory TradeDetailsListCardItem.tradeDetails(
@ -16,9 +17,19 @@ class TradeDetailsListCardItem extends StandartListItem {
required String createdAt, required String createdAt,
required CryptoCurrency from, required CryptoCurrency from,
required CryptoCurrency to, 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( return TradeDetailsListCardItem(
id: '${S.current.trade_details_id} ${formatAsText(id)}', id: '${S.current.trade_details_id} ${formatAsText(id)}',
extraId: extraId != null ? '$extraIdTitle $extraId' : null,
createdAt: formatAsText(createdAt), createdAt: formatAsText(createdAt),
pair: '${formatAsText(from)}${formatAsText(to)}', pair: '${formatAsText(from)}${formatAsText(to)}',
onTap: onTap); onTap: onTap);
@ -27,6 +38,7 @@ class TradeDetailsListCardItem extends StandartListItem {
final String id; final String id;
final String createdAt; final String createdAt;
final String pair; final String pair;
final String? extraId;
final void Function(BuildContext) onTap; final void Function(BuildContext) onTap;
static String formatAsText<T>(T value) => value?.toString() ?? ''; static String formatAsText<T>(T value) => value?.toString() ?? '';

View file

@ -69,6 +69,7 @@ class TradeDetailsPageBodyState extends State<TradeDetailsPageBody> {
if (item is TradeDetailsListCardItem) if (item is TradeDetailsListCardItem)
return TradeDetailsStandardListCard( return TradeDetailsStandardListCard(
id: item.id, id: item.id,
extraId: item.extraId,
create: item.createdAt, create: item.createdAt,
pair: item.pair, pair: item.pair,
currentTheme: tradeDetailsViewModel.settingsStore.currentTheme.type, currentTheme: tradeDetailsViewModel.settingsStore.currentTheme.type,

View file

@ -6,12 +6,14 @@ import 'package:cake_wallet/themes/theme_base.dart';
class TradeDetailsStandardListCard extends StatelessWidget { class TradeDetailsStandardListCard extends StatelessWidget {
TradeDetailsStandardListCard( TradeDetailsStandardListCard(
{required this.id, {required this.id,
this.extraId,
required this.create, required this.create,
required this.pair, required this.pair,
required this.onTap, required this.onTap,
required this.currentTheme}); required this.currentTheme});
final String id; final String id;
final String? extraId;
final String create; final String create;
final String pair; final String pair;
final ThemeType currentTheme; final ThemeType currentTheme;
@ -57,6 +59,16 @@ class TradeDetailsStandardListCard extends StatelessWidget {
SizedBox( SizedBox(
height: 8, 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, Text(create,
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,

View file

@ -146,7 +146,7 @@ abstract class ExchangeTradeViewModelBase with Store {
void _updateItems() { void _updateItems() {
final tagFrom = 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}' + ' ' : ''; final tagTo = tradesStore.trade!.to.tag != null ? '${tradesStore.trade!.to.tag}' + ' ' : '';
items.clear(); 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([ items.addAll([
ExchangeTradeItem( ExchangeTradeItem(
title: S.current.amount, title: S.current.amount,
@ -176,7 +166,7 @@ abstract class ExchangeTradeViewModelBase with Store {
isCopied: true, isCopied: true,
), ),
ExchangeTradeItem( ExchangeTradeItem(
title: S.current.estimated_receive_amount +':', title: S.current.estimated_receive_amount + ':',
data: '${tradesStore.trade?.receiveAmount} ${trade.to}', data: '${tradesStore.trade?.receiveAmount} ${trade.to}',
isCopied: true, isCopied: true,
), ),
@ -185,12 +175,25 @@ abstract class ExchangeTradeViewModelBase with Store {
data: trade.inputAddress ?? '', data: trade.inputAddress ?? '',
isCopied: true, 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( ExchangeTradeItem(
title: S.current.arrive_in_this_address('${tradesStore.trade!.to}', tagTo) + ':', title: S.current.arrive_in_this_address('${tradesStore.trade!.to}', tagTo) + ':',
data: trade.payoutAddress ?? '', data: trade.payoutAddress ?? '',
isCopied: true, isCopied: true,
), ),
]); );
} }
static bool _checkIfCanSend(TradesStore tradesStore, WalletBase wallet) { static bool _checkIfCanSend(TradesStore tradesStore, WalletBase wallet) {

View file

@ -153,6 +153,7 @@ abstract class TradeDetailsViewModelBase with Store {
items.add(TradeDetailsListCardItem.tradeDetails( items.add(TradeDetailsListCardItem.tradeDetails(
id: trade.id, id: trade.id,
extraId: trade.extraId,
createdAt: trade.createdAt != null ? dateFormat.format(trade.createdAt!) : '', createdAt: trade.createdAt != null ? dateFormat.format(trade.createdAt!) : '',
from: trade.from, from: trade.from,
to: trade.to, to: trade.to,