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 = [