mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2025-06-28 20:39:51 +00:00
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>
This commit is contained in:
parent
580bd01345
commit
f279a222df
12 changed files with 362 additions and 21 deletions
299
lib/exchange/provider/stealth_ex_exchange_provider.dart
Normal file
299
lib/exchange/provider/stealth_ex_exchange_provider.dart
Normal file
|
@ -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<CryptoCurrency> _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<bool> checkIsAvailable() async => true;
|
||||
|
||||
@override
|
||||
Future<Limits> 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<String, dynamic>;
|
||||
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<double> 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<Trade> 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<String, dynamic>;
|
||||
final deposit = responseJSON['deposit'] as Map<String, dynamic>;
|
||||
final withdrawal = responseJSON['withdrawal'] as Map<String, dynamic>;
|
||||
|
||||
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<Trade> 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<String, dynamic>;
|
||||
final deposit = responseJSON['deposit'] as Map<String, dynamic>;
|
||||
final withdrawal = responseJSON['withdrawal'] as Map<String, dynamic>;
|
||||
|
||||
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<Map<String, dynamic>> 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<String, dynamic>;
|
||||
final rate = responseJSON['rate'] as Map<String, dynamic>?;
|
||||
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();
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue