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;