Cw 807 add extra fields for xrp and xlm exchanges (#1869)
* 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
Serhii committed
Dec 13, 2024 at 01:24 UTC
489a409beace6fac7368645f2dc02d5923a05b23
11 files changed
+102
-27
lib/exchange/provider/exolix_exchange_provider.dart
+39
-10
@@ -60,15 +60,17 @@ class ExolixExchangeProvider extends ExchangeProvider {
60
Future<bool> checkIsAvailable() async => true;
61
62
@override
63
- Future<Limits> fetchLimits(
64
- {required CryptoCurrency from,
65
- required CryptoCurrency to,
66
- required bool isFixedRateMode}) async {
63
+ Future<Limits> fetchLimits({
64
+ required CryptoCurrency from,
65
+ required CryptoCurrency to,
66
+ required bool isFixedRateMode,
67
+ }) async {
68
final params = <String, String>{
69
'rateType': _getRateType(isFixedRateMode),
70
'amount': '1',
71
'apiToken': apiKey,
72
};
73
+
74
if (isFixedRateMode) {
75
params['coinFrom'] = _normalizeCurrency(to);
76
params['coinTo'] = _normalizeCurrency(from);
@@ -80,14 +82,30 @@ class ExolixExchangeProvider extends ExchangeProvider {
82
params['networkFrom'] = _networkFor(from);
83
params['networkTo'] = _networkFor(to);
84
}
83
- final uri = Uri.https(apiBaseUrl, ratePath, params);
84
- final response = await get(uri);
85
86
- if (response.statusCode != 200)
87
- throw Exception('Unexpected http status: ${response.statusCode}');
86
+ // Maximum of 2 attempts to fetch limits
87
+ for (int i = 0; i < 2; i++) {
88
+ final uri = Uri.https(apiBaseUrl, ratePath, params);
89
+ final response = await get(uri);
90
89
- final responseJSON = json.decode(response.body) as Map<String, dynamic>;
90
- return Limits(min: responseJSON['minAmount'] as double?);
91
+ if (response.statusCode == 200) {
92
+ final responseJSON = json.decode(response.body) as Map<String, dynamic>;
93
+ final minAmount = responseJSON['minAmount'];
94
+ final maxAmount = responseJSON['maxAmount'];
95
+ return Limits(min: _toDouble(minAmount), max: _toDouble(maxAmount));
96
+ } else if (response.statusCode == 422) {
97
+ final errorResponse = json.decode(response.body) as Map<String, dynamic>;
98
+ if (errorResponse.containsKey('minAmount')) {
99
+ params['amount'] = errorResponse['minAmount'].toString();
100
+ continue;
101
+ }
102
+ throw Exception('Error 422: ${errorResponse['message'] ?? 'Unknown error'}');
103
+ } else {
104
+ throw Exception('Unexpected HTTP status: ${response.statusCode}');
105
+ }
106
+ }
107
+
108
+ throw Exception('Failed to fetch limits after retrying.');
109
}
110
111
@override
@@ -279,4 +297,15 @@ class ExolixExchangeProvider extends ExchangeProvider {
297
298
String _normalizeAddress(String address) =>
299
address.startsWith('bitcoincash:') ? address.replaceFirst('bitcoincash:', '') : address;
300
+
301
+ static double? _toDouble(dynamic value) {
302
+ if (value is int) {
303
+ return value.toDouble();
304
+ } else if (value is double) {
305
+ return value;
306
+ } else if (value is String) {
307
+ return double.tryParse(value);
308
+ }
309
+ return null;
310
+ }
311
}
lib/exchange/provider/letsexchange_exchange_provider.dart
+4
@@ -168,6 +168,7 @@ class LetsExchangeExchangeProvider extends ExchangeProvider {
168
final status = responseJSON['status'] as String;
169
final createdAtString = responseJSON['created_at'] as String;
170
final expiredAtTimestamp = responseJSON['expired_at'] as int;
171
+ final extraId = responseJSON['deposit_extra_id'] as String?;
172
173
final createdAt = DateTime.parse(createdAtString);
174
final expiredAt = DateTime.fromMillisecondsSinceEpoch(expiredAtTimestamp * 1000);
@@ -199,6 +200,7 @@ class LetsExchangeExchangeProvider extends ExchangeProvider {
200
state: TradeState.deserialize(raw: status),
201
createdAt: createdAt,
202
expiredAt: expiredAt,
203
+ extraId: extraId,
204
);
205
} catch (e) {
206
log(e.toString());
@@ -231,6 +233,7 @@ class LetsExchangeExchangeProvider extends ExchangeProvider {
233
final status = responseJSON['status'] as String;
234
final createdAtString = responseJSON['created_at'] as String;
235
final expiredAtTimestamp = responseJSON['expired_at'] as int;
236
+ final extraId = responseJSON['deposit_extra_id'] as String?;
237
238
final createdAt = DateTime.parse(createdAtString);
239
final expiredAt = DateTime.fromMillisecondsSinceEpoch(expiredAtTimestamp * 1000);
@@ -249,6 +252,7 @@ class LetsExchangeExchangeProvider extends ExchangeProvider {
252
createdAt: createdAt,
253
expiredAt: expiredAt,
254
isRefund: status == 'refund',
255
+ extraId: extraId,
256
);
257
}
258
lib/exchange/provider/sideshift_exchange_provider.dart
+5
-1
@@ -203,6 +203,7 @@ class SideShiftExchangeProvider extends ExchangeProvider {
203
final inputAddress = responseJSON['depositAddress'] as String;
204
final settleAddress = responseJSON['settleAddress'] as String;
205
final depositAmount = responseJSON['depositAmount'] as String?;
206
+ final depositMemo = responseJSON['depositMemo'] as String?;
207
208
return Trade(
209
id: id,
@@ -217,6 +218,7 @@ class SideShiftExchangeProvider extends ExchangeProvider {
218
payoutAddress: settleAddress,
219
createdAt: DateTime.now(),
220
isSendAll: isSendAll,
221
+ extraId: depositMemo
222
);
223
}
224
@@ -251,6 +253,7 @@ class SideShiftExchangeProvider extends ExchangeProvider {
253
final isVariable = (responseJSON['type'] as String) == 'variable';
254
final expiredAtRaw = responseJSON['expiresAt'] as String;
255
final expiredAt = isVariable ? null : DateTime.tryParse(expiredAtRaw)?.toLocal();
256
+ final depositMemo = responseJSON['depositMemo'] as String?;
257
258
return Trade(
259
id: id,
@@ -261,7 +264,8 @@ class SideShiftExchangeProvider extends ExchangeProvider {
264
amount: expectedSendAmount ?? '',
265
state: TradeState.deserialize(raw: status ?? 'created'),
266
expiredAt: expiredAt,
264
- payoutAddress: settleAddress);
267
+ payoutAddress: settleAddress,
268
+ extraId: depositMemo);
269
}
270
271
Future<String> _createQuote(TradeRequest request) async {
lib/exchange/provider/stealth_ex_exchange_provider.dart
+4
@@ -154,6 +154,7 @@ class StealthExExchangeProvider extends ExchangeProvider {
154
final receiveAmount = toDouble(withdrawal['amount']);
155
final status = responseJSON['status'] as String;
156
final createdAtString = responseJSON['created_at'] as String;
157
+ final extraId = deposit['extra_id'] as String?;
158
159
final createdAt = DateTime.parse(createdAtString);
160
final expiredAt = validUntil != null
@@ -188,6 +189,7 @@ class StealthExExchangeProvider extends ExchangeProvider {
189
state: TradeState.deserialize(raw: status),
190
createdAt: createdAt,
191
expiredAt: expiredAt,
192
+ extraId: extraId,
193
);
194
} catch (e) {
195
log(e.toString());
@@ -220,6 +222,7 @@ class StealthExExchangeProvider extends ExchangeProvider {
222
final status = responseJSON['status'] as String;
223
final createdAtString = responseJSON['created_at'] as String;
224
final createdAt = DateTime.parse(createdAtString);
225
+ final extraId = deposit['extra_id'] as String?;
226
227
return Trade(
228
id: respId,
@@ -234,6 +237,7 @@ class StealthExExchangeProvider extends ExchangeProvider {
237
state: TradeState.deserialize(raw: status),
238
createdAt: createdAt,
239
isRefund: status == 'refunded',
240
+ extraId: extraId,
241
);
242
}
243
lib/exchange/provider/trocador_exchange_provider.dart
+4
-1
@@ -171,7 +171,8 @@ class TrocadorExchangeProvider extends ExchangeProvider {
171
if (!isFixedRateMode) 'amount_from': request.fromAmount,
172
if (isFixedRateMode) 'amount_to': request.toAmount,
173
'address': request.toAddress,
174
- 'refund': request.refundAddress
174
+ 'refund': request.refundAddress,
175
+ 'refund_memo' : '0',
176
};
177
178
if (isFixedRateMode) {
@@ -262,6 +263,7 @@ class TrocadorExchangeProvider extends ExchangeProvider {
263
final password = responseJSON['password'] as String;
264
final providerId = responseJSON['id_provider'] as String;
265
final providerName = responseJSON['provider'] as String;
266
+ final addressProviderMemo = responseJSON['address_provider_memo'] as String?;
267
268
return Trade(
269
id: id,
@@ -277,6 +279,7 @@ class TrocadorExchangeProvider extends ExchangeProvider {
279
password: password,
280
providerId: providerId,
281
providerName: providerName,
282
+ extraId: addressProviderMemo,
283
);
284
});
285
}
lib/exchange/trade.dart
+2
@@ -143,6 +143,7 @@ class Trade extends HiveObject {
143
isRefund: map['isRefund'] as bool?,
144
isSendAll: map['isSendAll'] as bool?,
145
router: map['router'] as String?,
146
+ extraId: map['extra_id'] as String?,
147
);
148
}
149
@@ -162,6 +163,7 @@ class Trade extends HiveObject {
163
'isRefund': isRefund,
164
'isSendAll': isSendAll,
165
'router': router,
166
+ 'extra_id': extraId,
167
};
168
}
169
lib/src/screens/trade_details/trade_details_list_card.dart
+14
-2
@@ -8,7 +8,8 @@ class TradeDetailsListCardItem extends StandartListItem {
8
{required this.id,
9
required this.createdAt,
10
required this.pair,
11
- required this.onTap})
11
+ required this.onTap,
12
+ this.extraId})
13
: super(title: '', value: '');
14
15
factory TradeDetailsListCardItem.tradeDetails(
@@ -16,9 +17,19 @@ class TradeDetailsListCardItem extends StandartListItem {
17
required String createdAt,
18
required CryptoCurrency from,
19
required CryptoCurrency to,
19
- required void Function(BuildContext) onTap}) {
20
+ required void Function(BuildContext) onTap,
21
+ String? extraId}) {
22
+
23
+
24
+ final extraIdTitle = from == CryptoCurrency.xrp
25
+ ? S.current.destination_tag
26
+ : from == CryptoCurrency.xlm
27
+ ? S.current.memo
28
+ : S.current.extra_id;
29
+
30
return TradeDetailsListCardItem(
31
id: '${S.current.trade_details_id} ${formatAsText(id)}',
32
+ extraId: extraId != null ? '$extraIdTitle $extraId' : null,
33
createdAt: formatAsText(createdAt),
34
pair: '${formatAsText(from)} → ${formatAsText(to)}',
35
onTap: onTap);
@@ -27,6 +38,7 @@ class TradeDetailsListCardItem extends StandartListItem {
38
final String id;
39
final String createdAt;
40
final String pair;
41
+ final String? extraId;
42
final void Function(BuildContext) onTap;
43
44
static String formatAsText<T>(T value) => value?.toString() ?? '';
lib/src/screens/trade_details/trade_details_page.dart
+1
@@ -69,6 +69,7 @@ class TradeDetailsPageBodyState extends State<TradeDetailsPageBody> {
69
if (item is TradeDetailsListCardItem)
70
return TradeDetailsStandardListCard(
71
id: item.id,
72
+ extraId: item.extraId,
73
create: item.createdAt,
74
pair: item.pair,
75
currentTheme: tradeDetailsViewModel.settingsStore.currentTheme.type,
lib/src/widgets/standard_list_card.dart
+12
@@ -6,12 +6,14 @@ import 'package:cake_wallet/themes/theme_base.dart';
6
class TradeDetailsStandardListCard extends StatelessWidget {
7
TradeDetailsStandardListCard(
8
{required this.id,
9
+ this.extraId,
10
required this.create,
11
required this.pair,
12
required this.onTap,
13
required this.currentTheme});
14
15
final String id;
16
+ final String? extraId;
17
final String create;
18
final String pair;
19
final ThemeType currentTheme;
@@ -57,6 +59,16 @@ class TradeDetailsStandardListCard extends StatelessWidget {
59
SizedBox(
60
height: 8,
61
),
62
+ if (extraId != null && extraId!.isNotEmpty)
63
+ Padding(
64
+ padding: const EdgeInsets.only(bottom: 8.0),
65
+ child: Text(extraId!,
66
+ style: TextStyle(
67
+ fontSize: 16,
68
+ fontFamily: 'Lato',
69
+ fontWeight: FontWeight.w400,
70
+ color: textColor)),
71
+ ),
72
Text(create,
73
style: TextStyle(
74
fontSize: 12,
lib/view_model/exchange/exchange_trade_view_model.dart
+16
-13
@@ -146,7 +146,7 @@ abstract class ExchangeTradeViewModelBase with Store {
146
147
void _updateItems() {
148
final tagFrom =
149
- tradesStore.trade!.from.tag != null ? '${tradesStore.trade!.from.tag}' + ' ' : '';
149
+ tradesStore.trade!.from.tag != null ? '${tradesStore.trade!.from.tag}' + ' ' : '';
150
final tagTo = tradesStore.trade!.to.tag != null ? '${tradesStore.trade!.to.tag}' + ' ' : '';
151
items.clear();
152
@@ -159,16 +159,6 @@ abstract class ExchangeTradeViewModelBase with Store {
159
),
160
);
161
162
- if (trade.extraId != null) {
163
- final title = trade.from == CryptoCurrency.xrp
164
- ? S.current.destination_tag
165
- : trade.from == CryptoCurrency.xlm
166
- ? S.current.memo
167
- : S.current.extra_id;
168
-
169
- items.add(ExchangeTradeItem(title: title, data: '${trade.extraId}', isCopied: false));
170
- }
171
-
162
items.addAll([
163
ExchangeTradeItem(
164
title: S.current.amount,
@@ -176,7 +166,7 @@ abstract class ExchangeTradeViewModelBase with Store {
166
isCopied: true,
167
),
168
ExchangeTradeItem(
179
- title: S.current.estimated_receive_amount +':',
169
+ title: S.current.estimated_receive_amount + ':',
170
data: '${tradesStore.trade?.receiveAmount} ${trade.to}',
171
isCopied: true,
172
),
@@ -185,12 +175,25 @@ abstract class ExchangeTradeViewModelBase with Store {
175
data: trade.inputAddress ?? '',
176
isCopied: true,
177
),
178
+ ]);
179
+
180
+ if (trade.extraId != null) {
181
+ final title = trade.from == CryptoCurrency.xrp
182
+ ? S.current.destination_tag
183
+ : trade.from == CryptoCurrency.xlm
184
+ ? S.current.memo
185
+ : S.current.extra_id;
186
+
187
+ items.add(ExchangeTradeItem(title: title, data: '${trade.extraId}', isCopied: true));
188
+ }
189
+
190
+ items.add(
191
ExchangeTradeItem(
192
title: S.current.arrive_in_this_address('${tradesStore.trade!.to}', tagTo) + ':',
193
data: trade.payoutAddress ?? '',
194
isCopied: true,
195
),
193
- ]);
196
+ );
197
}
198
199
static bool _checkIfCanSend(TradesStore tradesStore, WalletBase wallet) {
lib/view_model/trade_details_view_model.dart
+1
@@ -153,6 +153,7 @@ abstract class TradeDetailsViewModelBase with Store {
153
154
items.add(TradeDetailsListCardItem.tradeDetails(
155
id: trade.id,
156
+ extraId: trade.extraId,
157
createdAt: trade.createdAt != null ? dateFormat.format(trade.createdAt!) : '',
158
from: trade.from,
159
to: trade.to,