Add trocador exchange provider
Godwin Asuquo committed
Feb 6, 2023 at 21:20 UTC
032a8c8c3387ab9cb87d54fc4aa21977dc30266a
19 files changed
+453
-108
assets/images/trocador.png
Binary files /dev/null and b/assets/images/trocador.png differ
lib/entities/preferences_key.dart
+1
@@ -13,6 +13,7 @@ class PreferencesKey {
13
static const allowBiometricalAuthenticationKey =
14
'allow_biometrical_authentication';
15
static const disableExchangeKey = 'disable_exchange';
16
+ static const exchangeStatusKey = 'exchange_status';
17
static const currentTheme = 'current_theme';
18
static const isDarkThemeLegacy = 'dark_theme';
19
static const displayActionListModeKey = 'display_list_mode';
lib/exchange/changenow/changenow_exchange_provider.dart
+1
@@ -269,6 +269,7 @@ class ChangeNowExchangeProvider extends ExchangeProvider {
269
: currency.title.toLowerCase();
270
}
271
}
272
+
273
}
274
275
String normalizeCryptoCurrency(CryptoCurrency currency) {
lib/exchange/exchange_provider.dart
+1
@@ -14,6 +14,7 @@ abstract class ExchangeProvider {
14
bool get isAvailable;
15
bool get isEnabled;
16
bool get supportsFixedRate;
17
+ bool get shouldUseOnionAddress => false;
18
19
@override
20
String toString() => title;
lib/exchange/exchange_provider_description.dart
+14
-13
@@ -1,31 +1,30 @@
1
import 'package:cw_core/enumerable_item.dart';
2
3
-class ExchangeProviderDescription extends EnumerableItem<int>
4
- with Serializable<int> {
5
- const ExchangeProviderDescription({
6
- required String title,
7
- required int raw,
8
- required this.image,
9
- this.horizontalLogo = false})
3
+class ExchangeProviderDescription extends EnumerableItem<int> with Serializable<int> {
4
+ const ExchangeProviderDescription(
5
+ {required String title, required int raw, required this.image, this.horizontalLogo = false})
6
: super(title: title, raw: raw);
7
8
final bool horizontalLogo;
9
final String image;
10
15
- static const xmrto = ExchangeProviderDescription(title: 'XMR.TO', raw: 0, image: 'assets/images/xmrto.png');
11
+ static const xmrto =
12
+ ExchangeProviderDescription(title: 'XMR.TO', raw: 0, image: 'assets/images/xmrto.png');
13
static const changeNow =
14
ExchangeProviderDescription(title: 'ChangeNOW', raw: 1, image: 'assets/images/changenow.png');
15
static const morphToken =
16
ExchangeProviderDescription(title: 'MorphToken', raw: 2, image: 'assets/images/morph.png');
17
21
- static const sideShift =
18
+ static const sideShift =
19
ExchangeProviderDescription(title: 'SideShift', raw: 3, image: 'assets/images/sideshift.png');
20
24
- static const simpleSwap =
25
- ExchangeProviderDescription(title: 'SimpleSwap', raw: 4, image: 'assets/images/simpleSwap.png');
21
+ static const simpleSwap = ExchangeProviderDescription(
22
+ title: 'SimpleSwap', raw: 4, image: 'assets/images/simpleSwap.png');
23
27
- static const all =
28
- ExchangeProviderDescription(title: 'All trades', raw: 5, image:'');
24
+ static const trocador =
25
+ ExchangeProviderDescription(title: 'Trocador', raw: 5, image: 'assets/images/trocador.png');
26
+
27
+ static const all = ExchangeProviderDescription(title: 'All trades', raw: 6, image: '');
28
29
static ExchangeProviderDescription deserialize({required int raw}) {
30
switch (raw) {
@@ -40,6 +39,8 @@ class ExchangeProviderDescription extends EnumerableItem<int>
39
case 4:
40
return simpleSwap;
41
case 5:
42
+ return trocador;
43
+ case 6:
44
return all;
45
default:
46
throw Exception('Unexpected token: $raw for ExchangeProviderDescription deserialize');
lib/exchange/trocador/trocador_exchange_provider.dart
new
+268
@@ -0,0 +1,268 @@
1
+import 'dart:convert';
2
+
3
+import 'package:cake_wallet/exchange/exchange_pair.dart';
4
+import 'package:cake_wallet/exchange/exchange_provider.dart';
5
+import 'package:cake_wallet/exchange/trade_state.dart';
6
+import 'package:cake_wallet/exchange/trocador/trocador_request.dart';
7
+import 'package:cw_core/crypto_currency.dart';
8
+import 'package:cake_wallet/exchange/trade_request.dart';
9
+import 'package:cake_wallet/exchange/trade.dart';
10
+import 'package:cake_wallet/exchange/limits.dart';
11
+import 'package:cake_wallet/exchange/exchange_provider_description.dart';
12
+import 'package:cake_wallet/.secrets.g.dart' as secrets;
13
+import 'package:http/http.dart';
14
+
15
+class TrocadorExchangeProvider extends ExchangeProvider {
16
+ TrocadorExchangeProvider()
17
+ : _lastUsedRateId = '',
18
+ super(pairList: _supportedPairs());
19
+
20
+ static const List<CryptoCurrency> _notSupported = [
21
+ CryptoCurrency.xhv,
22
+ CryptoCurrency.dcr,
23
+ CryptoCurrency.oxt,
24
+ CryptoCurrency.pivx,
25
+ CryptoCurrency.scrt,
26
+ CryptoCurrency.stx,
27
+ CryptoCurrency.bttc,
28
+ CryptoCurrency.zaddr,
29
+ CryptoCurrency.usdcpoly,
30
+ CryptoCurrency.maticpoly,
31
+ ];
32
+
33
+ static List<ExchangePair> _supportedPairs() {
34
+ final supportedCurrencies =
35
+ CryptoCurrency.all.where((element) => !_notSupported.contains(element)).toList();
36
+
37
+ return supportedCurrencies
38
+ .map((i) => supportedCurrencies.map((k) => ExchangePair(from: i, to: k, reverse: true)))
39
+ .expand((i) => i)
40
+ .toList();
41
+ }
42
+
43
+ static const onionApiAuthority = 'trocadorfyhlu27aefre5u7zri66gudtzdyelymftvr4yjwcxhfaqsid.onion';
44
+ static const clearNetAuthority = 'trocador.app';
45
+ static const apiKey = secrets.trocadorApiKey;
46
+ static const newRatePath = '/api/new_rate';
47
+ static const createTradePath = 'api/new_trade';
48
+ static const tradePath = 'api/trade';
49
+ String _lastUsedRateId;
50
+
51
+ @override
52
+ Future<bool> checkIsAvailable() async => true;
53
+
54
+ @override
55
+ Future<Trade> createTrade({required TradeRequest request, required bool isFixedRateMode}) {
56
+ final _request = request as TrocadorRequest;
57
+ return _createTrade(request: _request, isFixedRateMode: isFixedRateMode);
58
+ }
59
+
60
+ Future<Trade> _createTrade({
61
+ required TrocadorRequest request,
62
+ required bool isFixedRateMode,
63
+ }) async {
64
+ final params = <String, String>{
65
+ 'api_key': apiKey,
66
+ 'ticker_from': request.from.title.toLowerCase(),
67
+ 'ticker_to': request.to.title.toLowerCase(),
68
+ 'network_from': _networkFor(request.from),
69
+ 'network_to': _networkFor(request.to),
70
+ 'payment': isFixedRateMode ? 'True' : 'False',
71
+ 'min_kycrating': 'C',
72
+ 'markup': '3',
73
+ 'best_only': 'True',
74
+ if (!isFixedRateMode) 'amount_from': request.fromAmount,
75
+ if (isFixedRateMode) 'amount_to': request.toAmount,
76
+ 'address': request.address,
77
+ 'refund': request.refundAddress
78
+ };
79
+
80
+ if (isFixedRateMode) {
81
+ await fetchRate(
82
+ from: request.from,
83
+ to: request.to,
84
+ amount: double.tryParse(request.toAmount) ?? 0,
85
+ isFixedRateMode: true,
86
+ isReceiveAmount: true,
87
+ );
88
+ params['id'] = _lastUsedRateId;
89
+ }
90
+
91
+ final String apiAuthority = shouldUseOnionAddress ? await _getAuthority() : clearNetAuthority;
92
+
93
+ final uri = Uri.https(apiAuthority, createTradePath, params);
94
+ final response = await get(uri);
95
+
96
+ if (response.statusCode == 400) {
97
+ final responseJSON = json.decode(response.body) as Map<String, dynamic>;
98
+ final error = responseJSON['error'] as String;
99
+ final message = responseJSON['message'] as String;
100
+ throw Exception('${error}\n$message');
101
+ }
102
+
103
+ if (response.statusCode != 200) {
104
+ throw Exception('Unexpected http status: ${response.statusCode}');
105
+ }
106
+
107
+ final responseJSON = json.decode(response.body) as Map<String, dynamic>;
108
+ final id = responseJSON['trade_id'] as String;
109
+ final inputAddress = responseJSON['address_provider'] as String;
110
+ final refundAddress = responseJSON['refund_address'] as String;
111
+ final status = responseJSON['status'] as String;
112
+ final state = TradeState.deserialize(raw: status);
113
+ final payoutAddress = responseJSON['address_user'] as String;
114
+ final date = responseJSON['date'] as String;
115
+
116
+ return Trade(
117
+ id: id,
118
+ from: request.from,
119
+ to: request.to,
120
+ provider: description,
121
+ inputAddress: inputAddress,
122
+ refundAddress: refundAddress,
123
+ state: state,
124
+ createdAt: DateTime.tryParse(date)?.toLocal(),
125
+ amount: responseJSON['amount_from']?.toString() ?? request.fromAmount,
126
+ payoutAddress: payoutAddress);
127
+ }
128
+
129
+ @override
130
+ ExchangeProviderDescription get description => ExchangeProviderDescription.trocador;
131
+
132
+ @override
133
+ Future<Limits> fetchLimits(
134
+ {required CryptoCurrency from,
135
+ required CryptoCurrency to,
136
+ required bool isFixedRateMode}) async {
137
+ //TODO: implement limits from trocador api
138
+ return Limits(
139
+ min: 0.0,
140
+ );
141
+ }
142
+
143
+ @override
144
+ Future<double> fetchRate(
145
+ {required CryptoCurrency from,
146
+ required CryptoCurrency to,
147
+ required double amount,
148
+ required bool isFixedRateMode,
149
+ required bool isReceiveAmount}) async {
150
+ try {
151
+ if (amount == 0) {
152
+ return 0.0;
153
+ }
154
+
155
+ final String apiAuthority = shouldUseOnionAddress ? await _getAuthority() : clearNetAuthority;
156
+
157
+ final params = <String, String>{
158
+ 'api_key': apiKey,
159
+ 'ticker_from': from.title.toLowerCase(),
160
+ 'ticker_to': to.title.toLowerCase(),
161
+ 'network_from': _networkFor(from),
162
+ 'network_to': _networkFor(to),
163
+ 'amount_from': amount.toString(),
164
+ 'amount_to': amount.toString(),
165
+ 'payment': isFixedRateMode ? 'True' : 'False',
166
+ 'min_kycrating': 'C',
167
+ 'markup': '3',
168
+ 'best_only': 'True',
169
+ };
170
+
171
+ final uri = Uri.https(apiAuthority, newRatePath, params);
172
+ final response = await get(uri);
173
+ final responseJSON = json.decode(response.body) as Map<String, dynamic>;
174
+ final fromAmount = double.parse(responseJSON['amount_from'].toString());
175
+ final toAmount = double.parse(responseJSON['amount_to'].toString());
176
+ final rateId = responseJSON['trade_id'] as String? ?? '';
177
+
178
+ if (rateId.isNotEmpty) {
179
+ _lastUsedRateId = rateId;
180
+ }
181
+
182
+ return isReceiveAmount ? (amount / fromAmount) : (toAmount / amount);
183
+ } catch (e) {
184
+ print(e.toString());
185
+ return 0.0;
186
+ }
187
+ }
188
+
189
+ @override
190
+ Future<Trade> findTradeById({required String id}) async {
191
+ final String apiAuthority = shouldUseOnionAddress ? await _getAuthority() : clearNetAuthority;
192
+ final uri = Uri.https(apiAuthority, tradePath, {'api_key': apiKey, 'id': id});
193
+ return get(uri).then((response) {
194
+ if (response.statusCode != 200) {
195
+ throw Exception('Unexpected http status: ${response.statusCode}');
196
+ }
197
+
198
+ final responseListJson = json.decode(response.body) as List;
199
+
200
+ final responseJSON = responseListJson.first;
201
+ final id = responseJSON['trade_id'] as String;
202
+ final inputAddress = responseJSON['address_user'] as String;
203
+ final refundAddress = responseJSON['refund_address'] as String;
204
+ final payoutAddress = responseJSON['address_provider'] as String;
205
+ final fromAmount = responseJSON['amount_from']?.toString() ?? '0';
206
+ final from = CryptoCurrency.fromString(responseJSON['ticker_from'] as String);
207
+ final to = CryptoCurrency.fromString(responseJSON['ticker_to'] as String);
208
+ final state = TradeState.deserialize(raw: responseJSON['status'] as String);
209
+ final date = DateTime.parse(responseJSON['date'] as String);
210
+
211
+ return Trade(
212
+ id: id,
213
+ from: from,
214
+ to: to,
215
+ provider: description,
216
+ inputAddress: inputAddress,
217
+ refundAddress: refundAddress,
218
+ createdAt: date,
219
+ amount: fromAmount,
220
+ state: state,
221
+ payoutAddress: payoutAddress,
222
+ );
223
+ });
224
+ }
225
+
226
+ @override
227
+ bool get isAvailable => true;
228
+
229
+ @override
230
+ bool get isEnabled => true;
231
+
232
+ @override
233
+ bool get supportsFixedRate => true;
234
+
235
+ @override
236
+ bool get shouldUseOnionAddress => true;
237
+
238
+ @override
239
+ String get title => 'Trocador';
240
+
241
+ String _networkFor(CryptoCurrency currency) {
242
+ switch (currency) {
243
+ case CryptoCurrency.usdt:
244
+ return CryptoCurrency.btc.title.toLowerCase();
245
+ default:
246
+ return currency.tag != null ? _normalizeTag(currency.tag!) : 'Mainnet';
247
+ }
248
+ }
249
+
250
+ String _normalizeTag(String tag) {
251
+ switch (tag) {
252
+ case 'ETH':
253
+ return 'ERC20';
254
+ default:
255
+ return tag.toLowerCase();
256
+ }
257
+ }
258
+
259
+ Future<String> _getAuthority() async {
260
+ try {
261
+ final uri = Uri.https(onionApiAuthority, '/api/trade');
262
+ await get(uri);
263
+ return onionApiAuthority;
264
+ } catch (e) {
265
+ return clearNetAuthority;
266
+ }
267
+ }
268
+}
lib/exchange/trocador/trocador_request.dart
new
+21
@@ -0,0 +1,21 @@
1
+import 'package:cake_wallet/exchange/trade_request.dart';
2
+import 'package:cw_core/crypto_currency.dart';
3
+
4
+class TrocadorRequest extends TradeRequest {
5
+ TrocadorRequest(
6
+ {required this.from,
7
+ required this.to,
8
+ required this.address,
9
+ required this.fromAmount,
10
+ required this.toAmount,
11
+ required this.refundAddress,
12
+ required this.isReverse});
13
+
14
+ CryptoCurrency from;
15
+ CryptoCurrency to;
16
+ String address;
17
+ String fromAmount;
18
+ String toAmount;
19
+ String refundAddress;
20
+ bool isReverse;
21
+}
lib/src/screens/dashboard/widgets/trade_row.dart
+37
-38
@@ -9,7 +9,8 @@ class TradeRow extends StatelessWidget {
9
required this.to,
10
required this.createdAtFormattedDate,
11
this.onTap,
12
- this.formattedAmount,});
12
+ this.formattedAmount,
13
+ });
14
15
final VoidCallback? onTap;
16
final ExchangeProviderDescription provider;
@@ -35,47 +36,40 @@ class TradeRow extends StatelessWidget {
36
SizedBox(width: 12),
37
Expanded(
38
child: Column(
38
- mainAxisSize: MainAxisSize.min,
39
- children: [
40
- Row(
41
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
42
- children: <Widget>[
43
- Text('${from.toString()} → ${to.toString()}',
44
- style: TextStyle(
45
- fontSize: 16,
46
- fontWeight: FontWeight.w500,
47
- color: Theme.of(context).accentTextTheme!.headline2!.backgroundColor!
48
- )),
49
- formattedAmount != null
50
- ? Text(formattedAmount! + ' ' + amountCrypto,
51
- style: TextStyle(
52
- fontSize: 16,
53
- fontWeight: FontWeight.w500,
54
- color: Theme.of(context).accentTextTheme!.headline2!.backgroundColor!
55
- ))
56
- : Container()
57
- ]),
58
- SizedBox(height: 5),
59
- Row(
60
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
61
- children: <Widget>[
62
- if (createdAtFormattedDate != null)
63
- Text(createdAtFormattedDate!,
64
- style: TextStyle(
65
- fontSize: 14,
66
- color: Theme.of(context).textTheme!
67
- .overline!.backgroundColor!))
68
- ])
69
- ],
70
- )
71
- )
39
+ mainAxisSize: MainAxisSize.min,
40
+ children: [
41
+ Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: <Widget>[
42
+ Text('${from.toString()} → ${to.toString()}',
43
+ style: TextStyle(
44
+ fontSize: 16,
45
+ fontWeight: FontWeight.w500,
46
+ color: Theme.of(context).accentTextTheme!.headline2!.backgroundColor!)),
47
+ formattedAmount != null
48
+ ? Text(formattedAmount! + ' ' + amountCrypto,
49
+ style: TextStyle(
50
+ fontSize: 16,
51
+ fontWeight: FontWeight.w500,
52
+ color:
53
+ Theme.of(context).accentTextTheme!.headline2!.backgroundColor!))
54
+ : Container()
55
+ ]),
56
+ SizedBox(height: 5),
57
+ Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: <Widget>[
58
+ if (createdAtFormattedDate != null)
59
+ Text(createdAtFormattedDate!,
60
+ style: TextStyle(
61
+ fontSize: 14,
62
+ color: Theme.of(context).textTheme!.overline!.backgroundColor!))
63
+ ])
64
+ ],
65
+ ))
66
],
67
),
68
));
69
}
70
77
- Image? _getPoweredImage(ExchangeProviderDescription provider) {
78
- Image? image;
71
+ Widget? _getPoweredImage(ExchangeProviderDescription provider) {
72
+ Widget? image;
73
74
switch (provider) {
75
case ExchangeProviderDescription.xmrto:
@@ -93,10 +87,15 @@ class TradeRow extends StatelessWidget {
87
case ExchangeProviderDescription.simpleSwap:
88
image = Image.asset('assets/images/simpleSwap.png', width: 36, height: 36);
89
break;
90
+ case ExchangeProviderDescription.trocador:
91
+ image = ClipRRect(
92
+ borderRadius: BorderRadius.circular(50),
93
+ child: Image.asset('assets/images/trocador.png', width: 36, height: 36));
94
+ break;
95
default:
96
image = null;
97
}
98
99
return image;
100
}
102
-}
\ No newline at end of file
101
+}
lib/src/screens/new_wallet/advanced_privacy_settings_page.dart
+11
-2
@@ -1,7 +1,11 @@
1
+import 'package:cake_wallet/entities/fiat_api_mode.dart';
2
import 'package:cake_wallet/src/screens/nodes/widgets/node_form.dart';
3
+import 'package:cake_wallet/src/screens/settings/widgets/settings_choices_cell.dart';
4
import 'package:cake_wallet/src/screens/settings/widgets/settings_switcher_cell.dart';
5
import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart';
6
import 'package:cake_wallet/view_model/advanced_privacy_settings_view_model.dart';
7
+import 'package:cake_wallet/view_model/settings/choices_list_item.dart';
8
+import 'package:cake_wallet/view_model/settings/switcher_list_item.dart';
9
import 'package:flutter_mobx/flutter_mobx.dart';
10
import 'package:flutter/material.dart';
11
import 'package:cake_wallet/generated/i18n.dart';
@@ -48,9 +52,14 @@ class _AdvancedPrivacySettingsBodyState extends State<AdvancedPrivacySettingsBod
52
content: Column(
53
crossAxisAlignment: CrossAxisAlignment.center,
54
children: [
51
- ...widget.privacySettingsViewModel.settings.map(
55
+ ...widget.privacySettingsViewModel.settings.whereType<ChoicesListItem<FiatApiMode>>().map(
56
(item) => Observer(
53
- builder: (_) => SettingsSwitcherCell(
57
+ builder: (_) => SettingsChoicesCell(item)
58
+ ),
59
+ ),
60
+ ...widget.privacySettingsViewModel.settings.whereType<SwitcherListItem>().map(
61
+ (item) => Observer(
62
+ builder: (_) => SettingsSwitcherCell(
63
title: item.title,
64
value: item.value(),
65
onValueChange: item.onValueChange,
lib/src/screens/settings/privacy_page.dart
+19
-12
@@ -1,6 +1,9 @@
1
+import 'package:cake_wallet/entities/fiat_api_mode.dart';
2
import 'package:cake_wallet/generated/i18n.dart';
3
import 'package:cake_wallet/src/screens/base_page.dart';
4
+import 'package:cake_wallet/src/screens/settings/widgets/settings_choices_cell.dart';
5
import 'package:cake_wallet/src/screens/settings/widgets/settings_switcher_cell.dart';
6
+import 'package:cake_wallet/view_model/settings/choices_list_item.dart';
7
import 'package:cake_wallet/view_model/settings/privacy_settings_view_model.dart';
8
import 'package:flutter/material.dart';
9
import 'package:flutter_mobx/flutter_mobx.dart';
@@ -21,18 +24,22 @@ class PrivacyPage extends BasePage {
24
return Column(
25
mainAxisSize: MainAxisSize.min,
26
children: [
24
- SettingsSwitcherCell(
25
- title: S.current.disable_fiat,
26
- value: _privacySettingsViewModel.isFiatDisabled,
27
- onValueChange: (BuildContext context, bool value) {
28
- _privacySettingsViewModel.setFiatMode(value);
29
- }),
30
- SettingsSwitcherCell(
31
- title: S.current.disable_exchange,
32
- value: _privacySettingsViewModel.disableExchange,
33
- onValueChange: (BuildContext context, bool value) {
34
- _privacySettingsViewModel.setEnableExchange(value);
35
- }),
27
+ SettingsChoicesCell(
28
+ ChoicesListItem<FiatApiMode>(
29
+ title: S.current.fiat_api,
30
+ items: FiatApiMode.all,
31
+ selectedItem: _privacySettingsViewModel.fiatApi,
32
+ onItemSelected: (FiatApiMode mode) => _privacySettingsViewModel.setFiatMode(mode),
33
+ ),
34
+ ),
35
+ SettingsChoicesCell(
36
+ ChoicesListItem<FiatApiMode>(
37
+ title: S.current.exchange,
38
+ items: FiatApiMode.all,
39
+ selectedItem: _privacySettingsViewModel.exchangeStatus,
40
+ onItemSelected: (FiatApiMode mode) => _privacySettingsViewModel.setEnableExchange(mode),
41
+ ),
42
+ ),
43
SettingsSwitcherCell(
44
title: S.current.settings_save_recipient_address,
45
value: _privacySettingsViewModel.shouldSaveRecipientAddress,
lib/store/dashboard/trade_filter_store.dart
+13
-3
@@ -12,7 +12,8 @@ abstract class TradeFilterStoreBase with Store {
12
displayChangeNow = true,
13
displaySideShift = true,
14
displayMorphToken = true,
15
- displaySimpleSwap = true;
15
+ displaySimpleSwap = true,
16
+ displayTrocador = true;
17
18
@observable
19
bool displayXMRTO;
@@ -29,8 +30,11 @@ abstract class TradeFilterStoreBase with Store {
30
@observable
31
bool displaySimpleSwap;
32
33
+ @observable
34
+ bool displayTrocador;
35
+
36
@computed
33
- bool get displayAllTrades => displayChangeNow && displaySideShift && displaySimpleSwap;
37
+ bool get displayAllTrades => displayChangeNow && displaySideShift && displaySimpleSwap && displayTrocador;
38
39
@action
40
void toggleDisplayExchange(ExchangeProviderDescription provider) {
@@ -50,6 +54,9 @@ abstract class TradeFilterStoreBase with Store {
54
case ExchangeProviderDescription.morphToken:
55
displayMorphToken = !displayMorphToken;
56
break;
57
+ case ExchangeProviderDescription.trocador:
58
+ displayTrocador = !displayTrocador;
59
+ break;
60
case ExchangeProviderDescription.all:
61
if (displayAllTrades) {
62
displayChangeNow = false;
@@ -57,12 +64,14 @@ abstract class TradeFilterStoreBase with Store {
64
displayXMRTO = false;
65
displayMorphToken = false;
66
displaySimpleSwap = false;
67
+ displayTrocador = false;
68
} else {
69
displayChangeNow = true;
70
displaySideShift = true;
71
displayXMRTO = true;
72
displayMorphToken = true;
73
displaySimpleSwap = true;
74
+ displayTrocador = true;
75
}
76
break;
77
}
@@ -88,7 +97,8 @@ abstract class TradeFilterStoreBase with Store {
97
ExchangeProviderDescription.morphToken)
98
||(displaySimpleSwap &&
99
item.trade.provider ==
91
- ExchangeProviderDescription.simpleSwap))
100
+ ExchangeProviderDescription.simpleSwap)
101
+ ||(displayTrocador && item.trade.provider == ExchangeProviderDescription.trocador))
102
.toList()
103
: _trades;
104
}
lib/store/settings_store.dart
+13
-10
@@ -31,7 +31,7 @@ abstract class SettingsStoreBase with Store {
31
required bool initialSaveRecipientAddress,
32
required FiatApiMode initialFiatMode,
33
required bool initialAllowBiometricalAuthentication,
34
- required bool initialExchangeEnabled,
34
+ required FiatApiMode initialExchangeStatus,
35
required ThemeBase initialTheme,
36
required int initialPinLength,
37
required String initialLanguageCode,
@@ -53,7 +53,7 @@ abstract class SettingsStoreBase with Store {
53
shouldSaveRecipientAddress = initialSaveRecipientAddress,
54
fiatApiMode = initialFiatMode,
55
allowBiometricalAuthentication = initialAllowBiometricalAuthentication,
56
- disableExchange = initialExchangeEnabled,
56
+ exchangeStatus = initialExchangeStatus,
57
currentTheme = initialTheme,
58
pinCodeLength = initialPinLength,
59
languageCode = initialLanguageCode,
@@ -153,9 +153,9 @@ abstract class SettingsStoreBase with Store {
153
PreferencesKey.currentBalanceDisplayModeKey, mode.serialize()));
154
155
reaction(
156
- (_) => disableExchange,
157
- (bool disableExchange) => sharedPreferences.setBool(
158
- PreferencesKey.disableExchangeKey, disableExchange));
156
+ (_) => exchangeStatus,
157
+ (FiatApiMode mode) => sharedPreferences.setInt(
158
+ PreferencesKey.exchangeStatusKey, mode.serialize()));
159
160
this
161
.nodes
@@ -192,7 +192,7 @@ abstract class SettingsStoreBase with Store {
192
bool allowBiometricalAuthentication;
193
194
@observable
195
- bool disableExchange;
195
+ FiatApiMode exchangeStatus;
196
197
@observable
198
ThemeBase currentTheme;
@@ -284,8 +284,9 @@ abstract class SettingsStoreBase with Store {
284
final allowBiometricalAuthentication = sharedPreferences
285
.getBool(PreferencesKey.allowBiometricalAuthenticationKey) ??
286
false;
287
- final disableExchange = sharedPreferences
288
- .getBool(PreferencesKey.disableExchangeKey) ?? false;
287
+ final exchangeStatus = FiatApiMode.deserialize(
288
+ raw: sharedPreferences
289
+ .getInt(PreferencesKey.exchangeStatusKey) ?? FiatApiMode.enabled.raw);
290
final legacyTheme =
291
(sharedPreferences.getBool(PreferencesKey.isDarkThemeLegacy) ?? false)
292
? ThemeType.dark.index
@@ -354,7 +355,7 @@ abstract class SettingsStoreBase with Store {
355
initialSaveRecipientAddress: shouldSaveRecipientAddress,
356
initialFiatMode: currentFiatApiMode,
357
initialAllowBiometricalAuthentication: allowBiometricalAuthentication,
357
- initialExchangeEnabled: disableExchange,
358
+ initialExchangeStatus: exchangeStatus,
359
initialTheme: savedTheme,
360
actionlistDisplayMode: actionListDisplayMode,
361
initialPinLength: pinLength,
@@ -400,7 +401,9 @@ abstract class SettingsStoreBase with Store {
401
allowBiometricalAuthentication = sharedPreferences
402
.getBool(PreferencesKey.allowBiometricalAuthenticationKey) ??
403
allowBiometricalAuthentication;
403
- disableExchange = sharedPreferences.getBool(PreferencesKey.disableExchangeKey) ?? disableExchange;
404
+ exchangeStatus = FiatApiMode.deserialize(
405
+ raw: sharedPreferences
406
+ .getInt(PreferencesKey.exchangeStatusKey) ?? FiatApiMode.enabled.raw);
407
final legacyTheme =
408
(sharedPreferences.getBool(PreferencesKey.isDarkThemeLegacy) ?? false)
409
? ThemeType.dark.index
lib/view_model/advanced_privacy_settings_view_model.dart
+18
-19
@@ -1,5 +1,7 @@
1
import 'package:cake_wallet/entities/fiat_api_mode.dart';
2
import 'package:cake_wallet/store/settings_store.dart';
3
+import 'package:cake_wallet/view_model/settings/choices_list_item.dart';
4
+import 'package:cake_wallet/view_model/settings/settings_list_item.dart';
5
import 'package:cake_wallet/view_model/settings/switcher_list_item.dart';
6
import 'package:cw_core/wallet_type.dart';
7
import 'package:mobx/mobx.dart';
@@ -14,18 +16,19 @@ abstract class AdvancedPrivacySettingsViewModelBase with Store {
16
AdvancedPrivacySettingsViewModelBase(this.type, this._settingsStore)
17
: _addCustomNode = false {
18
settings = [
17
- SwitcherListItem(
18
- title: S.current.disable_fiat,
19
- value: () => _settingsStore.fiatApiMode == FiatApiMode.disabled,
20
- onValueChange: (_, bool value) => setFiatMode(value),
21
- ),
22
- SwitcherListItem(
23
- title: S.current.disable_exchange,
24
- value: () => _settingsStore.disableExchange,
25
- onValueChange: (_, bool value) {
26
- _settingsStore.disableExchange = value;
27
- },
28
- ),
19
+ ChoicesListItem<FiatApiMode>(
20
+ title: S.current.fiat_api,
21
+ items: FiatApiMode.all,
22
+ selectedItem: _settingsStore.fiatApiMode,
23
+ onItemSelected: (FiatApiMode mode) => setFiatMode(mode),
24
+ ),
25
+
26
+ ChoicesListItem<FiatApiMode>(
27
+ title: S.current.exchange,
28
+ items: FiatApiMode.all,
29
+ selectedItem: _settingsStore.exchangeStatus,
30
+ onItemSelected: (FiatApiMode mode) => _settingsStore.exchangeStatus = mode,
31
+ ),
32
SwitcherListItem(
33
title: S.current.add_custom_node,
34
value: () => _addCustomNode,
@@ -34,7 +37,7 @@ abstract class AdvancedPrivacySettingsViewModelBase with Store {
37
];
38
}
39
37
- late List<SwitcherListItem> settings;
40
+ late List<SettingsListItem> settings;
41
42
@observable
43
bool _addCustomNode = false;
@@ -46,11 +49,7 @@ abstract class AdvancedPrivacySettingsViewModelBase with Store {
49
bool get addCustomNode => _addCustomNode;
50
51
@action
49
- void setFiatMode(bool value) {
50
- if (value) {
51
- _settingsStore.fiatApiMode = FiatApiMode.disabled;
52
- return;
53
- }
54
- _settingsStore.fiatApiMode = FiatApiMode.enabled;
52
+ void setFiatMode(FiatApiMode value) {
53
+ _settingsStore.fiatApiMode = value;
54
}
55
}
lib/view_model/dashboard/dashboard_view_model.dart
+7
-1
@@ -1,3 +1,4 @@
1
+import 'package:cake_wallet/entities/fiat_api_mode.dart';
2
import 'package:cake_wallet/wallet_type_utils.dart';
3
import 'package:cw_core/transaction_history.dart';
4
import 'package:cw_core/balance.dart';
@@ -96,6 +97,11 @@ abstract class DashboardViewModelBase with Store {
97
caption: ExchangeProviderDescription.simpleSwap.title,
98
onChanged: () => tradeFilterStore
99
.toggleDisplayExchange(ExchangeProviderDescription.simpleSwap)),
100
+ FilterItem(
101
+ value: () => tradeFilterStore.displayTrocador,
102
+ caption: ExchangeProviderDescription.trocador.title,
103
+ onChanged: () => tradeFilterStore
104
+ .toggleDisplayExchange(ExchangeProviderDescription.trocador)),
105
]
106
},
107
subname = '',
@@ -268,7 +274,7 @@ abstract class DashboardViewModelBase with Store {
274
settingsStore.shouldShowYatPopup = shouldShow;
275
276
@computed
271
- bool get isEnabledExchangeAction => !settingsStore.disableExchange;
277
+ bool get isEnabledExchangeAction => settingsStore.exchangeStatus != FiatApiMode.disabled;
278
279
@observable
280
bool hasExchangeAction;
lib/view_model/exchange/exchange_trade_view_model.dart
+4
@@ -1,6 +1,7 @@
1
import 'dart:async';
2
import 'package:cake_wallet/exchange/sideshift/sideshift_exchange_provider.dart';
3
import 'package:cake_wallet/exchange/simpleswap/simpleswap_exchange_provider.dart';
4
+import 'package:cake_wallet/exchange/trocador/trocador_exchange_provider.dart';
5
import 'package:cw_core/wallet_base.dart';
6
import 'package:cw_core/crypto_currency.dart';
7
import 'package:cake_wallet/exchange/changenow/changenow_exchange_provider.dart';
@@ -46,6 +47,9 @@ abstract class ExchangeTradeViewModelBase with Store {
47
case ExchangeProviderDescription.simpleSwap:
48
_provider = SimpleSwapExchangeProvider();
49
break;
50
+ case ExchangeProviderDescription.trocador:
51
+ _provider = TrocadorExchangeProvider();
52
+ break;
53
}
54
55
_updateItems();
lib/view_model/exchange/exchange_view_model.dart
+15
-1
@@ -7,6 +7,8 @@ import 'package:cake_wallet/exchange/sideshift/sideshift_exchange_provider.dart'
7
import 'package:cake_wallet/exchange/sideshift/sideshift_request.dart';
8
import 'package:cake_wallet/exchange/simpleswap/simpleswap_exchange_provider.dart';
9
import 'package:cake_wallet/exchange/simpleswap/simpleswap_request.dart';
10
+import 'package:cake_wallet/exchange/trocador/trocador_exchange_provider.dart';
11
+import 'package:cake_wallet/exchange/trocador/trocador_request.dart';
12
import 'package:cw_core/transaction_priority.dart';
13
import 'package:cw_core/wallet_base.dart';
14
import 'package:cw_core/crypto_currency.dart';
@@ -60,7 +62,7 @@ abstract class ExchangeViewModelBase with Store {
62
limitsState = LimitsInitialState(),
63
receiveCurrency = wallet.currency,
64
depositCurrency = wallet.currency,
63
- providerList = [ChangeNowExchangeProvider(), SideShiftExchangeProvider(), SimpleSwapExchangeProvider()],
65
+ providerList = [ChangeNowExchangeProvider(), SideShiftExchangeProvider(), SimpleSwapExchangeProvider(), TrocadorExchangeProvider()],
66
selectedProviders = ObservableList<ExchangeProvider>() {
67
const excludeDepositCurrencies = [CryptoCurrency.btt, CryptoCurrency.nano];
68
const excludeReceiveCurrencies = [CryptoCurrency.xlm, CryptoCurrency.xrp,
@@ -449,6 +451,18 @@ abstract class ExchangeViewModelBase with Store {
451
amount = isFixedRateMode ? receiveAmount : depositAmount;
452
}
453
454
+ if (provider is TrocadorExchangeProvider) {
455
+ request = TrocadorRequest(
456
+ from: depositCurrency,
457
+ to: receiveCurrency,
458
+ fromAmount: depositAmount.replaceAll(',', '.'),
459
+ toAmount: receiveAmount.replaceAll(',', '.'),
460
+ refundAddress: depositAddress,
461
+ address: receiveAddress,
462
+ isReverse: isFixedRateMode);
463
+ amount = isFixedRateMode ? receiveAmount : depositAmount;
464
+ }
465
+
466
amount = amount.replaceAll(',', '.');
467
468
if (limitsState is LimitsLoadedSuccessfully) {
lib/view_model/settings/privacy_settings_view_model.dart
+5
-9
@@ -12,27 +12,23 @@ abstract class PrivacySettingsViewModelBase with Store {
12
final SettingsStore _settingsStore;
13
14
@computed
15
- bool get disableExchange => _settingsStore.disableExchange;
15
+ FiatApiMode get exchangeStatus => _settingsStore.exchangeStatus;
16
17
@computed
18
bool get shouldSaveRecipientAddress => _settingsStore.shouldSaveRecipientAddress;
19
20
@computed
21
- bool get isFiatDisabled => _settingsStore.fiatApiMode == FiatApiMode.disabled;
21
+ FiatApiMode get fiatApi => _settingsStore.fiatApiMode;
22
23
@action
24
void setShouldSaveRecipientAddress(bool value) => _settingsStore.shouldSaveRecipientAddress = value;
25
26
@action
27
- void setEnableExchange(bool value) => _settingsStore.disableExchange = value;
27
+ void setEnableExchange(FiatApiMode value) => _settingsStore.exchangeStatus = value;
28
29
@action
30
- void setFiatMode(bool value) {
31
- if (value) {
32
- _settingsStore.fiatApiMode = FiatApiMode.disabled;
33
- return;
34
- }
35
- _settingsStore.fiatApiMode = FiatApiMode.enabled;
30
+ void setFiatMode(FiatApiMode value) {
31
+ _settingsStore.fiatApiMode = value;
32
}
33
34
}
lib/view_model/trade_details_view_model.dart
+4
@@ -6,6 +6,7 @@ import 'package:cake_wallet/exchange/morphtoken/morphtoken_exchange_provider.dar
6
import 'package:cake_wallet/exchange/sideshift/sideshift_exchange_provider.dart';
7
import 'package:cake_wallet/exchange/simpleswap/simpleswap_exchange_provider.dart';
8
import 'package:cake_wallet/exchange/trade.dart';
9
+import 'package:cake_wallet/exchange/trocador/trocador_exchange_provider.dart';
10
import 'package:cake_wallet/exchange/xmrto/xmrto_exchange_provider.dart';
11
import 'package:cake_wallet/store/settings_store.dart';
12
import 'package:cake_wallet/utils/date_formatter.dart';
@@ -48,6 +49,9 @@ abstract class TradeDetailsViewModelBase with Store {
49
case ExchangeProviderDescription.simpleSwap:
50
_provider = SimpleSwapExchangeProvider();
51
break;
52
+ case ExchangeProviderDescription.trocador:
53
+ _provider = TrocadorExchangeProvider();
54
+ break;
55
}
56
57
items = ObservableList<StandartListItem>();
tool/utils/secret_key.dart
+1
@@ -29,6 +29,7 @@ class SecretKey {
29
SecretKey('anypayToken', () => ''),
30
SecretKey('onramperApiKey', () => ''),
31
SecretKey('ioniaClientId', () => ''),
32
+ SecretKey('trocadorApiKey', () => ''),
33
];
34
35
final String name;