From a4c279b5381ce81b3608ea3d4bca1666cb9de914 Mon Sep 17 00:00:00 2001 From: Godwin Asuquo <41484542+godilite@users.noreply.github.com> Date: Fri, 9 Jun 2023 14:46:00 +0300 Subject: [PATCH] CW-411 Update sideshift api (#959) * Update sideshift api * Fix sideShift api v2 rate * Fix rate calculation on sideshift * Additional fixes * Minor: Add additional unsupported * Add network to quote * Add amount and affiliateID to quote * Refactor tag/network assigning * Fix syntax errors with rate qpi * Add network for fetching rate * Remove amount from rate endpoint * Fix transaction depositAmount * Fix issues from code review * Fix issues from code review * Remove affiliate ID --------- Co-authored-by: Justin Ehrenhofer --- .github/workflows/pr_test_build.yml | 1 - .../sideshift_exchange_provider.dart | 192 ++++++++++-------- .../exchange/exchange_trade_view_model.dart | 9 +- .../exchange/exchange_view_model.dart | 4 +- tool/utils/secret_key.dart | 1 - 5 files changed, 118 insertions(+), 89 deletions(-) diff --git a/.github/workflows/pr_test_build.yml b/.github/workflows/pr_test_build.yml index 076fc2ea1..4ca762c12 100644 --- a/.github/workflows/pr_test_build.yml +++ b/.github/workflows/pr_test_build.yml @@ -106,7 +106,6 @@ jobs: echo "const moonPayApiKey = '${{ secrets.MOON_PAY_API_KEY }}';" >> lib/.secrets.g.dart echo "const moonPaySecretKey = '${{ secrets.MOON_PAY_SECRET_KEY }}';" >> lib/.secrets.g.dart echo "const sideShiftAffiliateId = '${{ secrets.SIDE_SHIFT_AFFILIATE_ID }}';" >> lib/.secrets.g.dart - echo "const sideShiftApiKey = '${{ secrets.SIDE_SHIFT_API_KEY }}';" >> lib/.secrets.g.dart echo "const simpleSwapApiKey = '${{ secrets.SIMPLE_SWAP_API_KEY }}';" >> lib/.secrets.g.dart echo "const simpleSwapApiKeyDesktop = '${{ secrets.SIMPLE_SWAP_API_KEY_DESKTOP }}';" >> lib/.secrets.g.dart echo "const onramperApiKey = '${{ secrets.ONRAMPER_API_KEY }}';" >> lib/.secrets.g.dart diff --git a/lib/exchange/sideshift/sideshift_exchange_provider.dart b/lib/exchange/sideshift/sideshift_exchange_provider.dart index a5b0c990f..26575f2c1 100644 --- a/lib/exchange/sideshift/sideshift_exchange_provider.dart +++ b/lib/exchange/sideshift/sideshift_exchange_provider.dart @@ -19,10 +19,10 @@ class SideShiftExchangeProvider extends ExchangeProvider { static const affiliateId = secrets.sideShiftAffiliateId; static const apiBaseUrl = 'https://sideshift.ai/api'; - static const rangePath = '/v1/pairs'; - static const orderPath = '/v1/orders'; - static const quotePath = '/v1/quotes'; - static const permissionPath = '/v1/permissions'; + static const rangePath = '/v2/pair'; + static const orderPath = '/v2/shifts'; + static const quotePath = '/v2/quotes'; + static const permissionPath = '/v2/permissions'; static const List _notSupported = [ CryptoCurrency.xhv, @@ -36,23 +36,22 @@ class SideShiftExchangeProvider extends ExchangeProvider { CryptoCurrency.scrt, CryptoCurrency.stx, CryptoCurrency.bttc, + CryptoCurrency.usdt, + CryptoCurrency.eos, ]; static List _supportedPairs() { - final supportedCurrencies = CryptoCurrency.all - .where((element) => !_notSupported.contains(element)) - .toList(); + final supportedCurrencies = + CryptoCurrency.all.where((element) => !_notSupported.contains(element)).toList(); return supportedCurrencies - .map((i) => supportedCurrencies - .map((k) => ExchangePair(from: i, to: k, reverse: true))) + .map((i) => supportedCurrencies.map((k) => ExchangePair(from: i, to: k, reverse: true))) .expand((i) => i) .toList(); } @override - ExchangeProviderDescription get description => - ExchangeProviderDescription.sideShift; + ExchangeProviderDescription get description => ExchangeProviderDescription.sideShift; @override Future fetchRate( @@ -65,17 +64,18 @@ class SideShiftExchangeProvider extends ExchangeProvider { if (amount == 0) { return 0.0; } - final fromCurrency = _normalizeCryptoCurrency(from); - final toCurrency = _normalizeCryptoCurrency(to); - final url = - apiBaseUrl + rangePath + '/' + fromCurrency + '/' + toCurrency; + + final fromCurrency = from.title.toLowerCase(); + final toCurrency = to.title.toLowerCase(); + final depositNetwork = _networkFor(from); + final settleNetwork = _networkFor(to); + + final url = "$apiBaseUrl$rangePath/$fromCurrency-$depositNetwork/$toCurrency-$settleNetwork"; + final uri = Uri.parse(url); final response = await get(uri); final responseJSON = json.decode(response.body) as Map; final rate = double.parse(responseJSON['rate'] as String); - final max = double.parse(responseJSON['max'] as String); - - if (amount > max) return 0.00; return rate; } catch (_) { @@ -101,25 +101,38 @@ class SideShiftExchangeProvider extends ExchangeProvider { } final responseJSON = json.decode(response.body) as Map; - final canCreateOrder = responseJSON['createOrder'] as bool; - final canCreateQuote = responseJSON['createQuote'] as bool; - return canCreateOrder && canCreateQuote; + final cancreateShift = responseJSON['createShift'] as bool; + return cancreateShift; } @override - Future createTrade( - {required TradeRequest request, required bool isFixedRateMode}) async { + Future createTrade({required TradeRequest request, required bool isFixedRateMode}) async { final _request = request as SideShiftRequest; - final quoteId = await _createQuote(_request); - final url = apiBaseUrl + orderPath; - final headers = {'Content-Type': 'application/json'}; + String url = ''; + final depositCoin = request.depositMethod.title.toLowerCase(); + final settleCoin = request.settleMethod.title.toLowerCase(); final body = { - 'type': 'fixed', - 'quoteId': quoteId, 'affiliateId': affiliateId, 'settleAddress': _request.settleAddress, - 'refundAddress': _request.refundAddress + 'refundAddress': _request.refundAddress, }; + + if (isFixedRateMode) { + final quoteId = await _createQuote(_request); + body['quoteId'] = quoteId; + + url = apiBaseUrl + orderPath + '/fixed'; + } else { + url = apiBaseUrl + orderPath + '/variable'; + final depositNetwork = _networkFor(request.depositMethod); + final settleNetwork = _networkFor(request.settleMethod); + body["depositCoin"] = depositCoin; + body["settleCoin"] = settleCoin; + body["settleNetwork"] = settleNetwork; + body["depositNetwork"] = depositNetwork; + } + final headers = {'Content-Type': 'application/json'}; + final uri = Uri.parse(url); final response = await post(uri, headers: headers, body: json.encode(body)); @@ -136,8 +149,9 @@ class SideShiftExchangeProvider extends ExchangeProvider { final responseJSON = json.decode(response.body) as Map; final id = responseJSON['id'] as String; - final inputAddress = responseJSON['depositAddress']['address'] as String; - final settleAddress = responseJSON['settleAddress']['address'] as String; + final inputAddress = responseJSON['depositAddress'] as String; + final settleAddress = responseJSON['settleAddress'] as String; + final depositAmount = responseJSON['depositAmount'] as String?; return Trade( id: id, @@ -147,7 +161,7 @@ class SideShiftExchangeProvider extends ExchangeProvider { inputAddress: inputAddress, refundAddress: settleAddress, state: TradeState.created, - amount: _request.depositAmount, + amount: depositAmount ?? _request.depositAmount, payoutAddress: settleAddress, createdAt: DateTime.now(), ); @@ -156,13 +170,17 @@ class SideShiftExchangeProvider extends ExchangeProvider { Future _createQuote(SideShiftRequest request) async { final url = apiBaseUrl + quotePath; final headers = {'Content-Type': 'application/json'}; - final depositMethod = _normalizeCryptoCurrency(request.depositMethod); - final settleMethod = _normalizeCryptoCurrency(request.settleMethod); + final depositMethod = request.depositMethod.title.toLowerCase(); + final settleMethod = request.settleMethod.title.toLowerCase(); + final depositNetwork = _networkFor(request.depositMethod); + final settleNetwork = _networkFor(request.settleMethod); final body = { - 'depositMethod': depositMethod, - 'settleMethod': settleMethod, + 'depositCoin': depositMethod, + 'settleCoin': settleMethod, 'affiliateId': affiliateId, - 'depositAmount': request.depositAmount, + 'settleAmount': request.depositAmount, + 'settleNetwork': settleNetwork, + 'depositNetwork': depositNetwork, }; final uri = Uri.parse(url); final response = await post(uri, headers: headers, body: json.encode(body)); @@ -189,9 +207,15 @@ class SideShiftExchangeProvider extends ExchangeProvider { {required CryptoCurrency from, required CryptoCurrency to, required bool isFixedRateMode}) async { - final fromCurrency = _normalizeCryptoCurrency(from); - final toCurrency = _normalizeCryptoCurrency(to); - final url = apiBaseUrl + rangePath + '/' + fromCurrency + '/' + toCurrency; + final fromCurrency = isFixedRateMode ? to : from; + final toCurrency = isFixedRateMode ? from : to; + + final fromNetwork = _networkFor(fromCurrency); + final toNetwork = _networkFor(toCurrency); + + final url = + "$apiBaseUrl$rangePath/${fromCurrency.title.toLowerCase()}-$fromNetwork/${toCurrency.title.toLowerCase()}-$toNetwork"; + final uri = Uri.parse(url); final response = await get(uri); @@ -210,6 +234,14 @@ class SideShiftExchangeProvider extends ExchangeProvider { final min = double.tryParse(responseJSON['min'] as String? ?? ''); final max = double.tryParse(responseJSON['max'] as String? ?? ''); + if (isFixedRateMode) { + final currentRate = double.parse(responseJSON['rate'] as String); + return Limits( + min: min != null ? (min * currentRate) : null, + max: max != null ? (max * currentRate) : null, + ); + } + return Limits(min: min, max: max); } @@ -227,8 +259,7 @@ class SideShiftExchangeProvider extends ExchangeProvider { final responseJSON = json.decode(response.body) as Map; final error = responseJSON['error']['message'] as String; - throw TradeNotFoundException(id, - provider: description, description: error); + throw TradeNotFoundException(id, provider: description, description: error); } if (response.statusCode != 200) { @@ -236,36 +267,32 @@ class SideShiftExchangeProvider extends ExchangeProvider { } final responseJSON = json.decode(response.body) as Map; - final fromCurrency = responseJSON['depositMethodId'] as String; + final fromCurrency = responseJSON['depositCoin'] as String; final from = CryptoCurrency.fromString(fromCurrency); - final toCurrency = responseJSON['settleMethodId'] as String; + final toCurrency = responseJSON['settleCoin'] as String; final to = CryptoCurrency.fromString(toCurrency); - final inputAddress = responseJSON['depositAddress']['address'] as String; - final expectedSendAmount = responseJSON['depositAmount'].toString(); - final deposits = responseJSON['deposits'] as List?; - final settleAddress = responseJSON['settleAddress']['address'] as String; + final inputAddress = responseJSON['depositAddress'] as String; + final expectedSendAmount = responseJSON['depositAmount'] as String?; + final status = responseJSON['status'] as String?; + final settleAddress = responseJSON['settleAddress'] as String; TradeState? state; - String? status; - if (deposits?.isNotEmpty ?? false) { - status = deposits![0]['status'] as String?; - } state = TradeState.deserialize(raw: status ?? 'created'); + final isVariable = (responseJSON['type'] as String) == 'variable'; - final expiredAtRaw = responseJSON['expiresAtISO'] as String; - final expiredAt = DateTime.tryParse(expiredAtRaw)?.toLocal(); + final expiredAtRaw = responseJSON['expiresAt'] as String; + final expiredAt = isVariable ? null : DateTime.tryParse(expiredAtRaw)?.toLocal(); return Trade( - id: id, - from: from, - to: to, - provider: description, - inputAddress: inputAddress, - amount: expectedSendAmount, - state: state, - expiredAt: expiredAt, - payoutAddress: settleAddress - ); + id: id, + from: from, + to: to, + provider: description, + inputAddress: inputAddress, + amount: expectedSendAmount ?? '', + state: state, + expiredAt: expiredAt, + payoutAddress: settleAddress); } @override @@ -280,28 +307,25 @@ class SideShiftExchangeProvider extends ExchangeProvider { @override String get title => 'SideShift'; - static String _normalizeCryptoCurrency(CryptoCurrency currency) { - switch (currency) { - case CryptoCurrency.zaddr: - return 'zaddr'; - case CryptoCurrency.zec: - return 'zec'; - case CryptoCurrency.bnb: - return currency.tag!.toLowerCase(); - case CryptoCurrency.usdterc20: - return 'usdtErc20'; - case CryptoCurrency.usdttrc20: - return 'usdtTrc20'; - case CryptoCurrency.usdcpoly: - return 'usdcpolygon'; - case CryptoCurrency.usdcsol: - return 'usdcsol'; - case CryptoCurrency.maticpoly: + String _networkFor(CryptoCurrency currency) => + currency.tag != null ? _normalizeTag(currency.tag!) : 'mainnet'; + + String _normalizeTag(String tag) { + switch (tag) { + case 'ETH': + return 'ethereum'; + case 'TRX': + return 'tron'; + case 'LN': + return 'lightning'; + case 'POLY': return 'polygon'; - case CryptoCurrency.btcln: - return 'ln'; + case 'ZEC': + return 'zcash'; + case 'AVAXC': + return 'avax'; default: - return currency.title.toLowerCase(); + return tag.toLowerCase(); } } } diff --git a/lib/view_model/exchange/exchange_trade_view_model.dart b/lib/view_model/exchange/exchange_trade_view_model.dart index 1d9f4f582..d5aeaa4fc 100644 --- a/lib/view_model/exchange/exchange_trade_view_model.dart +++ b/lib/view_model/exchange/exchange_trade_view_model.dart @@ -47,7 +47,7 @@ abstract class ExchangeTradeViewModelBase with Store { case ExchangeProviderDescription.simpleSwap: _provider = SimpleSwapExchangeProvider(); break; - case ExchangeProviderDescription.trocador: + case ExchangeProviderDescription.trocador: _provider = TrocadorExchangeProvider(); break; } @@ -114,6 +114,10 @@ abstract class ExchangeTradeViewModelBase with Store { updatedTrade.createdAt = trade.createdAt; } + if (updatedTrade.amount.isEmpty) { + updatedTrade.amount = trade.amount; + } + trade = updatedTrade; _updateItems(); @@ -123,7 +127,8 @@ abstract class ExchangeTradeViewModelBase with Store { } void _updateItems() { - final tagFrom = tradesStore.trade!.from.tag != null ? '${tradesStore.trade!.from.tag}' + ' ' : ''; + final tagFrom = + tradesStore.trade!.from.tag != null ? '${tradesStore.trade!.from.tag}' + ' ' : ''; final tagTo = tradesStore.trade!.to.tag != null ? '${tradesStore.trade!.to.tag}' + ' ' : ''; items.clear(); items.add(ExchangeTradeItem( diff --git a/lib/view_model/exchange/exchange_view_model.dart b/lib/view_model/exchange/exchange_view_model.dart index 77fb9a3d4..f823001a4 100644 --- a/lib/view_model/exchange/exchange_view_model.dart +++ b/lib/view_model/exchange/exchange_view_model.dart @@ -443,7 +443,9 @@ abstract class ExchangeViewModelBase with Store { request = SideShiftRequest( depositMethod: depositCurrency, settleMethod: receiveCurrency, - depositAmount: depositAmount.replaceAll(',', '.'), + depositAmount: isFixedRateMode + ? receiveAmount.replaceAll(',', '.') + : depositAmount.replaceAll(',', '.'), settleAddress: receiveAddress, refundAddress: depositAddress, ); diff --git a/tool/utils/secret_key.dart b/tool/utils/secret_key.dart index 621ab1cfc..b5b4de6d6 100644 --- a/tool/utils/secret_key.dart +++ b/tool/utils/secret_key.dart @@ -20,7 +20,6 @@ class SecretKey { SecretKey('moonPayApiKey', () => ''), SecretKey('moonPaySecretKey', () => ''), SecretKey('sideShiftAffiliateId', () => ''), - SecretKey('sideShiftApiKey', () => ''), SecretKey('simpleSwapApiKey', () => ''), SecretKey('simpleSwapApiKeyDesktop', () => ''), SecretKey('anypayToken', () => ''),