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 <justin.ehrenhofer@gmail.com>

Godwin Asuquo committed Jun 9, 2023 at 14:46 UTC a4c279b5381ce81b3608ea3d4bca1666cb9de914
5 files changed +118 -89
.github/workflows/pr_test_build.yml
-1
@@ -106,7 +106,6 @@ jobs:
106 echo "const moonPayApiKey = '${{ secrets.MOON_PAY_API_KEY }}';" >> lib/.secrets.g.dart
107 echo "const moonPaySecretKey = '${{ secrets.MOON_PAY_SECRET_KEY }}';" >> lib/.secrets.g.dart
108 echo "const sideShiftAffiliateId = '${{ secrets.SIDE_SHIFT_AFFILIATE_ID }}';" >> lib/.secrets.g.dart
109 - echo "const sideShiftApiKey = '${{ secrets.SIDE_SHIFT_API_KEY }}';" >> lib/.secrets.g.dart
109 echo "const simpleSwapApiKey = '${{ secrets.SIMPLE_SWAP_API_KEY }}';" >> lib/.secrets.g.dart
110 echo "const simpleSwapApiKeyDesktop = '${{ secrets.SIMPLE_SWAP_API_KEY_DESKTOP }}';" >> lib/.secrets.g.dart
111 echo "const onramperApiKey = '${{ secrets.ONRAMPER_API_KEY }}';" >> lib/.secrets.g.dart
lib/exchange/sideshift/sideshift_exchange_provider.dart
+108 -84
@@ -19,10 +19,10 @@ class SideShiftExchangeProvider extends ExchangeProvider {
19
20 static const affiliateId = secrets.sideShiftAffiliateId;
21 static const apiBaseUrl = 'https://sideshift.ai/api';
22 - static const rangePath = '/v1/pairs';
23 - static const orderPath = '/v1/orders';
24 - static const quotePath = '/v1/quotes';
25 - static const permissionPath = '/v1/permissions';
22 + static const rangePath = '/v2/pair';
23 + static const orderPath = '/v2/shifts';
24 + static const quotePath = '/v2/quotes';
25 + static const permissionPath = '/v2/permissions';
26
27 static const List<CryptoCurrency> _notSupported = [
28 CryptoCurrency.xhv,
@@ -36,23 +36,22 @@ class SideShiftExchangeProvider extends ExchangeProvider {
36 CryptoCurrency.scrt,
37 CryptoCurrency.stx,
38 CryptoCurrency.bttc,
39 + CryptoCurrency.usdt,
40 + CryptoCurrency.eos,
41 ];
42
43 static List<ExchangePair> _supportedPairs() {
42 - final supportedCurrencies = CryptoCurrency.all
43 - .where((element) => !_notSupported.contains(element))
44 - .toList();
44 + final supportedCurrencies =
45 + CryptoCurrency.all.where((element) => !_notSupported.contains(element)).toList();
46
47 return supportedCurrencies
47 - .map((i) => supportedCurrencies
48 - .map((k) => ExchangePair(from: i, to: k, reverse: true)))
48 + .map((i) => supportedCurrencies.map((k) => ExchangePair(from: i, to: k, reverse: true)))
49 .expand((i) => i)
50 .toList();
51 }
52
53 @override
54 - ExchangeProviderDescription get description =>
55 - ExchangeProviderDescription.sideShift;
54 + ExchangeProviderDescription get description => ExchangeProviderDescription.sideShift;
55
56 @override
57 Future<double> fetchRate(
@@ -65,17 +64,18 @@ class SideShiftExchangeProvider extends ExchangeProvider {
64 if (amount == 0) {
65 return 0.0;
66 }
68 - final fromCurrency = _normalizeCryptoCurrency(from);
69 - final toCurrency = _normalizeCryptoCurrency(to);
70 - final url =
71 - apiBaseUrl + rangePath + '/' + fromCurrency + '/' + toCurrency;
67 +
68 + final fromCurrency = from.title.toLowerCase();
69 + final toCurrency = to.title.toLowerCase();
70 + final depositNetwork = _networkFor(from);
71 + final settleNetwork = _networkFor(to);
72 +
73 + final url = "$apiBaseUrl$rangePath/$fromCurrency-$depositNetwork/$toCurrency-$settleNetwork";
74 +
75 final uri = Uri.parse(url);
76 final response = await get(uri);
77 final responseJSON = json.decode(response.body) as Map<String, dynamic>;
78 final rate = double.parse(responseJSON['rate'] as String);
76 - final max = double.parse(responseJSON['max'] as String);
77 -
78 - if (amount > max) return 0.00;
79
80 return rate;
81 } catch (_) {
@@ -101,25 +101,38 @@ class SideShiftExchangeProvider extends ExchangeProvider {
101 }
102
103 final responseJSON = json.decode(response.body) as Map<String, dynamic>;
104 - final canCreateOrder = responseJSON['createOrder'] as bool;
105 - final canCreateQuote = responseJSON['createQuote'] as bool;
106 - return canCreateOrder && canCreateQuote;
104 + final cancreateShift = responseJSON['createShift'] as bool;
105 + return cancreateShift;
106 }
107
108 @override
110 - Future<Trade> createTrade(
111 - {required TradeRequest request, required bool isFixedRateMode}) async {
109 + Future<Trade> createTrade({required TradeRequest request, required bool isFixedRateMode}) async {
110 final _request = request as SideShiftRequest;
113 - final quoteId = await _createQuote(_request);
114 - final url = apiBaseUrl + orderPath;
115 - final headers = {'Content-Type': 'application/json'};
111 + String url = '';
112 + final depositCoin = request.depositMethod.title.toLowerCase();
113 + final settleCoin = request.settleMethod.title.toLowerCase();
114 final body = {
117 - 'type': 'fixed',
118 - 'quoteId': quoteId,
115 'affiliateId': affiliateId,
116 'settleAddress': _request.settleAddress,
121 - 'refundAddress': _request.refundAddress
117 + 'refundAddress': _request.refundAddress,
118 };
119 +
120 + if (isFixedRateMode) {
121 + final quoteId = await _createQuote(_request);
122 + body['quoteId'] = quoteId;
123 +
124 + url = apiBaseUrl + orderPath + '/fixed';
125 + } else {
126 + url = apiBaseUrl + orderPath + '/variable';
127 + final depositNetwork = _networkFor(request.depositMethod);
128 + final settleNetwork = _networkFor(request.settleMethod);
129 + body["depositCoin"] = depositCoin;
130 + body["settleCoin"] = settleCoin;
131 + body["settleNetwork"] = settleNetwork;
132 + body["depositNetwork"] = depositNetwork;
133 + }
134 + final headers = {'Content-Type': 'application/json'};
135 +
136 final uri = Uri.parse(url);
137 final response = await post(uri, headers: headers, body: json.encode(body));
138
@@ -136,8 +149,9 @@ class SideShiftExchangeProvider extends ExchangeProvider {
149
150 final responseJSON = json.decode(response.body) as Map<String, dynamic>;
151 final id = responseJSON['id'] as String;
139 - final inputAddress = responseJSON['depositAddress']['address'] as String;
140 - final settleAddress = responseJSON['settleAddress']['address'] as String;
152 + final inputAddress = responseJSON['depositAddress'] as String;
153 + final settleAddress = responseJSON['settleAddress'] as String;
154 + final depositAmount = responseJSON['depositAmount'] as String?;
155
156 return Trade(
157 id: id,
@@ -147,7 +161,7 @@ class SideShiftExchangeProvider extends ExchangeProvider {
161 inputAddress: inputAddress,
162 refundAddress: settleAddress,
163 state: TradeState.created,
150 - amount: _request.depositAmount,
164 + amount: depositAmount ?? _request.depositAmount,
165 payoutAddress: settleAddress,
166 createdAt: DateTime.now(),
167 );
@@ -156,13 +170,17 @@ class SideShiftExchangeProvider extends ExchangeProvider {
170 Future<String> _createQuote(SideShiftRequest request) async {
171 final url = apiBaseUrl + quotePath;
172 final headers = {'Content-Type': 'application/json'};
159 - final depositMethod = _normalizeCryptoCurrency(request.depositMethod);
160 - final settleMethod = _normalizeCryptoCurrency(request.settleMethod);
173 + final depositMethod = request.depositMethod.title.toLowerCase();
174 + final settleMethod = request.settleMethod.title.toLowerCase();
175 + final depositNetwork = _networkFor(request.depositMethod);
176 + final settleNetwork = _networkFor(request.settleMethod);
177 final body = {
162 - 'depositMethod': depositMethod,
163 - 'settleMethod': settleMethod,
178 + 'depositCoin': depositMethod,
179 + 'settleCoin': settleMethod,
180 'affiliateId': affiliateId,
165 - 'depositAmount': request.depositAmount,
181 + 'settleAmount': request.depositAmount,
182 + 'settleNetwork': settleNetwork,
183 + 'depositNetwork': depositNetwork,
184 };
185 final uri = Uri.parse(url);
186 final response = await post(uri, headers: headers, body: json.encode(body));
@@ -189,9 +207,15 @@ class SideShiftExchangeProvider extends ExchangeProvider {
207 {required CryptoCurrency from,
208 required CryptoCurrency to,
209 required bool isFixedRateMode}) async {
192 - final fromCurrency = _normalizeCryptoCurrency(from);
193 - final toCurrency = _normalizeCryptoCurrency(to);
194 - final url = apiBaseUrl + rangePath + '/' + fromCurrency + '/' + toCurrency;
210 + final fromCurrency = isFixedRateMode ? to : from;
211 + final toCurrency = isFixedRateMode ? from : to;
212 +
213 + final fromNetwork = _networkFor(fromCurrency);
214 + final toNetwork = _networkFor(toCurrency);
215 +
216 + final url =
217 + "$apiBaseUrl$rangePath/${fromCurrency.title.toLowerCase()}-$fromNetwork/${toCurrency.title.toLowerCase()}-$toNetwork";
218 +
219 final uri = Uri.parse(url);
220 final response = await get(uri);
221
@@ -210,6 +234,14 @@ class SideShiftExchangeProvider extends ExchangeProvider {
234 final min = double.tryParse(responseJSON['min'] as String? ?? '');
235 final max = double.tryParse(responseJSON['max'] as String? ?? '');
236
237 + if (isFixedRateMode) {
238 + final currentRate = double.parse(responseJSON['rate'] as String);
239 + return Limits(
240 + min: min != null ? (min * currentRate) : null,
241 + max: max != null ? (max * currentRate) : null,
242 + );
243 + }
244 +
245 return Limits(min: min, max: max);
246 }
247
@@ -227,8 +259,7 @@ class SideShiftExchangeProvider extends ExchangeProvider {
259 final responseJSON = json.decode(response.body) as Map<String, dynamic>;
260 final error = responseJSON['error']['message'] as String;
261
230 - throw TradeNotFoundException(id,
231 - provider: description, description: error);
262 + throw TradeNotFoundException(id, provider: description, description: error);
263 }
264
265 if (response.statusCode != 200) {
@@ -236,36 +267,32 @@ class SideShiftExchangeProvider extends ExchangeProvider {
267 }
268
269 final responseJSON = json.decode(response.body) as Map<String, dynamic>;
239 - final fromCurrency = responseJSON['depositMethodId'] as String;
270 + final fromCurrency = responseJSON['depositCoin'] as String;
271 final from = CryptoCurrency.fromString(fromCurrency);
241 - final toCurrency = responseJSON['settleMethodId'] as String;
272 + final toCurrency = responseJSON['settleCoin'] as String;
273 final to = CryptoCurrency.fromString(toCurrency);
243 - final inputAddress = responseJSON['depositAddress']['address'] as String;
244 - final expectedSendAmount = responseJSON['depositAmount'].toString();
245 - final deposits = responseJSON['deposits'] as List?;
246 - final settleAddress = responseJSON['settleAddress']['address'] as String;
274 + final inputAddress = responseJSON['depositAddress'] as String;
275 + final expectedSendAmount = responseJSON['depositAmount'] as String?;
276 + final status = responseJSON['status'] as String?;
277 + final settleAddress = responseJSON['settleAddress'] as String;
278 TradeState? state;
248 - String? status;
279
250 - if (deposits?.isNotEmpty ?? false) {
251 - status = deposits![0]['status'] as String?;
252 - }
280 state = TradeState.deserialize(raw: status ?? 'created');
281 + final isVariable = (responseJSON['type'] as String) == 'variable';
282
255 - final expiredAtRaw = responseJSON['expiresAtISO'] as String;
256 - final expiredAt = DateTime.tryParse(expiredAtRaw)?.toLocal();
283 + final expiredAtRaw = responseJSON['expiresAt'] as String;
284 + final expiredAt = isVariable ? null : DateTime.tryParse(expiredAtRaw)?.toLocal();
285
286 return Trade(
259 - id: id,
260 - from: from,
261 - to: to,
262 - provider: description,
263 - inputAddress: inputAddress,
264 - amount: expectedSendAmount,
265 - state: state,
266 - expiredAt: expiredAt,
267 - payoutAddress: settleAddress
268 - );
287 + id: id,
288 + from: from,
289 + to: to,
290 + provider: description,
291 + inputAddress: inputAddress,
292 + amount: expectedSendAmount ?? '',
293 + state: state,
294 + expiredAt: expiredAt,
295 + payoutAddress: settleAddress);
296 }
297
298 @override
@@ -280,28 +307,25 @@ class SideShiftExchangeProvider extends ExchangeProvider {
307 @override
308 String get title => 'SideShift';
309
283 - static String _normalizeCryptoCurrency(CryptoCurrency currency) {
284 - switch (currency) {
285 - case CryptoCurrency.zaddr:
286 - return 'zaddr';
287 - case CryptoCurrency.zec:
288 - return 'zec';
289 - case CryptoCurrency.bnb:
290 - return currency.tag!.toLowerCase();
291 - case CryptoCurrency.usdterc20:
292 - return 'usdtErc20';
293 - case CryptoCurrency.usdttrc20:
294 - return 'usdtTrc20';
295 - case CryptoCurrency.usdcpoly:
296 - return 'usdcpolygon';
297 - case CryptoCurrency.usdcsol:
298 - return 'usdcsol';
299 - case CryptoCurrency.maticpoly:
310 + String _networkFor(CryptoCurrency currency) =>
311 + currency.tag != null ? _normalizeTag(currency.tag!) : 'mainnet';
312 +
313 + String _normalizeTag(String tag) {
314 + switch (tag) {
315 + case 'ETH':
316 + return 'ethereum';
317 + case 'TRX':
318 + return 'tron';
319 + case 'LN':
320 + return 'lightning';
321 + case 'POLY':
322 return 'polygon';
301 - case CryptoCurrency.btcln:
302 - return 'ln';
323 + case 'ZEC':
324 + return 'zcash';
325 + case 'AVAXC':
326 + return 'avax';
327 default:
304 - return currency.title.toLowerCase();
328 + return tag.toLowerCase();
329 }
330 }
331 }
lib/view_model/exchange/exchange_trade_view_model.dart
+7 -2
@@ -47,7 +47,7 @@ abstract class ExchangeTradeViewModelBase with Store {
47 case ExchangeProviderDescription.simpleSwap:
48 _provider = SimpleSwapExchangeProvider();
49 break;
50 - case ExchangeProviderDescription.trocador:
50 + case ExchangeProviderDescription.trocador:
51 _provider = TrocadorExchangeProvider();
52 break;
53 }
@@ -114,6 +114,10 @@ abstract class ExchangeTradeViewModelBase with Store {
114 updatedTrade.createdAt = trade.createdAt;
115 }
116
117 + if (updatedTrade.amount.isEmpty) {
118 + updatedTrade.amount = trade.amount;
119 + }
120 +
121 trade = updatedTrade;
122
123 _updateItems();
@@ -123,7 +127,8 @@ abstract class ExchangeTradeViewModelBase with Store {
127 }
128
129 void _updateItems() {
126 - final tagFrom = tradesStore.trade!.from.tag != null ? '${tradesStore.trade!.from.tag}' + ' ' : '';
130 + final tagFrom =
131 + tradesStore.trade!.from.tag != null ? '${tradesStore.trade!.from.tag}' + ' ' : '';
132 final tagTo = tradesStore.trade!.to.tag != null ? '${tradesStore.trade!.to.tag}' + ' ' : '';
133 items.clear();
134 items.add(ExchangeTradeItem(
lib/view_model/exchange/exchange_view_model.dart
+3 -1
@@ -443,7 +443,9 @@ abstract class ExchangeViewModelBase with Store {
443 request = SideShiftRequest(
444 depositMethod: depositCurrency,
445 settleMethod: receiveCurrency,
446 - depositAmount: depositAmount.replaceAll(',', '.'),
446 + depositAmount: isFixedRateMode
447 + ? receiveAmount.replaceAll(',', '.')
448 + : depositAmount.replaceAll(',', '.'),
449 settleAddress: receiveAddress,
450 refundAddress: depositAddress,
451 );
tool/utils/secret_key.dart
-1
@@ -20,7 +20,6 @@ class SecretKey {
20 SecretKey('moonPayApiKey', () => ''),
21 SecretKey('moonPaySecretKey', () => ''),
22 SecretKey('sideShiftAffiliateId', () => ''),
23 - SecretKey('sideShiftApiKey', () => ''),
23 SecretKey('simpleSwapApiKey', () => ''),
24 SecretKey('simpleSwapApiKeyDesktop', () => ''),
25 SecretKey('anypayToken', () => ''),