diff --git a/.github/workflows/pr_test_build_android.yml b/.github/workflows/pr_test_build_android.yml index b7b2aaa71..b60ae1f7e 100644 --- a/.github/workflows/pr_test_build_android.yml +++ b/.github/workflows/pr_test_build_android.yml @@ -168,6 +168,8 @@ jobs: echo "const nanoNowNodesApiKey = '${{ secrets.NANO_NOW_NODES_API_KEY }}';" >> cw_nano/lib/.secrets.g.dart echo "const tronGridApiKey = '${{ secrets.TRON_GRID_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart echo "const tronNowNodesApiKey = '${{ secrets.TRON_NOW_NODES_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart + echo "const stealthExBearerToken = '${{ secrets.STEALTH_EX_BEARER_TOKEN }}';" >> lib/.secrets.g.dart + echo "const stealthExAdditionalFeePercent = '${{ secrets.STEALTH_EX_ADDITIONAL_FEE_PERCENT }}';" >> lib/.secrets.g.dart - name: Rename app run: | diff --git a/.github/workflows/pr_test_build_linux.yml b/.github/workflows/pr_test_build_linux.yml index 7935dd177..e25bea2ec 100644 --- a/.github/workflows/pr_test_build_linux.yml +++ b/.github/workflows/pr_test_build_linux.yml @@ -154,6 +154,8 @@ jobs: echo "const nanoNowNodesApiKey = '${{ secrets.NANO_NOW_NODES_API_KEY }}';" >> cw_nano/lib/.secrets.g.dart echo "const tronGridApiKey = '${{ secrets.TRON_GRID_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart echo "const tronNowNodesApiKey = '${{ secrets.TRON_NOW_NODES_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart + echo "const stealthExBearerToken = '${{ secrets.STEALTH_EX_BEARER_TOKEN }}';" >> lib/.secrets.g.dart + echo "const stealthExAdditionalFeePercent = '${{ secrets.STEALTH_EX_ADDITIONAL_FEE_PERCENT }}';" >> lib/.secrets.g.dart - name: Rename app run: | diff --git a/assets/images/stealthex.png b/assets/images/stealthex.png new file mode 100644 index 000000000..311d47b74 Binary files /dev/null and b/assets/images/stealthex.png differ diff --git a/lib/exchange/exchange_provider_description.dart b/lib/exchange/exchange_provider_description.dart index c28de5b72..a91288024 100644 --- a/lib/exchange/exchange_provider_description.dart +++ b/lib/exchange/exchange_provider_description.dart @@ -27,6 +27,8 @@ class ExchangeProviderDescription extends EnumerableItem with Serializable< ExchangeProviderDescription(title: 'ThorChain', raw: 8, image: 'assets/images/thorchain.png'); static const quantex = ExchangeProviderDescription(title: 'Quantex', raw: 9, image: 'assets/images/quantex.png'); + static const stealthEx = + ExchangeProviderDescription(title: 'StealthEx', raw: 10, image: 'assets/images/stealthex.png'); static ExchangeProviderDescription deserialize({required int raw}) { switch (raw) { @@ -50,6 +52,8 @@ class ExchangeProviderDescription extends EnumerableItem with Serializable< return thorChain; case 9: return quantex; + case 10: + return stealthEx; default: throw Exception('Unexpected token: $raw for ExchangeProviderDescription deserialize'); } diff --git a/lib/exchange/provider/stealth_ex_exchange_provider.dart b/lib/exchange/provider/stealth_ex_exchange_provider.dart new file mode 100644 index 000000000..3e05091e6 --- /dev/null +++ b/lib/exchange/provider/stealth_ex_exchange_provider.dart @@ -0,0 +1,299 @@ +import 'dart:convert'; +import 'dart:developer'; + +import 'package:cake_wallet/.secrets.g.dart' as secrets; +import 'package:cake_wallet/exchange/provider/exchange_provider.dart'; +import 'package:cake_wallet/exchange/exchange_provider_description.dart'; +import 'package:cake_wallet/exchange/limits.dart'; +import 'package:cake_wallet/exchange/trade.dart'; +import 'package:cake_wallet/exchange/trade_not_created_exception.dart'; +import 'package:cake_wallet/exchange/trade_request.dart'; +import 'package:cake_wallet/exchange/trade_state.dart'; +import 'package:cake_wallet/exchange/utils/currency_pairs_utils.dart'; +import 'package:cw_core/crypto_currency.dart'; +import 'package:http/http.dart' as http; + +class StealthExExchangeProvider extends ExchangeProvider { + StealthExExchangeProvider() : super(pairList: supportedPairs(_notSupported)); + + static const List _notSupported = []; + + static final apiKey = secrets.stealthExBearerToken; + static final _additionalFeePercent = double.tryParse(secrets.stealthExAdditionalFeePercent); + static const _baseUrl = 'https://api.stealthex.io'; + static const _rangePath = '/v4/rates/range'; + static const _amountPath = '/v4/rates/estimated-amount'; + static const _exchangesPath = '/v4/exchanges'; + + @override + String get title => 'StealthEX'; + + @override + bool get isAvailable => true; + + @override + bool get isEnabled => true; + + @override + bool get supportsFixedRate => true; + + @override + ExchangeProviderDescription get description => ExchangeProviderDescription.stealthEx; + + @override + Future checkIsAvailable() async => true; + + @override + Future fetchLimits( + {required CryptoCurrency from, + required CryptoCurrency to, + required bool isFixedRateMode}) async { + final curFrom = isFixedRateMode ? to : from; + final curTo = isFixedRateMode ? from : to; + + final headers = {'Authorization': apiKey, 'Content-Type': 'application/json'}; + final body = { + 'route': { + 'from': {'symbol': _getName(curFrom), 'network': _getNetwork(curFrom)}, + 'to': {'symbol': _getName(curTo), 'network': _getNetwork(curTo)} + }, + 'estimation': isFixedRateMode ? 'reversed' : 'direct', + 'rate': isFixedRateMode ? 'fixed' : 'floating', + 'additional_fee_percent': _additionalFeePercent, + }; + + try { + final response = await http.post(Uri.parse(_baseUrl + _rangePath), + headers: headers, body: json.encode(body)); + if (response.statusCode != 200) { + throw Exception('StealthEx fetch limits failed: ${response.body}'); + } + final responseJSON = json.decode(response.body) as Map; + final min = responseJSON['min_amount'] as double?; + final max = responseJSON['max_amount'] as double?; + return Limits(min: min, max: max); + } catch (e) { + log(e.toString()); + throw Exception('StealthEx failed to fetch limits'); + } + } + + @override + Future fetchRate( + {required CryptoCurrency from, + required CryptoCurrency to, + required double amount, + required bool isFixedRateMode, + required bool isReceiveAmount}) async { + final response = await getEstimatedExchangeAmount( + from: from, to: to, amount: amount, isFixedRateMode: isFixedRateMode); + final estimatedAmount = response['estimated_amount'] as double? ?? 0.0; + return estimatedAmount > 0.0 + ? isFixedRateMode + ? amount / estimatedAmount + : estimatedAmount / amount + : 0.0; + } + + @override + Future createTrade( + {required TradeRequest request, + required bool isFixedRateMode, + required bool isSendAll}) async { + String? rateId; + String? validUntil; + + try { + if (isFixedRateMode) { + final response = await getEstimatedExchangeAmount( + from: request.fromCurrency, + to: request.toCurrency, + amount: double.parse(request.toAmount), + isFixedRateMode: isFixedRateMode); + rateId = response['rate_id'] as String?; + validUntil = response['valid_until'] as String?; + if (rateId == null) throw TradeNotCreatedException(description); + } + + final headers = {'Authorization': apiKey, 'Content-Type': 'application/json'}; + final body = { + 'route': { + 'from': { + 'symbol': _getName(request.fromCurrency), + 'network': _getNetwork(request.fromCurrency) + }, + 'to': {'symbol': _getName(request.toCurrency), 'network': _getNetwork(request.toCurrency)} + }, + 'estimation': isFixedRateMode ? 'reversed' : 'direct', + 'rate': isFixedRateMode ? 'fixed' : 'floating', + if (isFixedRateMode) 'rate_id': rateId, + 'amount': + isFixedRateMode ? double.parse(request.toAmount) : double.parse(request.fromAmount), + 'address': request.toAddress, + 'refund_address': request.refundAddress, + 'additional_fee_percent': _additionalFeePercent, + }; + + final response = await http.post(Uri.parse(_baseUrl + _exchangesPath), + headers: headers, body: json.encode(body)); + + if (response.statusCode != 201) { + throw Exception('StealthEx create trade failed: ${response.body}'); + } + final responseJSON = json.decode(response.body) as Map; + final deposit = responseJSON['deposit'] as Map; + final withdrawal = responseJSON['withdrawal'] as Map; + + final id = responseJSON['id'] as String; + final from = deposit['symbol'] as String; + final to = withdrawal['symbol'] as String; + final payoutAddress = withdrawal['address'] as String; + final depositAddress = deposit['address'] as String; + final refundAddress = responseJSON['refund_address'] as String; + final depositAmount = toDouble(deposit['amount']); + final receiveAmount = toDouble(withdrawal['amount']); + final status = responseJSON['status'] as String; + final createdAtString = responseJSON['created_at'] as String; + + final createdAt = DateTime.parse(createdAtString); + final expiredAt = validUntil != null + ? DateTime.parse(validUntil) + : DateTime.now().add(Duration(minutes: 5)); + + + CryptoCurrency fromCurrency; + if (request.fromCurrency.tag != null && request.fromCurrency.title.toLowerCase() == from) { + fromCurrency = request.fromCurrency; + } else { + fromCurrency = CryptoCurrency.fromString(from); + } + + CryptoCurrency toCurrency; + if (request.toCurrency.tag != null && request.toCurrency.title.toLowerCase() == to) { + toCurrency = request.toCurrency; + } else { + toCurrency = CryptoCurrency.fromString(to); + } + + return Trade( + id: id, + from: fromCurrency, + to: toCurrency, + provider: description, + inputAddress: depositAddress, + payoutAddress: payoutAddress, + refundAddress: refundAddress, + amount: depositAmount.toString(), + receiveAmount: receiveAmount.toString(), + state: TradeState.deserialize(raw: status), + createdAt: createdAt, + expiredAt: expiredAt, + ); + } catch (e) { + log(e.toString()); + throw TradeNotCreatedException(description); + } + } + + @override + Future findTradeById({required String id}) async { + final headers = {'Authorization': apiKey, 'Content-Type': 'application/json'}; + + final uri = Uri.parse('$_baseUrl$_exchangesPath/$id'); + final response = await http.get(uri, headers: headers); + + if (response.statusCode != 200) { + throw Exception('StealthEx fetch trade failed: ${response.body}'); + } + final responseJSON = json.decode(response.body) as Map; + final deposit = responseJSON['deposit'] as Map; + final withdrawal = responseJSON['withdrawal'] as Map; + + final respId = responseJSON['id'] as String; + final from = deposit['symbol'] as String; + final to = withdrawal['symbol'] as String; + final payoutAddress = withdrawal['address'] as String; + final depositAddress = deposit['address'] as String; + final refundAddress = responseJSON['refund_address'] as String; + final depositAmount = toDouble(deposit['amount']); + final receiveAmount = toDouble(withdrawal['amount']); + final status = responseJSON['status'] as String; + final createdAtString = responseJSON['created_at'] as String; + final createdAt = DateTime.parse(createdAtString); + + return Trade( + id: respId, + from: CryptoCurrency.fromString(from), + to: CryptoCurrency.fromString(to), + provider: description, + inputAddress: depositAddress, + payoutAddress: payoutAddress, + refundAddress: refundAddress, + amount: depositAmount.toString(), + receiveAmount: receiveAmount.toString(), + state: TradeState.deserialize(raw: status), + createdAt: createdAt, + isRefund: status == 'refunded', + ); + } + + Future> getEstimatedExchangeAmount( + {required CryptoCurrency from, + required CryptoCurrency to, + required double amount, + required bool isFixedRateMode}) async { + final headers = {'Authorization': apiKey, 'Content-Type': 'application/json'}; + + final body = { + 'route': { + 'from': {'symbol': _getName(from), 'network': _getNetwork(from)}, + 'to': {'symbol': _getName(to), 'network': _getNetwork(to)} + }, + 'estimation': isFixedRateMode ? 'reversed' : 'direct', + 'rate': isFixedRateMode ? 'fixed' : 'floating', + 'amount': amount, + 'additional_fee_percent': _additionalFeePercent, + }; + + try { + final response = await http.post(Uri.parse(_baseUrl + _amountPath), + headers: headers, body: json.encode(body)); + if (response.statusCode != 200) return {}; + final responseJSON = json.decode(response.body) as Map; + final rate = responseJSON['rate'] as Map?; + return { + 'estimated_amount': responseJSON['estimated_amount'] as double?, + if (rate != null) 'valid_until': rate['valid_until'] as String?, + if (rate != null) 'rate_id': rate['id'] as String? + }; + } catch (e) { + log(e.toString()); + return {}; + } + } + + double toDouble(dynamic value) { + if (value is int) { + return value.toDouble(); + } else if (value is double) { + return value; + } else { + return 0.0; + } + } + + String _getName(CryptoCurrency currency) { + if (currency == CryptoCurrency.usdcEPoly) return 'usdce'; + return currency.title.toLowerCase(); + } + + String _getNetwork(CryptoCurrency currency) { + if (currency.tag == null) return 'mainnet'; + + if (currency == CryptoCurrency.maticpoly) return 'mainnet'; + + if (currency.tag == 'POLY') return 'matic'; + + return currency.tag!.toLowerCase(); + } +} diff --git a/lib/exchange/trade_state.dart b/lib/exchange/trade_state.dart index 0a196835e..e94906763 100644 --- a/lib/exchange/trade_state.dart +++ b/lib/exchange/trade_state.dart @@ -40,7 +40,6 @@ class TradeState extends EnumerableItem with Serializable { static const exchanging = TradeState(raw: 'exchanging', title: 'Exchanging'); static const sending = TradeState(raw: 'sending', title: 'Sending'); static const success = TradeState(raw: 'success', title: 'Success'); - static TradeState deserialize({required String raw}) { switch (raw) { @@ -119,6 +118,7 @@ class TradeState extends EnumerableItem with Serializable { case 'refunded': return refunded; case 'confirmation': + case 'verifying': return confirmation; case 'confirmed': return confirmed; diff --git a/lib/store/dashboard/trade_filter_store.dart b/lib/store/dashboard/trade_filter_store.dart index c05839578..4ceebedfd 100644 --- a/lib/store/dashboard/trade_filter_store.dart +++ b/lib/store/dashboard/trade_filter_store.dart @@ -16,7 +16,8 @@ abstract class TradeFilterStoreBase with Store { displaySimpleSwap = true, displayTrocador = true, displayExolix = true, - displayThorChain = true; + displayThorChain = true, + displayStealthEx = true; @observable bool displayXMRTO; @@ -42,6 +43,9 @@ abstract class TradeFilterStoreBase with Store { @observable bool displayThorChain; + @observable + bool displayStealthEx; + @computed bool get displayAllTrades => displayChangeNow && @@ -49,7 +53,8 @@ abstract class TradeFilterStoreBase with Store { displaySimpleSwap && displayTrocador && displayExolix && - displayThorChain; + displayThorChain && + displayStealthEx; @action void toggleDisplayExchange(ExchangeProviderDescription provider) { @@ -78,6 +83,9 @@ abstract class TradeFilterStoreBase with Store { case ExchangeProviderDescription.thorChain: displayThorChain = !displayThorChain; break; + case ExchangeProviderDescription.stealthEx: + displayStealthEx = !displayStealthEx; + break; case ExchangeProviderDescription.all: if (displayAllTrades) { displayChangeNow = false; @@ -88,6 +96,7 @@ abstract class TradeFilterStoreBase with Store { displayTrocador = false; displayExolix = false; displayThorChain = false; + displayStealthEx = false; } else { displayChangeNow = true; displaySideShift = true; @@ -97,6 +106,7 @@ abstract class TradeFilterStoreBase with Store { displayTrocador = true; displayExolix = true; displayThorChain = true; + displayStealthEx = true; } break; } @@ -112,13 +122,19 @@ abstract class TradeFilterStoreBase with Store { ? _trades .where((item) => (displayXMRTO && item.trade.provider == ExchangeProviderDescription.xmrto) || - (displaySideShift && item.trade.provider == ExchangeProviderDescription.sideShift) || - (displayChangeNow && item.trade.provider == ExchangeProviderDescription.changeNow) || - (displayMorphToken && item.trade.provider == ExchangeProviderDescription.morphToken) || - (displaySimpleSwap && item.trade.provider == ExchangeProviderDescription.simpleSwap) || + (displaySideShift && + item.trade.provider == ExchangeProviderDescription.sideShift) || + (displayChangeNow && + item.trade.provider == ExchangeProviderDescription.changeNow) || + (displayMorphToken && + item.trade.provider == ExchangeProviderDescription.morphToken) || + (displaySimpleSwap && + item.trade.provider == ExchangeProviderDescription.simpleSwap) || (displayTrocador && item.trade.provider == ExchangeProviderDescription.trocador) || (displayExolix && item.trade.provider == ExchangeProviderDescription.exolix) || - (displayThorChain && item.trade.provider == ExchangeProviderDescription.thorChain)) + (displayThorChain && + item.trade.provider == ExchangeProviderDescription.thorChain) || + (displayStealthEx && item.trade.provider == ExchangeProviderDescription.stealthEx)) .toList() : _trades; } diff --git a/lib/view_model/dashboard/dashboard_view_model.dart b/lib/view_model/dashboard/dashboard_view_model.dart index d58d7535c..dc96c4461 100644 --- a/lib/view_model/dashboard/dashboard_view_model.dart +++ b/lib/view_model/dashboard/dashboard_view_model.dart @@ -1,12 +1,14 @@ import 'dart:convert'; +import 'package:cake_wallet/.secrets.g.dart' as secrets; +import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/buy/buy_provider.dart'; import 'package:cake_wallet/core/key_service.dart'; import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart'; import 'package:cake_wallet/entities/balance_display_mode.dart'; +import 'package:cake_wallet/entities/exchange_api_mode.dart'; import 'package:cake_wallet/entities/preferences_key.dart'; import 'package:cake_wallet/entities/provider_types.dart'; -import 'package:cake_wallet/entities/exchange_api_mode.dart'; import 'package:cake_wallet/entities/service_status.dart'; import 'package:cake_wallet/exchange/exchange_provider_description.dart'; import 'package:cake_wallet/generated/i18n.dart'; @@ -45,11 +47,9 @@ import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:eth_sig_util/util/utils.dart'; import 'package:flutter/services.dart'; -import 'package:mobx/mobx.dart'; -import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:http/http.dart' as http; +import 'package:mobx/mobx.dart'; import 'package:shared_preferences/shared_preferences.dart'; -import 'package:cake_wallet/.secrets.g.dart' as secrets; part 'dashboard_view_model.g.dart'; @@ -129,6 +129,11 @@ abstract class DashboardViewModelBase with Store { caption: ExchangeProviderDescription.thorChain.title, onChanged: () => tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.thorChain)), + FilterItem( + value: () => tradeFilterStore.displayStealthEx, + caption: ExchangeProviderDescription.stealthEx.title, + onChanged: () => + tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.stealthEx)), ] }, subname = '', diff --git a/lib/view_model/exchange/exchange_trade_view_model.dart b/lib/view_model/exchange/exchange_trade_view_model.dart index 5d99ff8a5..4cb7e4cad 100644 --- a/lib/view_model/exchange/exchange_trade_view_model.dart +++ b/lib/view_model/exchange/exchange_trade_view_model.dart @@ -7,6 +7,7 @@ import 'package:cake_wallet/exchange/provider/exolix_exchange_provider.dart'; import 'package:cake_wallet/exchange/provider/quantex_exchange_provider.dart'; import 'package:cake_wallet/exchange/provider/sideshift_exchange_provider.dart'; import 'package:cake_wallet/exchange/provider/simpleswap_exchange_provider.dart'; +import 'package:cake_wallet/exchange/provider/stealth_ex_exchange_provider.dart'; import 'package:cake_wallet/exchange/provider/thorchain_exchange.provider.dart'; import 'package:cake_wallet/exchange/provider/trocador_exchange_provider.dart'; import 'package:cake_wallet/exchange/trade.dart'; @@ -52,6 +53,8 @@ abstract class ExchangeTradeViewModelBase with Store { case ExchangeProviderDescription.quantex: _provider = QuantexExchangeProvider(); break; + case ExchangeProviderDescription.stealthEx: + _provider = StealthExExchangeProvider(); case ExchangeProviderDescription.thorChain: _provider = ThorChainExchangeProvider(tradesStore: trades); break; diff --git a/lib/view_model/exchange/exchange_view_model.dart b/lib/view_model/exchange/exchange_view_model.dart index 2bbe9954e..bd9474e39 100644 --- a/lib/view_model/exchange/exchange_view_model.dart +++ b/lib/view_model/exchange/exchange_view_model.dart @@ -4,6 +4,7 @@ import 'dart:convert'; import 'package:bitcoin_base/bitcoin_base.dart'; import 'package:cake_wallet/core/create_trade_result.dart'; +import 'package:cake_wallet/exchange/provider/stealth_ex_exchange_provider.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/sync_status.dart'; import 'package:cw_core/transaction_priority.dart'; @@ -160,15 +161,16 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with final SharedPreferences sharedPreferences; List get _allProviders => [ - ChangeNowExchangeProvider(settingsStore: _settingsStore), - SideShiftExchangeProvider(), - SimpleSwapExchangeProvider(), - ThorChainExchangeProvider(tradesStore: trades), - if (FeatureFlag.isExolixEnabled) ExolixExchangeProvider(), - QuantexExchangeProvider(), - TrocadorExchangeProvider( - useTorOnly: _useTorOnly, providerStates: _settingsStore.trocadorProviderStates), - ]; + ChangeNowExchangeProvider(settingsStore: _settingsStore), + SideShiftExchangeProvider(), + SimpleSwapExchangeProvider(), + ThorChainExchangeProvider(tradesStore: trades), + if (FeatureFlag.isExolixEnabled) ExolixExchangeProvider(), + QuantexExchangeProvider(), + StealthExExchangeProvider(), + TrocadorExchangeProvider( + useTorOnly: _useTorOnly, providerStates: _settingsStore.trocadorProviderStates), + ]; @observable ExchangeProvider? provider; diff --git a/lib/view_model/trade_details_view_model.dart b/lib/view_model/trade_details_view_model.dart index eed1b6c75..e71d97ae0 100644 --- a/lib/view_model/trade_details_view_model.dart +++ b/lib/view_model/trade_details_view_model.dart @@ -7,6 +7,7 @@ import 'package:cake_wallet/exchange/provider/exolix_exchange_provider.dart'; import 'package:cake_wallet/exchange/provider/quantex_exchange_provider.dart'; import 'package:cake_wallet/exchange/provider/sideshift_exchange_provider.dart'; import 'package:cake_wallet/exchange/provider/simpleswap_exchange_provider.dart'; +import 'package:cake_wallet/exchange/provider/stealth_ex_exchange_provider.dart'; import 'package:cake_wallet/exchange/provider/thorchain_exchange.provider.dart'; import 'package:cake_wallet/exchange/provider/trocador_exchange_provider.dart'; import 'package:cake_wallet/exchange/trade.dart'; @@ -60,6 +61,9 @@ abstract class TradeDetailsViewModelBase with Store { case ExchangeProviderDescription.quantex: _provider = QuantexExchangeProvider(); break; + case ExchangeProviderDescription.stealthEx: + _provider = StealthExExchangeProvider(); + break; } _updateItems(); @@ -86,6 +90,8 @@ abstract class TradeDetailsViewModelBase with Store { return 'https://track.ninerealms.com/${trade.id}'; case ExchangeProviderDescription.quantex: return 'https://myquantex.com/send/${trade.id}'; + case ExchangeProviderDescription.stealthEx: + return 'https://stealthex.io/exchange/?id=${trade.id}'; } return null; } diff --git a/tool/utils/secret_key.dart b/tool/utils/secret_key.dart index a1d93fcf9..0741ab4e7 100644 --- a/tool/utils/secret_key.dart +++ b/tool/utils/secret_key.dart @@ -43,6 +43,8 @@ class SecretKey { SecretKey('cakePayApiKey', () => ''), SecretKey('CSRFToken', () => ''), SecretKey('authorization', () => ''), + SecretKey('stealthExBearerToken', () => ''), + SecretKey('stealthExAdditionalFeePercent', () => ''), ]; static final evmChainsSecrets = [