Cw 682 integrate stealth ex exchange provider (#1575)

* add stealthEx provider * minor fix * Update pr_test_build.yml * Update dashboard_view_model.dart * update api key * add api key * add secret to linux [skip ci] * fix network param issue * additional fee percent [skip ci] * fix for poly network * add StealthEx tracking link. * minor fix * update name [skip ci] --------- Co-authored-by: OmarHatem <omarh.ismail1@gmail.com>

Serhii committed Sep 6, 2024 at 16:03 UTC f279a222df152c2e03c66fe910a36e6f49360ff5
12 files changed +362 -21
.github/workflows/pr_test_build_android.yml
+2
@@ -168,6 +168,8 @@ jobs:
168 echo "const nanoNowNodesApiKey = '${{ secrets.NANO_NOW_NODES_API_KEY }}';" >> cw_nano/lib/.secrets.g.dart
169 echo "const tronGridApiKey = '${{ secrets.TRON_GRID_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart
170 echo "const tronNowNodesApiKey = '${{ secrets.TRON_NOW_NODES_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart
171 + echo "const stealthExBearerToken = '${{ secrets.STEALTH_EX_BEARER_TOKEN }}';" >> lib/.secrets.g.dart
172 + echo "const stealthExAdditionalFeePercent = '${{ secrets.STEALTH_EX_ADDITIONAL_FEE_PERCENT }}';" >> lib/.secrets.g.dart
173
174 - name: Rename app
175 run: |
.github/workflows/pr_test_build_linux.yml
+2
@@ -154,6 +154,8 @@ jobs:
154 echo "const nanoNowNodesApiKey = '${{ secrets.NANO_NOW_NODES_API_KEY }}';" >> cw_nano/lib/.secrets.g.dart
155 echo "const tronGridApiKey = '${{ secrets.TRON_GRID_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart
156 echo "const tronNowNodesApiKey = '${{ secrets.TRON_NOW_NODES_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart
157 + echo "const stealthExBearerToken = '${{ secrets.STEALTH_EX_BEARER_TOKEN }}';" >> lib/.secrets.g.dart
158 + echo "const stealthExAdditionalFeePercent = '${{ secrets.STEALTH_EX_ADDITIONAL_FEE_PERCENT }}';" >> lib/.secrets.g.dart
159
160 - name: Rename app
161 run: |
assets/images/stealthex.png
Binary files /dev/null and b/assets/images/stealthex.png differ
lib/exchange/exchange_provider_description.dart
+4
@@ -27,6 +27,8 @@ class ExchangeProviderDescription extends EnumerableItem<int> with Serializable<
27 ExchangeProviderDescription(title: 'ThorChain', raw: 8, image: 'assets/images/thorchain.png');
28 static const quantex =
29 ExchangeProviderDescription(title: 'Quantex', raw: 9, image: 'assets/images/quantex.png');
30 + static const stealthEx =
31 + ExchangeProviderDescription(title: 'StealthEx', raw: 10, image: 'assets/images/stealthex.png');
32
33 static ExchangeProviderDescription deserialize({required int raw}) {
34 switch (raw) {
@@ -50,6 +52,8 @@ class ExchangeProviderDescription extends EnumerableItem<int> with Serializable<
52 return thorChain;
53 case 9:
54 return quantex;
55 + case 10:
56 + return stealthEx;
57 default:
58 throw Exception('Unexpected token: $raw for ExchangeProviderDescription deserialize');
59 }
lib/exchange/provider/stealth_ex_exchange_provider.dart new
+299
@@ -0,0 +1,299 @@
1 +import 'dart:convert';
2 +import 'dart:developer';
3 +
4 +import 'package:cake_wallet/.secrets.g.dart' as secrets;
5 +import 'package:cake_wallet/exchange/provider/exchange_provider.dart';
6 +import 'package:cake_wallet/exchange/exchange_provider_description.dart';
7 +import 'package:cake_wallet/exchange/limits.dart';
8 +import 'package:cake_wallet/exchange/trade.dart';
9 +import 'package:cake_wallet/exchange/trade_not_created_exception.dart';
10 +import 'package:cake_wallet/exchange/trade_request.dart';
11 +import 'package:cake_wallet/exchange/trade_state.dart';
12 +import 'package:cake_wallet/exchange/utils/currency_pairs_utils.dart';
13 +import 'package:cw_core/crypto_currency.dart';
14 +import 'package:http/http.dart' as http;
15 +
16 +class StealthExExchangeProvider extends ExchangeProvider {
17 + StealthExExchangeProvider() : super(pairList: supportedPairs(_notSupported));
18 +
19 + static const List<CryptoCurrency> _notSupported = [];
20 +
21 + static final apiKey = secrets.stealthExBearerToken;
22 + static final _additionalFeePercent = double.tryParse(secrets.stealthExAdditionalFeePercent);
23 + static const _baseUrl = 'https://api.stealthex.io';
24 + static const _rangePath = '/v4/rates/range';
25 + static const _amountPath = '/v4/rates/estimated-amount';
26 + static const _exchangesPath = '/v4/exchanges';
27 +
28 + @override
29 + String get title => 'StealthEX';
30 +
31 + @override
32 + bool get isAvailable => true;
33 +
34 + @override
35 + bool get isEnabled => true;
36 +
37 + @override
38 + bool get supportsFixedRate => true;
39 +
40 + @override
41 + ExchangeProviderDescription get description => ExchangeProviderDescription.stealthEx;
42 +
43 + @override
44 + Future<bool> checkIsAvailable() async => true;
45 +
46 + @override
47 + Future<Limits> fetchLimits(
48 + {required CryptoCurrency from,
49 + required CryptoCurrency to,
50 + required bool isFixedRateMode}) async {
51 + final curFrom = isFixedRateMode ? to : from;
52 + final curTo = isFixedRateMode ? from : to;
53 +
54 + final headers = {'Authorization': apiKey, 'Content-Type': 'application/json'};
55 + final body = {
56 + 'route': {
57 + 'from': {'symbol': _getName(curFrom), 'network': _getNetwork(curFrom)},
58 + 'to': {'symbol': _getName(curTo), 'network': _getNetwork(curTo)}
59 + },
60 + 'estimation': isFixedRateMode ? 'reversed' : 'direct',
61 + 'rate': isFixedRateMode ? 'fixed' : 'floating',
62 + 'additional_fee_percent': _additionalFeePercent,
63 + };
64 +
65 + try {
66 + final response = await http.post(Uri.parse(_baseUrl + _rangePath),
67 + headers: headers, body: json.encode(body));
68 + if (response.statusCode != 200) {
69 + throw Exception('StealthEx fetch limits failed: ${response.body}');
70 + }
71 + final responseJSON = json.decode(response.body) as Map<String, dynamic>;
72 + final min = responseJSON['min_amount'] as double?;
73 + final max = responseJSON['max_amount'] as double?;
74 + return Limits(min: min, max: max);
75 + } catch (e) {
76 + log(e.toString());
77 + throw Exception('StealthEx failed to fetch limits');
78 + }
79 + }
80 +
81 + @override
82 + Future<double> fetchRate(
83 + {required CryptoCurrency from,
84 + required CryptoCurrency to,
85 + required double amount,
86 + required bool isFixedRateMode,
87 + required bool isReceiveAmount}) async {
88 + final response = await getEstimatedExchangeAmount(
89 + from: from, to: to, amount: amount, isFixedRateMode: isFixedRateMode);
90 + final estimatedAmount = response['estimated_amount'] as double? ?? 0.0;
91 + return estimatedAmount > 0.0
92 + ? isFixedRateMode
93 + ? amount / estimatedAmount
94 + : estimatedAmount / amount
95 + : 0.0;
96 + }
97 +
98 + @override
99 + Future<Trade> createTrade(
100 + {required TradeRequest request,
101 + required bool isFixedRateMode,
102 + required bool isSendAll}) async {
103 + String? rateId;
104 + String? validUntil;
105 +
106 + try {
107 + if (isFixedRateMode) {
108 + final response = await getEstimatedExchangeAmount(
109 + from: request.fromCurrency,
110 + to: request.toCurrency,
111 + amount: double.parse(request.toAmount),
112 + isFixedRateMode: isFixedRateMode);
113 + rateId = response['rate_id'] as String?;
114 + validUntil = response['valid_until'] as String?;
115 + if (rateId == null) throw TradeNotCreatedException(description);
116 + }
117 +
118 + final headers = {'Authorization': apiKey, 'Content-Type': 'application/json'};
119 + final body = {
120 + 'route': {
121 + 'from': {
122 + 'symbol': _getName(request.fromCurrency),
123 + 'network': _getNetwork(request.fromCurrency)
124 + },
125 + 'to': {'symbol': _getName(request.toCurrency), 'network': _getNetwork(request.toCurrency)}
126 + },
127 + 'estimation': isFixedRateMode ? 'reversed' : 'direct',
128 + 'rate': isFixedRateMode ? 'fixed' : 'floating',
129 + if (isFixedRateMode) 'rate_id': rateId,
130 + 'amount':
131 + isFixedRateMode ? double.parse(request.toAmount) : double.parse(request.fromAmount),
132 + 'address': request.toAddress,
133 + 'refund_address': request.refundAddress,
134 + 'additional_fee_percent': _additionalFeePercent,
135 + };
136 +
137 + final response = await http.post(Uri.parse(_baseUrl + _exchangesPath),
138 + headers: headers, body: json.encode(body));
139 +
140 + if (response.statusCode != 201) {
141 + throw Exception('StealthEx create trade failed: ${response.body}');
142 + }
143 + final responseJSON = json.decode(response.body) as Map<String, dynamic>;
144 + final deposit = responseJSON['deposit'] as Map<String, dynamic>;
145 + final withdrawal = responseJSON['withdrawal'] as Map<String, dynamic>;
146 +
147 + final id = responseJSON['id'] as String;
148 + final from = deposit['symbol'] as String;
149 + final to = withdrawal['symbol'] as String;
150 + final payoutAddress = withdrawal['address'] as String;
151 + final depositAddress = deposit['address'] as String;
152 + final refundAddress = responseJSON['refund_address'] as String;
153 + final depositAmount = toDouble(deposit['amount']);
154 + final receiveAmount = toDouble(withdrawal['amount']);
155 + final status = responseJSON['status'] as String;
156 + final createdAtString = responseJSON['created_at'] as String;
157 +
158 + final createdAt = DateTime.parse(createdAtString);
159 + final expiredAt = validUntil != null
160 + ? DateTime.parse(validUntil)
161 + : DateTime.now().add(Duration(minutes: 5));
162 +
163 +
164 + CryptoCurrency fromCurrency;
165 + if (request.fromCurrency.tag != null && request.fromCurrency.title.toLowerCase() == from) {
166 + fromCurrency = request.fromCurrency;
167 + } else {
168 + fromCurrency = CryptoCurrency.fromString(from);
169 + }
170 +
171 + CryptoCurrency toCurrency;
172 + if (request.toCurrency.tag != null && request.toCurrency.title.toLowerCase() == to) {
173 + toCurrency = request.toCurrency;
174 + } else {
175 + toCurrency = CryptoCurrency.fromString(to);
176 + }
177 +
178 + return Trade(
179 + id: id,
180 + from: fromCurrency,
181 + to: toCurrency,
182 + provider: description,
183 + inputAddress: depositAddress,
184 + payoutAddress: payoutAddress,
185 + refundAddress: refundAddress,
186 + amount: depositAmount.toString(),
187 + receiveAmount: receiveAmount.toString(),
188 + state: TradeState.deserialize(raw: status),
189 + createdAt: createdAt,
190 + expiredAt: expiredAt,
191 + );
192 + } catch (e) {
193 + log(e.toString());
194 + throw TradeNotCreatedException(description);
195 + }
196 + }
197 +
198 + @override
199 + Future<Trade> findTradeById({required String id}) async {
200 + final headers = {'Authorization': apiKey, 'Content-Type': 'application/json'};
201 +
202 + final uri = Uri.parse('$_baseUrl$_exchangesPath/$id');
203 + final response = await http.get(uri, headers: headers);
204 +
205 + if (response.statusCode != 200) {
206 + throw Exception('StealthEx fetch trade failed: ${response.body}');
207 + }
208 + final responseJSON = json.decode(response.body) as Map<String, dynamic>;
209 + final deposit = responseJSON['deposit'] as Map<String, dynamic>;
210 + final withdrawal = responseJSON['withdrawal'] as Map<String, dynamic>;
211 +
212 + final respId = responseJSON['id'] as String;
213 + final from = deposit['symbol'] as String;
214 + final to = withdrawal['symbol'] as String;
215 + final payoutAddress = withdrawal['address'] as String;
216 + final depositAddress = deposit['address'] as String;
217 + final refundAddress = responseJSON['refund_address'] as String;
218 + final depositAmount = toDouble(deposit['amount']);
219 + final receiveAmount = toDouble(withdrawal['amount']);
220 + final status = responseJSON['status'] as String;
221 + final createdAtString = responseJSON['created_at'] as String;
222 + final createdAt = DateTime.parse(createdAtString);
223 +
224 + return Trade(
225 + id: respId,
226 + from: CryptoCurrency.fromString(from),
227 + to: CryptoCurrency.fromString(to),
228 + provider: description,
229 + inputAddress: depositAddress,
230 + payoutAddress: payoutAddress,
231 + refundAddress: refundAddress,
232 + amount: depositAmount.toString(),
233 + receiveAmount: receiveAmount.toString(),
234 + state: TradeState.deserialize(raw: status),
235 + createdAt: createdAt,
236 + isRefund: status == 'refunded',
237 + );
238 + }
239 +
240 + Future<Map<String, dynamic>> getEstimatedExchangeAmount(
241 + {required CryptoCurrency from,
242 + required CryptoCurrency to,
243 + required double amount,
244 + required bool isFixedRateMode}) async {
245 + final headers = {'Authorization': apiKey, 'Content-Type': 'application/json'};
246 +
247 + final body = {
248 + 'route': {
249 + 'from': {'symbol': _getName(from), 'network': _getNetwork(from)},
250 + 'to': {'symbol': _getName(to), 'network': _getNetwork(to)}
251 + },
252 + 'estimation': isFixedRateMode ? 'reversed' : 'direct',
253 + 'rate': isFixedRateMode ? 'fixed' : 'floating',
254 + 'amount': amount,
255 + 'additional_fee_percent': _additionalFeePercent,
256 + };
257 +
258 + try {
259 + final response = await http.post(Uri.parse(_baseUrl + _amountPath),
260 + headers: headers, body: json.encode(body));
261 + if (response.statusCode != 200) return {};
262 + final responseJSON = json.decode(response.body) as Map<String, dynamic>;
263 + final rate = responseJSON['rate'] as Map<String, dynamic>?;
264 + return {
265 + 'estimated_amount': responseJSON['estimated_amount'] as double?,
266 + if (rate != null) 'valid_until': rate['valid_until'] as String?,
267 + if (rate != null) 'rate_id': rate['id'] as String?
268 + };
269 + } catch (e) {
270 + log(e.toString());
271 + return {};
272 + }
273 + }
274 +
275 + double toDouble(dynamic value) {
276 + if (value is int) {
277 + return value.toDouble();
278 + } else if (value is double) {
279 + return value;
280 + } else {
281 + return 0.0;
282 + }
283 + }
284 +
285 + String _getName(CryptoCurrency currency) {
286 + if (currency == CryptoCurrency.usdcEPoly) return 'usdce';
287 + return currency.title.toLowerCase();
288 + }
289 +
290 + String _getNetwork(CryptoCurrency currency) {
291 + if (currency.tag == null) return 'mainnet';
292 +
293 + if (currency == CryptoCurrency.maticpoly) return 'mainnet';
294 +
295 + if (currency.tag == 'POLY') return 'matic';
296 +
297 + return currency.tag!.toLowerCase();
298 + }
299 +}
lib/exchange/trade_state.dart
+1 -1
@@ -40,7 +40,6 @@ class TradeState extends EnumerableItem<String> with Serializable<String> {
40 static const exchanging = TradeState(raw: 'exchanging', title: 'Exchanging');
41 static const sending = TradeState(raw: 'sending', title: 'Sending');
42 static const success = TradeState(raw: 'success', title: 'Success');
43 -
43 static TradeState deserialize({required String raw}) {
44
45 switch (raw) {
@@ -119,6 +118,7 @@ class TradeState extends EnumerableItem<String> with Serializable<String> {
118 case 'refunded':
119 return refunded;
120 case 'confirmation':
121 + case 'verifying':
122 return confirmation;
123 case 'confirmed':
124 return confirmed;
lib/store/dashboard/trade_filter_store.dart
+23 -7
@@ -16,7 +16,8 @@ abstract class TradeFilterStoreBase with Store {
16 displaySimpleSwap = true,
17 displayTrocador = true,
18 displayExolix = true,
19 - displayThorChain = true;
19 + displayThorChain = true,
20 + displayStealthEx = true;
21
22 @observable
23 bool displayXMRTO;
@@ -42,6 +43,9 @@ abstract class TradeFilterStoreBase with Store {
43 @observable
44 bool displayThorChain;
45
46 + @observable
47 + bool displayStealthEx;
48 +
49 @computed
50 bool get displayAllTrades =>
51 displayChangeNow &&
@@ -49,7 +53,8 @@ abstract class TradeFilterStoreBase with Store {
53 displaySimpleSwap &&
54 displayTrocador &&
55 displayExolix &&
52 - displayThorChain;
56 + displayThorChain &&
57 + displayStealthEx;
58
59 @action
60 void toggleDisplayExchange(ExchangeProviderDescription provider) {
@@ -78,6 +83,9 @@ abstract class TradeFilterStoreBase with Store {
83 case ExchangeProviderDescription.thorChain:
84 displayThorChain = !displayThorChain;
85 break;
86 + case ExchangeProviderDescription.stealthEx:
87 + displayStealthEx = !displayStealthEx;
88 + break;
89 case ExchangeProviderDescription.all:
90 if (displayAllTrades) {
91 displayChangeNow = false;
@@ -88,6 +96,7 @@ abstract class TradeFilterStoreBase with Store {
96 displayTrocador = false;
97 displayExolix = false;
98 displayThorChain = false;
99 + displayStealthEx = false;
100 } else {
101 displayChangeNow = true;
102 displaySideShift = true;
@@ -97,6 +106,7 @@ abstract class TradeFilterStoreBase with Store {
106 displayTrocador = true;
107 displayExolix = true;
108 displayThorChain = true;
109 + displayStealthEx = true;
110 }
111 break;
112 }
@@ -112,13 +122,19 @@ abstract class TradeFilterStoreBase with Store {
122 ? _trades
123 .where((item) =>
124 (displayXMRTO && item.trade.provider == ExchangeProviderDescription.xmrto) ||
115 - (displaySideShift && item.trade.provider == ExchangeProviderDescription.sideShift) ||
116 - (displayChangeNow && item.trade.provider == ExchangeProviderDescription.changeNow) ||
117 - (displayMorphToken && item.trade.provider == ExchangeProviderDescription.morphToken) ||
118 - (displaySimpleSwap && item.trade.provider == ExchangeProviderDescription.simpleSwap) ||
125 + (displaySideShift &&
126 + item.trade.provider == ExchangeProviderDescription.sideShift) ||
127 + (displayChangeNow &&
128 + item.trade.provider == ExchangeProviderDescription.changeNow) ||
129 + (displayMorphToken &&
130 + item.trade.provider == ExchangeProviderDescription.morphToken) ||
131 + (displaySimpleSwap &&
132 + item.trade.provider == ExchangeProviderDescription.simpleSwap) ||
133 (displayTrocador && item.trade.provider == ExchangeProviderDescription.trocador) ||
134 (displayExolix && item.trade.provider == ExchangeProviderDescription.exolix) ||
121 - (displayThorChain && item.trade.provider == ExchangeProviderDescription.thorChain))
135 + (displayThorChain &&
136 + item.trade.provider == ExchangeProviderDescription.thorChain) ||
137 + (displayStealthEx && item.trade.provider == ExchangeProviderDescription.stealthEx))
138 .toList()
139 : _trades;
140 }
lib/view_model/dashboard/dashboard_view_model.dart
+9 -4
@@ -1,12 +1,14 @@
1 import 'dart:convert';
2
3 +import 'package:cake_wallet/.secrets.g.dart' as secrets;
4 +import 'package:cake_wallet/bitcoin/bitcoin.dart';
5 import 'package:cake_wallet/buy/buy_provider.dart';
6 import 'package:cake_wallet/core/key_service.dart';
7 import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart';
8 import 'package:cake_wallet/entities/balance_display_mode.dart';
9 +import 'package:cake_wallet/entities/exchange_api_mode.dart';
10 import 'package:cake_wallet/entities/preferences_key.dart';
11 import 'package:cake_wallet/entities/provider_types.dart';
9 -import 'package:cake_wallet/entities/exchange_api_mode.dart';
12 import 'package:cake_wallet/entities/service_status.dart';
13 import 'package:cake_wallet/exchange/exchange_provider_description.dart';
14 import 'package:cake_wallet/generated/i18n.dart';
@@ -45,11 +47,9 @@ import 'package:cw_core/wallet_info.dart';
47 import 'package:cw_core/wallet_type.dart';
48 import 'package:eth_sig_util/util/utils.dart';
49 import 'package:flutter/services.dart';
48 -import 'package:mobx/mobx.dart';
49 -import 'package:cake_wallet/bitcoin/bitcoin.dart';
50 import 'package:http/http.dart' as http;
51 +import 'package:mobx/mobx.dart';
52 import 'package:shared_preferences/shared_preferences.dart';
52 -import 'package:cake_wallet/.secrets.g.dart' as secrets;
53
54 part 'dashboard_view_model.g.dart';
55
@@ -129,6 +129,11 @@ abstract class DashboardViewModelBase with Store {
129 caption: ExchangeProviderDescription.thorChain.title,
130 onChanged: () =>
131 tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.thorChain)),
132 + FilterItem(
133 + value: () => tradeFilterStore.displayStealthEx,
134 + caption: ExchangeProviderDescription.stealthEx.title,
135 + onChanged: () =>
136 + tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.stealthEx)),
137 ]
138 },
139 subname = '',
lib/view_model/exchange/exchange_trade_view_model.dart
+3
@@ -7,6 +7,7 @@ import 'package:cake_wallet/exchange/provider/exolix_exchange_provider.dart';
7 import 'package:cake_wallet/exchange/provider/quantex_exchange_provider.dart';
8 import 'package:cake_wallet/exchange/provider/sideshift_exchange_provider.dart';
9 import 'package:cake_wallet/exchange/provider/simpleswap_exchange_provider.dart';
10 +import 'package:cake_wallet/exchange/provider/stealth_ex_exchange_provider.dart';
11 import 'package:cake_wallet/exchange/provider/thorchain_exchange.provider.dart';
12 import 'package:cake_wallet/exchange/provider/trocador_exchange_provider.dart';
13 import 'package:cake_wallet/exchange/trade.dart';
@@ -52,6 +53,8 @@ abstract class ExchangeTradeViewModelBase with Store {
53 case ExchangeProviderDescription.quantex:
54 _provider = QuantexExchangeProvider();
55 break;
56 + case ExchangeProviderDescription.stealthEx:
57 + _provider = StealthExExchangeProvider();
58 case ExchangeProviderDescription.thorChain:
59 _provider = ThorChainExchangeProvider(tradesStore: trades);
60 break;
lib/view_model/exchange/exchange_view_model.dart
+11 -9
@@ -4,6 +4,7 @@ import 'dart:convert';
4
5 import 'package:bitcoin_base/bitcoin_base.dart';
6 import 'package:cake_wallet/core/create_trade_result.dart';
7 +import 'package:cake_wallet/exchange/provider/stealth_ex_exchange_provider.dart';
8 import 'package:cw_core/crypto_currency.dart';
9 import 'package:cw_core/sync_status.dart';
10 import 'package:cw_core/transaction_priority.dart';
@@ -160,15 +161,16 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
161 final SharedPreferences sharedPreferences;
162
163 List<ExchangeProvider> get _allProviders => [
163 - ChangeNowExchangeProvider(settingsStore: _settingsStore),
164 - SideShiftExchangeProvider(),
165 - SimpleSwapExchangeProvider(),
166 - ThorChainExchangeProvider(tradesStore: trades),
167 - if (FeatureFlag.isExolixEnabled) ExolixExchangeProvider(),
168 - QuantexExchangeProvider(),
169 - TrocadorExchangeProvider(
170 - useTorOnly: _useTorOnly, providerStates: _settingsStore.trocadorProviderStates),
171 - ];
164 + ChangeNowExchangeProvider(settingsStore: _settingsStore),
165 + SideShiftExchangeProvider(),
166 + SimpleSwapExchangeProvider(),
167 + ThorChainExchangeProvider(tradesStore: trades),
168 + if (FeatureFlag.isExolixEnabled) ExolixExchangeProvider(),
169 + QuantexExchangeProvider(),
170 + StealthExExchangeProvider(),
171 + TrocadorExchangeProvider(
172 + useTorOnly: _useTorOnly, providerStates: _settingsStore.trocadorProviderStates),
173 + ];
174
175 @observable
176 ExchangeProvider? provider;
lib/view_model/trade_details_view_model.dart
+6
@@ -7,6 +7,7 @@ import 'package:cake_wallet/exchange/provider/exolix_exchange_provider.dart';
7 import 'package:cake_wallet/exchange/provider/quantex_exchange_provider.dart';
8 import 'package:cake_wallet/exchange/provider/sideshift_exchange_provider.dart';
9 import 'package:cake_wallet/exchange/provider/simpleswap_exchange_provider.dart';
10 +import 'package:cake_wallet/exchange/provider/stealth_ex_exchange_provider.dart';
11 import 'package:cake_wallet/exchange/provider/thorchain_exchange.provider.dart';
12 import 'package:cake_wallet/exchange/provider/trocador_exchange_provider.dart';
13 import 'package:cake_wallet/exchange/trade.dart';
@@ -60,6 +61,9 @@ abstract class TradeDetailsViewModelBase with Store {
61 case ExchangeProviderDescription.quantex:
62 _provider = QuantexExchangeProvider();
63 break;
64 + case ExchangeProviderDescription.stealthEx:
65 + _provider = StealthExExchangeProvider();
66 + break;
67 }
68
69 _updateItems();
@@ -86,6 +90,8 @@ abstract class TradeDetailsViewModelBase with Store {
90 return 'https://track.ninerealms.com/${trade.id}';
91 case ExchangeProviderDescription.quantex:
92 return 'https://myquantex.com/send/${trade.id}';
93 + case ExchangeProviderDescription.stealthEx:
94 + return 'https://stealthex.io/exchange/?id=${trade.id}';
95 }
96 return null;
97 }
tool/utils/secret_key.dart
+2
@@ -43,6 +43,8 @@ class SecretKey {
43 SecretKey('cakePayApiKey', () => ''),
44 SecretKey('CSRFToken', () => ''),
45 SecretKey('authorization', () => ''),
46 + SecretKey('stealthExBearerToken', () => ''),
47 + SecretKey('stealthExAdditionalFeePercent', () => ''),
48 ];
49
50 static final evmChainsSecrets = [