diff --git a/lib/router.dart b/lib/router.dart index c10ebea12..1698737c6 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -16,6 +16,7 @@ import 'package:cake_wallet/src/domain/services/wallet_service.dart'; import 'package:cake_wallet/src/domain/common/crypto_currency.dart'; import 'package:cake_wallet/src/domain/exchange/changenow/changenow_exchange_provider.dart'; import 'package:cake_wallet/src/domain/exchange/xmrto/xmrto_exchange_provider.dart'; +import 'package:cake_wallet/src/domain/exchange/morphtoken/morphtoken_exchange_provider.dart'; import 'package:cake_wallet/src/domain/common/node.dart'; import 'package:cake_wallet/src/domain/monero/transaction_description.dart'; import 'package:cake_wallet/src/domain/exchange/trade.dart'; @@ -453,7 +454,8 @@ class Router { trades: trades, providerList: [ xmrtoprovider, - ChangeNowExchangeProvider() + ChangeNowExchangeProvider(), + MorphTokenExchangeProvider() ], walletStore: walletStore); }), diff --git a/lib/src/domain/exchange/exchange_provider_description.dart b/lib/src/domain/exchange/exchange_provider_description.dart index 0ae388848..67a2565d8 100644 --- a/lib/src/domain/exchange/exchange_provider_description.dart +++ b/lib/src/domain/exchange/exchange_provider_description.dart @@ -8,6 +8,8 @@ class ExchangeProviderDescription extends EnumerableItem static const xmrto = ExchangeProviderDescription(title: 'XMR.TO', raw: 0); static const changeNow = ExchangeProviderDescription(title: 'ChangeNOW', raw: 1); + static const morphToken = + ExchangeProviderDescription(title: 'MorphToken', raw: 2); static ExchangeProviderDescription deserialize({int raw}) { switch (raw) { @@ -15,6 +17,8 @@ class ExchangeProviderDescription extends EnumerableItem return xmrto; case 1: return changeNow; + case 2: + return morphToken; default: return null; } diff --git a/lib/src/domain/exchange/morphtoken/morphtoken_exchange_provider.dart b/lib/src/domain/exchange/morphtoken/morphtoken_exchange_provider.dart new file mode 100644 index 000000000..ed06462a7 --- /dev/null +++ b/lib/src/domain/exchange/morphtoken/morphtoken_exchange_provider.dart @@ -0,0 +1,167 @@ +import 'dart:convert'; +import 'package:cake_wallet/src/domain/exchange/trade_not_found_exeption.dart'; +import 'package:flutter/foundation.dart'; +import 'package:http/http.dart'; +import 'package:cake_wallet/src/domain/common/crypto_currency.dart'; +import 'package:cake_wallet/src/domain/exchange/exchange_pair.dart'; +import 'package:cake_wallet/src/domain/exchange/exchange_provider.dart'; +import 'package:cake_wallet/src/domain/exchange/limits.dart'; +import 'package:cake_wallet/src/domain/exchange/trade.dart'; +import 'package:cake_wallet/src/domain/exchange/trade_request.dart'; +import 'package:cake_wallet/src/domain/exchange/trade_state.dart'; +import 'package:cake_wallet/src/domain/exchange/morphtoken/morphtoken_request.dart'; +import 'package:cake_wallet/src/domain/exchange/exchange_provider_description.dart'; +import 'package:cake_wallet/src/domain/exchange/trade_not_created_exeption.dart'; + +class MorphTokenExchangeProvider extends ExchangeProvider { + MorphTokenExchangeProvider() + : super( + pairList: CryptoCurrency.all + .map((i) { + return CryptoCurrency.all.map((k) { + if (i == CryptoCurrency.btc && k == CryptoCurrency.xmr) { + return ExchangePair(from: i, to: k, reverse: false); + } + + if (i == CryptoCurrency.xmr && k == CryptoCurrency.btc) { + return null; + } + + return ExchangePair(from: i, to: k, reverse: true); + }).where((c) => c != null); + }) + .expand((i) => i) + .toList()); + + static const apiUri = 'https://api.morphtoken.com'; + static const _morphURISuffix = '/morph'; + static const _limitsURISuffix = '/limits'; + static const _ratesURISuffix = '/rates'; + static const weight = 10000; + + @override + String get title => 'MorphToken'; + + @override + ExchangeProviderDescription get description => + ExchangeProviderDescription.morphToken; + + @override + Future fetchLimits({CryptoCurrency from, CryptoCurrency to}) async { + final url = apiUri + _limitsURISuffix; + final headers = {'Content-type': 'application/json'}; + final body = + json.encode({ + "input": { + "asset": from.toString() + }, + "output": [{ + "asset": to.toString(), + "weight": weight + }]}); + final response = + await post(url.toString(), headers: headers, body: json.encode(body)); + final responseJSON = json.decode(response.body) as Map; + + final min = responseJSON['input']['limits']['min'] as double; + final max = responseJSON['input']['limits']['max'] as double; + + return Limits(min: min, max: max); + } + + @override + Future createTrade({TradeRequest request}) async { + const url = apiUri + _morphURISuffix; + final _request = request as MorphTokenRequest; + final body = { + "input": { + "asset": _request.from.toString(), + "refund": _request.refundAddress + }, + "output": [{ + "asset": _request.to.toString(), + "weight": weight, + "address": _request.address + }] + }; + + final response = await post(url, + headers: {'Content-Type': 'application/json'}, body: json.encode(body)); + + if (response.statusCode != 200) { + if (response.statusCode == 400) { + final responseJSON = json.decode(response.body) as Map; + final error = responseJSON['description'] as String; + + throw TradeNotCreatedException(description, description: error); + } + + throw TradeNotCreatedException(description); + } + + final responseJSON = json.decode(response.body) as Map; + final id = responseJSON['id'] as String; + + return Trade( + id: id, + provider: description, + from: _request.from, + to: _request.to, + state: TradeState.created, + amount: _request.amount, + createdAt: DateTime.now()); + } + + @override + Future findTradeById({@required String id}) async { + final url = apiUri + _morphURISuffix + '/' + id; + final response = await get(url); + + if (response.statusCode != 200) { + if (response.statusCode == 400) { + final responseJSON = json.decode(response.body) as Map; + final error = responseJSON['description'] as String; + + throw TradeNotFoundException(id, + provider: description, description: error); + } + + throw TradeNotFoundException(id, provider: description); + } + + final responseJSON = json.decode(response.body) as Map; + final fromCurrency = responseJSON['input']['asset'] as String; + final from = CryptoCurrency.fromString(fromCurrency); + final toCurrency = responseJSON['output']['asset'] as String; + final to = CryptoCurrency.fromString(toCurrency); + final inputAddress = responseJSON['input']['refund_address'] as String; + final outputWeight = responseJSON['output']['weight'].toString(); + final status = responseJSON['state'] as String; + final state = TradeState.deserialize(raw: status); + final extraId = responseJSON['id'] as String; + final outputTransaction = responseJSON['deposit_address'] as String; + + return Trade( + id: id, + from: from, + to: to, + provider: description, + inputAddress: inputAddress, + amount: outputWeight, + state: state, + extraId: extraId, + outputTransaction: outputTransaction); + } + + @override + Future calculateAmount( + {CryptoCurrency from, CryptoCurrency to, double amount}) async { + final url = apiUri + _ratesURISuffix; + final response = await get(url); + final responseJSON = json.decode(response.body) as Map; + final rate = responseJSON['data'][from.toString()][to.toString()] as String; + final estimatedAmount = double.parse(rate) * amount; + + return estimatedAmount; + } +} diff --git a/lib/src/domain/exchange/morphtoken/morphtoken_request.dart b/lib/src/domain/exchange/morphtoken/morphtoken_request.dart new file mode 100644 index 000000000..f034372d9 --- /dev/null +++ b/lib/src/domain/exchange/morphtoken/morphtoken_request.dart @@ -0,0 +1,18 @@ +import 'package:flutter/foundation.dart'; +import 'package:cake_wallet/src/domain/common/crypto_currency.dart'; +import 'package:cake_wallet/src/domain/exchange/trade_request.dart'; + +class MorphTokenRequest extends TradeRequest { + MorphTokenRequest( + {@required this.from, + @required this.to, + @required this.address, + @required this.amount, + @required this.refundAddress}); + + CryptoCurrency from; + CryptoCurrency to; + String address; + String amount; + String refundAddress; +} diff --git a/lib/src/screens/exchange/exchange_page.dart b/lib/src/screens/exchange/exchange_page.dart index a5cb2b55c..ce533c3cd 100644 --- a/lib/src/screens/exchange/exchange_page.dart +++ b/lib/src/screens/exchange/exchange_page.dart @@ -278,6 +278,9 @@ class ExchangeFormState extends State { case ExchangeProviderDescription.changeNow: imageSrc = 'assets/images/change_now.png'; break; + case ExchangeProviderDescription.morphToken: + imageSrc = 'assets/images/morph_icon.png'; + break; } return Padding( diff --git a/lib/src/stores/exchange/exchange_store.dart b/lib/src/stores/exchange/exchange_store.dart index a0cf932e4..832187757 100644 --- a/lib/src/stores/exchange/exchange_store.dart +++ b/lib/src/stores/exchange/exchange_store.dart @@ -8,6 +8,8 @@ import 'package:cake_wallet/src/domain/exchange/exchange_provider.dart'; import 'package:cake_wallet/src/domain/exchange/trade_request.dart'; import 'package:cake_wallet/src/domain/exchange/xmrto/xmrto_exchange_provider.dart'; import 'package:cake_wallet/src/domain/exchange/xmrto/xmrto_trade_request.dart'; +import 'package:cake_wallet/src/domain/exchange/morphtoken/morphtoken_exchange_provider.dart'; +import 'package:cake_wallet/src/domain/exchange/morphtoken/morphtoken_request.dart'; import 'package:cake_wallet/src/domain/exchange/trade.dart'; import 'package:cake_wallet/src/stores/wallet/wallet_store.dart'; import 'package:cake_wallet/src/stores/exchange/exchange_trade_state.dart'; @@ -161,6 +163,15 @@ abstract class ExchangeStoreBase with Store { address: receiveAddress); } + if (provider is MorphTokenExchangeProvider) { + request = MorphTokenRequest( + from: depositCurrency, + to: receiveCurrency, + amount: depositAmount, + refundAddress: depositAddress, + address: receiveAddress); + } + try { tradeState = TradeIsCreating(); final trade = await provider.createTrade(request: request);