diff --git a/android/.settings/org.eclipse.buildship.core.prefs b/android/.settings/org.eclipse.buildship.core.prefs index e8895216f..9d2efc8e7 100644 --- a/android/.settings/org.eclipse.buildship.core.prefs +++ b/android/.settings/org.eclipse.buildship.core.prefs @@ -1,2 +1,2 @@ connection.project.dir= -eclipse.preferences.version=1 +eclipse.preferences.version=1 \ No newline at end of file diff --git a/assets/images/simpleSwap.png b/assets/images/simpleSwap.png new file mode 100644 index 000000000..3c89687a5 Binary files /dev/null and b/assets/images/simpleSwap.png differ diff --git a/lib/exchange/exchange_provider_description.dart b/lib/exchange/exchange_provider_description.dart index ded72a2f9..7b3509fe1 100644 --- a/lib/exchange/exchange_provider_description.dart +++ b/lib/exchange/exchange_provider_description.dart @@ -13,6 +13,9 @@ class ExchangeProviderDescription extends EnumerableItem static const sideShift = ExchangeProviderDescription(title: 'SideShift', raw: 3); + + static const simpleSwap = + ExchangeProviderDescription(title: 'SimpleSwap', raw: 4); static ExchangeProviderDescription deserialize({int raw}) { switch (raw) { @@ -24,6 +27,8 @@ class ExchangeProviderDescription extends EnumerableItem return morphToken; case 3: return sideShift; + case 4: + return simpleSwap; default: return null; } diff --git a/lib/exchange/sideshift/sideshift_exchange_provider.dart b/lib/exchange/sideshift/sideshift_exchange_provider.dart index c512b6322..1f06de94e 100644 --- a/lib/exchange/sideshift/sideshift_exchange_provider.dart +++ b/lib/exchange/sideshift/sideshift_exchange_provider.dart @@ -47,8 +47,8 @@ class SideShiftExchangeProvider extends ExchangeProvider { if (amount == 0) { return 0.0; } - final fromCurrency = normalizeCryptoCurrency(from); - final toCurrency = normalizeCryptoCurrency(to); + final fromCurrency = _normalizeCryptoCurrency(from); + final toCurrency = _normalizeCryptoCurrency(to); final url = apiBaseUrl + rangePath + '/' + fromCurrency + '/' + toCurrency; final response = await get(url); @@ -136,8 +136,8 @@ 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 = _normalizeCryptoCurrency(request.depositMethod); + final settleMethod = _normalizeCryptoCurrency(request.settleMethod); final body = { 'depositMethod': depositMethod, 'settleMethod': settleMethod, @@ -166,8 +166,8 @@ class SideShiftExchangeProvider extends ExchangeProvider { @override Future fetchLimits( {CryptoCurrency from, CryptoCurrency to, bool isFixedRateMode}) async { - final fromCurrency = normalizeCryptoCurrency(from); - final toCurrency = normalizeCryptoCurrency(to); + final fromCurrency = _normalizeCryptoCurrency(from); + final toCurrency = _normalizeCryptoCurrency(to); final url = apiBaseUrl + rangePath + '/' + fromCurrency + '/' + toCurrency; final response = await get(url); @@ -247,7 +247,7 @@ class SideShiftExchangeProvider extends ExchangeProvider { @override String get title => 'SideShift'; - static String normalizeCryptoCurrency(CryptoCurrency currency) { + static String _normalizeCryptoCurrency(CryptoCurrency currency) { switch (currency) { case CryptoCurrency.zaddr: return 'zaddr'; diff --git a/lib/exchange/simpleswap/simpleswap_exchange_provider.dart b/lib/exchange/simpleswap/simpleswap_exchange_provider.dart new file mode 100644 index 000000000..78e83dd4d --- /dev/null +++ b/lib/exchange/simpleswap/simpleswap_exchange_provider.dart @@ -0,0 +1,217 @@ +import 'dart:convert'; + +import 'package:cake_wallet/exchange/exchange_pair.dart'; +import 'package:cake_wallet/exchange/exchange_provider.dart'; +import 'package:cake_wallet/exchange/exchange_provider_description.dart'; +import 'package:cake_wallet/exchange/simpleswap/simpleswap_request.dart'; +import 'package:cake_wallet/exchange/trade_not_created_exeption.dart'; +import 'package:cake_wallet/exchange/trade_not_found_exeption.dart'; +import 'package:cake_wallet/exchange/trade_state.dart'; +import 'package:cw_core/crypto_currency.dart'; +import 'package:cake_wallet/exchange/trade_request.dart'; +import 'package:cake_wallet/exchange/trade.dart'; +import 'package:cake_wallet/exchange/limits.dart'; +import 'package:cake_wallet/.secrets.g.dart' as secrets; +import 'package:http/http.dart'; + +class SimpleSwapExchangeProvider extends ExchangeProvider { + SimpleSwapExchangeProvider() + : super( + pairList: CryptoCurrency.all + .map((i) => + CryptoCurrency.all.map((k) => ExchangePair(from: i, to: k, reverse: true)).where((c) => c != null)) + .expand((i) => i) + .toList()); + + static const apiAuthority = 'api.simpleswap.io'; + static const getEstimatePath = '/v1/get_estimated'; + static const rangePath = '/v1/get_ranges'; + static const getExchangePath = '/v1/get_exchange'; + static const createExchangePath = '/v1/create_exchange'; + static const apiKey = secrets.simpleSwapApiKey; + + @override + ExchangeProviderDescription get description => + ExchangeProviderDescription.simpleSwap; + + @override + Future calculateAmount( + {CryptoCurrency from, CryptoCurrency to, double amount, bool isFixedRateMode, bool isReceiveAmount}) async { + try { + if (amount == 0) { + return 0.0; + } + final fromCurrency = _normalizeCryptoCurrency(from); + final toCurrency = _normalizeCryptoCurrency(to); + final params = { + 'api_key': apiKey, + 'currency_from': fromCurrency, + 'currency_to': toCurrency, + 'amount': amount.toString(), + 'fixed': isFixedRateMode.toString() + }; + final uri = Uri.https(apiAuthority, getEstimatePath, params); + + final response = await get(uri); + + if (response.body == null) return 0.00; + final data = json.decode(response.body) as String; + + return double.parse(data); + } catch (_) { + return 0.00; + } + } + + @override + Future checkIsAvailable() async { + final uri = Uri.https(apiAuthority, getEstimatePath, {'api_key': apiKey}); + final response = await get(uri); + + return !(response.statusCode == 403); + } + + @override + Future createTrade({TradeRequest request, bool isFixedRateMode}) async { + final _request = request as SimpleSwapRequest; + final headers = { + 'Content-Type': 'application/json'}; + final params = { + 'api_key': apiKey, + }; + final body = { + "currency_from": _normalizeCryptoCurrency(_request.from), + "currency_to": _normalizeCryptoCurrency(_request.to), + "amount": _request.amount, + "fixed": isFixedRateMode, + "user_refund_address": _request.refundAddress, + "address_to": _request.address + }; + final uri = Uri.https(apiAuthority, createExchangePath, params); + + final response = await post(uri, headers: headers, body: json.encode(body)); + + if (response.statusCode != 200) { + if (response.statusCode == 400) { + final responseJSON = json.decode(response.body) as Map; + final error = responseJSON['message'] as String; + + throw TradeNotCreatedException(description, description: error); + } + + throw TradeNotCreatedException(description); + } + + final responseJSON = json.decode(response.body) as Map; + final id = responseJSON['id'] as String; + final inputAddress = responseJSON['address_from'] as String; + final settleAddress = responseJSON['user_refund_address'] as String; + + return Trade( + id: id, + provider: description, + from: _request.from, + to: _request.to, + inputAddress: inputAddress, + refundAddress: settleAddress, + state: TradeState.created, + amount: _request.amount, + createdAt: DateTime.now(), + ); + } + + @override + Future fetchLimits({CryptoCurrency from, CryptoCurrency to, bool isFixedRateMode}) async { + final fromCurrency = _normalizeCryptoCurrency(from); + final toCurrency = _normalizeCryptoCurrency(to); + final params = { + 'api_key': apiKey, + 'fixed': isFixedRateMode.toString(), + 'currency_from': fromCurrency, + 'currency_to': toCurrency, + }; + final uri = Uri.https(apiAuthority, rangePath, params); + + final response = await get(uri); + + if (response.statusCode == 500) { + final responseJSON = json.decode(response.body) as Map; + final error = responseJSON['message'] as String; + + throw Exception('$error'); + } + + if (response.statusCode != 200) { + return null; + } + + final responseJSON = json.decode(response.body) as Map; + final min = responseJSON['min'] != null ? double.tryParse(responseJSON['min'] as String) : null; + final max = responseJSON['max'] != null ? double.parse(responseJSON['max'] as String) : null; + + return Limits(min: min, max: max); + } + + @override + Future findTradeById({String id}) async { + final params = {'api_key': apiKey, 'id': id}; + final uri = Uri.https(apiAuthority, getExchangePath, params); + final response = await get(uri); + + if (response.statusCode == 404) { + throw TradeNotFoundException(id, provider: description); + } + + if (response.statusCode == 400) { + final responseJSON = json.decode(response.body) as Map; + final error = responseJSON['message'] as String; + + throw TradeNotFoundException(id, provider: description, description: error); + } + + if (response.statusCode != 200) { + return null; + } + + final responseJSON = json.decode(response.body) as Map; + final fromCurrency = responseJSON['currency_from'] as String; + final from = CryptoCurrency.fromString(fromCurrency); + final toCurrency = responseJSON['currency_to'] as String; + final to = CryptoCurrency.fromString(toCurrency); + final inputAddress = responseJSON['address_from'] as String; + final expectedSendAmount = responseJSON['expected_amount'].toString(); + final status = responseJSON['status'] as String; + final state = TradeState.deserialize(raw: status); + + return Trade( + id: id, + from: from, + to: to, + provider: description, + inputAddress: inputAddress, + amount: expectedSendAmount, + state: state, + ); + } + + @override + bool get isAvailable => true; + + @override + String get title => 'SimpleSwap'; + + static String _normalizeCryptoCurrency(CryptoCurrency currency) { + switch (currency) { + case CryptoCurrency.zaddr: + return 'zec'; + case CryptoCurrency.zec: + return 'zec'; + case CryptoCurrency.bnb: + return currency.tag.toLowerCase(); + case CryptoCurrency.usdterc20: + return 'usdterc'; + default: + return currency.title.toLowerCase(); + } + } +} diff --git a/lib/exchange/simpleswap/simpleswap_request.dart b/lib/exchange/simpleswap/simpleswap_request.dart new file mode 100644 index 000000000..03a53c38e --- /dev/null +++ b/lib/exchange/simpleswap/simpleswap_request.dart @@ -0,0 +1,20 @@ +import 'package:cake_wallet/exchange/trade_request.dart'; +import 'package:cw_core/crypto_currency.dart'; +import 'package:flutter/material.dart'; + +class SimpleSwapRequest extends TradeRequest { + SimpleSwapRequest({ + @required this.from, + @required this.to, + @required this.address, + @required this.amount, + @required this.refundAddress, + }); + + CryptoCurrency from; + CryptoCurrency to; + String address; + String amount; + String toAmount; + String refundAddress; +} diff --git a/lib/src/screens/dashboard/widgets/trade_row.dart b/lib/src/screens/dashboard/widgets/trade_row.dart index faaac9edb..55b7cadc2 100644 --- a/lib/src/screens/dashboard/widgets/trade_row.dart +++ b/lib/src/screens/dashboard/widgets/trade_row.dart @@ -90,6 +90,9 @@ class TradeRow extends StatelessWidget { case ExchangeProviderDescription.sideShift: image = Image.asset('assets/images/sideshift.png', width: 36, height: 36); break; + case ExchangeProviderDescription.simpleSwap: + image = Image.asset('assets/images/simpleSwap.png', width: 36, height: 36); + break; default: image = null; } diff --git a/lib/src/screens/exchange/exchange_page.dart b/lib/src/screens/exchange/exchange_page.dart index 0c1ae01b3..3fce3029a 100644 --- a/lib/src/screens/exchange/exchange_page.dart +++ b/lib/src/screens/exchange/exchange_page.dart @@ -744,7 +744,9 @@ class ExchangePage extends BasePage { }); _receiveAmountFocus.addListener(() { + if(receiveAmountController.text.isNotEmpty){ exchangeViewModel.isFixedRateMode = true; + } exchangeViewModel.changeReceiveAmount( amount: receiveAmountController.text); }); diff --git a/lib/src/screens/exchange/exchange_template_page.dart b/lib/src/screens/exchange/exchange_template_page.dart index 7d98594d5..5b6f50844 100644 --- a/lib/src/screens/exchange/exchange_template_page.dart +++ b/lib/src/screens/exchange/exchange_template_page.dart @@ -1,8 +1,6 @@ import 'dart:ui'; import 'package:cake_wallet/exchange/exchange_provider.dart'; -import 'package:cake_wallet/exchange/exchange_template.dart'; import 'package:cake_wallet/src/screens/base_page.dart'; -import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart'; import 'package:cake_wallet/src/widgets/keyboard_done_button.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; @@ -19,9 +17,7 @@ import 'package:cake_wallet/exchange/xmrto/xmrto_exchange_provider.dart'; import 'package:cake_wallet/src/screens/exchange/widgets/exchange_card.dart'; import 'package:cake_wallet/src/widgets/primary_button.dart'; import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart'; -import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; import 'package:cake_wallet/view_model/exchange/exchange_view_model.dart'; -import 'package:cake_wallet/core/address_validator.dart'; import 'package:cake_wallet/core/amount_validator.dart'; import 'package:cake_wallet/src/screens/exchange/widgets/present_provider_picker.dart'; diff --git a/lib/src/screens/exchange/widgets/present_provider_picker.dart b/lib/src/screens/exchange/widgets/present_provider_picker.dart index 16b35d7bb..51738986a 100644 --- a/lib/src/screens/exchange/widgets/present_provider_picker.dart +++ b/lib/src/screens/exchange/widgets/present_provider_picker.dart @@ -74,6 +74,9 @@ class PresentProviderPicker extends StatelessWidget { case ExchangeProviderDescription.sideShift: images.add(Image.asset('assets/images/sideshift.png', width: 20)); break; + case ExchangeProviderDescription.simpleSwap: + images.add(Image.asset('assets/images/simpleSwap.png', width: 20)); + break; } } diff --git a/lib/store/dashboard/trade_filter_store.dart b/lib/store/dashboard/trade_filter_store.dart index 11ac2a8a4..5cb7fd9e2 100644 --- a/lib/store/dashboard/trade_filter_store.dart +++ b/lib/store/dashboard/trade_filter_store.dart @@ -11,7 +11,9 @@ abstract class TradeFilterStoreBase with Store { TradeFilterStoreBase( {this.displayXMRTO = true, this.displayChangeNow = true, - this.displayMorphToken = true}); + this.displayMorphToken = true, + this.displaySimpleSwap = true, + }); @observable bool displayXMRTO; @@ -22,6 +24,9 @@ abstract class TradeFilterStoreBase with Store { @observable bool displayMorphToken; + @observable + bool displaySimpleSwap; + @action void toggleDisplayExchange(ExchangeProviderDescription provider) { switch (provider) { @@ -34,13 +39,16 @@ abstract class TradeFilterStoreBase with Store { case ExchangeProviderDescription.morphToken: displayMorphToken = !displayMorphToken; break; + case ExchangeProviderDescription.simpleSwap: + displaySimpleSwap = !displaySimpleSwap; + break; } } List filtered({List trades, WalletBase wallet}) { final _trades = trades.where((item) => item.trade.walletId == wallet.id).toList(); - final needToFilter = !displayChangeNow || !displayXMRTO || !displayMorphToken; + final needToFilter = !displayChangeNow || !displayXMRTO || !displayMorphToken || !displaySimpleSwap; return needToFilter ? _trades @@ -52,7 +60,10 @@ abstract class TradeFilterStoreBase with Store { ExchangeProviderDescription.changeNow) || (displayMorphToken && item.trade.provider == - ExchangeProviderDescription.morphToken)) + ExchangeProviderDescription.morphToken) + ||(displaySimpleSwap && + item.trade.provider == + ExchangeProviderDescription.simpleSwap)) .toList() : _trades; } diff --git a/lib/view_model/exchange/exchange_trade_view_model.dart b/lib/view_model/exchange/exchange_trade_view_model.dart index de2ac6daa..16523a8ce 100644 --- a/lib/view_model/exchange/exchange_trade_view_model.dart +++ b/lib/view_model/exchange/exchange_trade_view_model.dart @@ -1,4 +1,6 @@ import 'dart:async'; +import 'package:cake_wallet/exchange/sideshift/sideshift_exchange_provider.dart'; +import 'package:cake_wallet/exchange/simpleswap/simpleswap_exchange_provider.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cake_wallet/exchange/changenow/changenow_exchange_provider.dart'; @@ -37,6 +39,12 @@ abstract class ExchangeTradeViewModelBase with Store { case ExchangeProviderDescription.morphToken: _provider = MorphTokenExchangeProvider(trades: trades); break; + case ExchangeProviderDescription.sideShift: + _provider = SideShiftExchangeProvider(); + break; + case ExchangeProviderDescription.simpleSwap: + _provider = SimpleSwapExchangeProvider(); + break; } items = ObservableList(); diff --git a/lib/view_model/exchange/exchange_view_model.dart b/lib/view_model/exchange/exchange_view_model.dart index 3eed44011..f0ff5ee9e 100644 --- a/lib/view_model/exchange/exchange_view_model.dart +++ b/lib/view_model/exchange/exchange_view_model.dart @@ -1,5 +1,7 @@ import 'package:cake_wallet/exchange/sideshift/sideshift_exchange_provider.dart'; import 'package:cake_wallet/exchange/sideshift/sideshift_request.dart'; +import 'package:cake_wallet/exchange/simpleswap/simpleswap_exchange_provider.dart'; +import 'package:cake_wallet/exchange/simpleswap/simpleswap_request.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/sync_status.dart'; @@ -35,7 +37,7 @@ abstract class ExchangeViewModelBase with Store { this.tradesStore, this._settingsStore) { const excludeDepositCurrencies = [CryptoCurrency.xhv]; const excludeReceiveCurrencies = [CryptoCurrency.xlm, CryptoCurrency.xrp, CryptoCurrency.bnb, CryptoCurrency.xhv]; - providerList = [ChangeNowExchangeProvider(), SideShiftExchangeProvider()]; + providerList = [ChangeNowExchangeProvider(), SideShiftExchangeProvider(), SimpleSwapExchangeProvider()]; _initialPairBasedOnWallet(); isDepositAddressEnabled = !(depositCurrency == wallet.currency); isReceiveAddressEnabled = !(receiveCurrency == wallet.currency); @@ -267,6 +269,18 @@ abstract class ExchangeViewModelBase with Store { currency = depositCurrency; } + if (provider is SimpleSwapExchangeProvider) { + request = SimpleSwapRequest( + from: depositCurrency, + to: receiveCurrency, + amount: depositAmount?.replaceAll(',', '.'), + address: receiveAddress, + refundAddress: depositAddress, + ); + amount = depositAmount; + currency = depositCurrency; + } + if (provider is XMRTOExchangeProvider) { request = XMRTOTradeRequest( from: depositCurrency, @@ -459,6 +473,6 @@ abstract class ExchangeViewModelBase with Store { isReceiveAmountEditable = false; }*/ //isReceiveAmountEditable = false; - isReceiveAmountEditable = provider is ChangeNowExchangeProvider; + isReceiveAmountEditable = provider is ChangeNowExchangeProvider || provider is SimpleSwapExchangeProvider; } } diff --git a/lib/view_model/trade_details_view_model.dart b/lib/view_model/trade_details_view_model.dart index 043028c90..00aabdebd 100644 --- a/lib/view_model/trade_details_view_model.dart +++ b/lib/view_model/trade_details_view_model.dart @@ -4,6 +4,7 @@ import 'package:cake_wallet/exchange/exchange_provider.dart'; import 'package:cake_wallet/exchange/exchange_provider_description.dart'; import 'package:cake_wallet/exchange/morphtoken/morphtoken_exchange_provider.dart'; import 'package:cake_wallet/exchange/sideshift/sideshift_exchange_provider.dart'; +import 'package:cake_wallet/exchange/simpleswap/simpleswap_exchange_provider.dart'; import 'package:cake_wallet/exchange/trade.dart'; import 'package:cake_wallet/exchange/xmrto/xmrto_exchange_provider.dart'; import 'package:cake_wallet/utils/date_formatter.dart'; @@ -35,6 +36,9 @@ abstract class TradeDetailsViewModelBase with Store { case ExchangeProviderDescription.sideShift: _provider = SideShiftExchangeProvider(); break; + case ExchangeProviderDescription.simpleSwap: + _provider = SimpleSwapExchangeProvider(); + break; } items = ObservableList(); @@ -112,6 +116,12 @@ abstract class TradeDetailsViewModelBase with Store { title: 'Track', value: buildURL, onTap: () => launch(buildURL))); } + if (trade.provider == ExchangeProviderDescription.simpleSwap) { + final buildURL = 'https://simpleswap.io/exchange?id=${trade.id.toString()}'; + items.add(TrackTradeListItem( + title: 'Track', value: buildURL, onTap: () => launch(buildURL))); + } + if (trade.createdAt != null) { items.add(StandartListItem( title: S.current.trade_details_created_at, diff --git a/tool/utils/secret_key.dart b/tool/utils/secret_key.dart index aa9928c8c..9bf360017 100644 --- a/tool/utils/secret_key.dart +++ b/tool/utils/secret_key.dart @@ -25,6 +25,7 @@ class SecretKey { SecretKey('moonPaySecretKey', () => ''), SecretKey('sideShiftAffiliateId', () => ''), SecretKey('sideShiftApiKey', () => ''), + SecretKey('simpleSwapApiKey', () => '') ]; final String name;