CWA-169 | created MorphToken exchange provider and MorphToken request (also this provider added to exchange provider description, exchange page, exchange store, router)

Oleksandr Sobol committed Jan 30, 2020 at 20:23 UTC c1a7ee01649da87218c1263035589803966af6d3
6 files changed +206 -1
lib/router.dart
+3 -1
@@ -16,6 +16,7 @@ import 'package:cake_wallet/src/domain/services/wallet_service.dart';
16 import 'package:cake_wallet/src/domain/common/crypto_currency.dart';
17 import 'package:cake_wallet/src/domain/exchange/changenow/changenow_exchange_provider.dart';
18 import 'package:cake_wallet/src/domain/exchange/xmrto/xmrto_exchange_provider.dart';
19 +import 'package:cake_wallet/src/domain/exchange/morphtoken/morphtoken_exchange_provider.dart';
20 import 'package:cake_wallet/src/domain/common/node.dart';
21 import 'package:cake_wallet/src/domain/monero/transaction_description.dart';
22 import 'package:cake_wallet/src/domain/exchange/trade.dart';
@@ -453,7 +454,8 @@ class Router {
454 trades: trades,
455 providerList: [
456 xmrtoprovider,
456 - ChangeNowExchangeProvider()
457 + ChangeNowExchangeProvider(),
458 + MorphTokenExchangeProvider()
459 ],
460 walletStore: walletStore);
461 }),
lib/src/domain/exchange/exchange_provider_description.dart
+4
@@ -8,6 +8,8 @@ class ExchangeProviderDescription extends EnumerableItem<int>
8 static const xmrto = ExchangeProviderDescription(title: 'XMR.TO', raw: 0);
9 static const changeNow =
10 ExchangeProviderDescription(title: 'ChangeNOW', raw: 1);
11 + static const morphToken =
12 + ExchangeProviderDescription(title: 'MorphToken', raw: 2);
13
14 static ExchangeProviderDescription deserialize({int raw}) {
15 switch (raw) {
@@ -15,6 +17,8 @@ class ExchangeProviderDescription extends EnumerableItem<int>
17 return xmrto;
18 case 1:
19 return changeNow;
20 + case 2:
21 + return morphToken;
22 default:
23 return null;
24 }
lib/src/domain/exchange/morphtoken/morphtoken_exchange_provider.dart new
+167
@@ -0,0 +1,167 @@
1 +import 'dart:convert';
2 +import 'package:cake_wallet/src/domain/exchange/trade_not_found_exeption.dart';
3 +import 'package:flutter/foundation.dart';
4 +import 'package:http/http.dart';
5 +import 'package:cake_wallet/src/domain/common/crypto_currency.dart';
6 +import 'package:cake_wallet/src/domain/exchange/exchange_pair.dart';
7 +import 'package:cake_wallet/src/domain/exchange/exchange_provider.dart';
8 +import 'package:cake_wallet/src/domain/exchange/limits.dart';
9 +import 'package:cake_wallet/src/domain/exchange/trade.dart';
10 +import 'package:cake_wallet/src/domain/exchange/trade_request.dart';
11 +import 'package:cake_wallet/src/domain/exchange/trade_state.dart';
12 +import 'package:cake_wallet/src/domain/exchange/morphtoken/morphtoken_request.dart';
13 +import 'package:cake_wallet/src/domain/exchange/exchange_provider_description.dart';
14 +import 'package:cake_wallet/src/domain/exchange/trade_not_created_exeption.dart';
15 +
16 +class MorphTokenExchangeProvider extends ExchangeProvider {
17 + MorphTokenExchangeProvider()
18 + : super(
19 + pairList: CryptoCurrency.all
20 + .map((i) {
21 + return CryptoCurrency.all.map((k) {
22 + if (i == CryptoCurrency.btc && k == CryptoCurrency.xmr) {
23 + return ExchangePair(from: i, to: k, reverse: false);
24 + }
25 +
26 + if (i == CryptoCurrency.xmr && k == CryptoCurrency.btc) {
27 + return null;
28 + }
29 +
30 + return ExchangePair(from: i, to: k, reverse: true);
31 + }).where((c) => c != null);
32 + })
33 + .expand((i) => i)
34 + .toList());
35 +
36 + static const apiUri = 'https://api.morphtoken.com';
37 + static const _morphURISuffix = '/morph';
38 + static const _limitsURISuffix = '/limits';
39 + static const _ratesURISuffix = '/rates';
40 + static const weight = 10000;
41 +
42 + @override
43 + String get title => 'MorphToken';
44 +
45 + @override
46 + ExchangeProviderDescription get description =>
47 + ExchangeProviderDescription.morphToken;
48 +
49 + @override
50 + Future<Limits> fetchLimits({CryptoCurrency from, CryptoCurrency to}) async {
51 + final url = apiUri + _limitsURISuffix;
52 + final headers = {'Content-type': 'application/json'};
53 + final body =
54 + json.encode({
55 + "input": {
56 + "asset": from.toString()
57 + },
58 + "output": [{
59 + "asset": to.toString(),
60 + "weight": weight
61 + }]});
62 + final response =
63 + await post(url.toString(), headers: headers, body: json.encode(body));
64 + final responseJSON = json.decode(response.body) as Map<String, dynamic>;
65 +
66 + final min = responseJSON['input']['limits']['min'] as double;
67 + final max = responseJSON['input']['limits']['max'] as double;
68 +
69 + return Limits(min: min, max: max);
70 + }
71 +
72 + @override
73 + Future<Trade> createTrade({TradeRequest request}) async {
74 + const url = apiUri + _morphURISuffix;
75 + final _request = request as MorphTokenRequest;
76 + final body = {
77 + "input": {
78 + "asset": _request.from.toString(),
79 + "refund": _request.refundAddress
80 + },
81 + "output": [{
82 + "asset": _request.to.toString(),
83 + "weight": weight,
84 + "address": _request.address
85 + }]
86 + };
87 +
88 + final response = await post(url,
89 + headers: {'Content-Type': 'application/json'}, body: json.encode(body));
90 +
91 + if (response.statusCode != 200) {
92 + if (response.statusCode == 400) {
93 + final responseJSON = json.decode(response.body) as Map<String, dynamic>;
94 + final error = responseJSON['description'] as String;
95 +
96 + throw TradeNotCreatedException(description, description: error);
97 + }
98 +
99 + throw TradeNotCreatedException(description);
100 + }
101 +
102 + final responseJSON = json.decode(response.body) as Map<String, dynamic>;
103 + final id = responseJSON['id'] as String;
104 +
105 + return Trade(
106 + id: id,
107 + provider: description,
108 + from: _request.from,
109 + to: _request.to,
110 + state: TradeState.created,
111 + amount: _request.amount,
112 + createdAt: DateTime.now());
113 + }
114 +
115 + @override
116 + Future<Trade> findTradeById({@required String id}) async {
117 + final url = apiUri + _morphURISuffix + '/' + id;
118 + final response = await get(url);
119 +
120 + if (response.statusCode != 200) {
121 + if (response.statusCode == 400) {
122 + final responseJSON = json.decode(response.body) as Map<String, dynamic>;
123 + final error = responseJSON['description'] as String;
124 +
125 + throw TradeNotFoundException(id,
126 + provider: description, description: error);
127 + }
128 +
129 + throw TradeNotFoundException(id, provider: description);
130 + }
131 +
132 + final responseJSON = json.decode(response.body) as Map<String, dynamic>;
133 + final fromCurrency = responseJSON['input']['asset'] as String;
134 + final from = CryptoCurrency.fromString(fromCurrency);
135 + final toCurrency = responseJSON['output']['asset'] as String;
136 + final to = CryptoCurrency.fromString(toCurrency);
137 + final inputAddress = responseJSON['input']['refund_address'] as String;
138 + final outputWeight = responseJSON['output']['weight'].toString();
139 + final status = responseJSON['state'] as String;
140 + final state = TradeState.deserialize(raw: status);
141 + final extraId = responseJSON['id'] as String;
142 + final outputTransaction = responseJSON['deposit_address'] as String;
143 +
144 + return Trade(
145 + id: id,
146 + from: from,
147 + to: to,
148 + provider: description,
149 + inputAddress: inputAddress,
150 + amount: outputWeight,
151 + state: state,
152 + extraId: extraId,
153 + outputTransaction: outputTransaction);
154 + }
155 +
156 + @override
157 + Future<double> calculateAmount(
158 + {CryptoCurrency from, CryptoCurrency to, double amount}) async {
159 + final url = apiUri + _ratesURISuffix;
160 + final response = await get(url);
161 + final responseJSON = json.decode(response.body) as Map<String, dynamic>;
162 + final rate = responseJSON['data'][from.toString()][to.toString()] as String;
163 + final estimatedAmount = double.parse(rate) * amount;
164 +
165 + return estimatedAmount;
166 + }
167 +}
lib/src/domain/exchange/morphtoken/morphtoken_request.dart new
+18
@@ -0,0 +1,18 @@
1 +import 'package:flutter/foundation.dart';
2 +import 'package:cake_wallet/src/domain/common/crypto_currency.dart';
3 +import 'package:cake_wallet/src/domain/exchange/trade_request.dart';
4 +
5 +class MorphTokenRequest extends TradeRequest {
6 + MorphTokenRequest(
7 + {@required this.from,
8 + @required this.to,
9 + @required this.address,
10 + @required this.amount,
11 + @required this.refundAddress});
12 +
13 + CryptoCurrency from;
14 + CryptoCurrency to;
15 + String address;
16 + String amount;
17 + String refundAddress;
18 +}
lib/src/screens/exchange/exchange_page.dart
+3
@@ -278,6 +278,9 @@ class ExchangeFormState extends State<ExchangeForm> {
278 case ExchangeProviderDescription.changeNow:
279 imageSrc = 'assets/images/change_now.png';
280 break;
281 + case ExchangeProviderDescription.morphToken:
282 + imageSrc = 'assets/images/morph_icon.png';
283 + break;
284 }
285
286 return Padding(
lib/src/stores/exchange/exchange_store.dart
+11
@@ -8,6 +8,8 @@ import 'package:cake_wallet/src/domain/exchange/exchange_provider.dart';
8 import 'package:cake_wallet/src/domain/exchange/trade_request.dart';
9 import 'package:cake_wallet/src/domain/exchange/xmrto/xmrto_exchange_provider.dart';
10 import 'package:cake_wallet/src/domain/exchange/xmrto/xmrto_trade_request.dart';
11 +import 'package:cake_wallet/src/domain/exchange/morphtoken/morphtoken_exchange_provider.dart';
12 +import 'package:cake_wallet/src/domain/exchange/morphtoken/morphtoken_request.dart';
13 import 'package:cake_wallet/src/domain/exchange/trade.dart';
14 import 'package:cake_wallet/src/stores/wallet/wallet_store.dart';
15 import 'package:cake_wallet/src/stores/exchange/exchange_trade_state.dart';
@@ -161,6 +163,15 @@ abstract class ExchangeStoreBase with Store {
163 address: receiveAddress);
164 }
165
166 + if (provider is MorphTokenExchangeProvider) {
167 + request = MorphTokenRequest(
168 + from: depositCurrency,
169 + to: receiveCurrency,
170 + amount: depositAmount,
171 + refundAddress: depositAddress,
172 + address: receiveAddress);
173 + }
174 +
175 try {
176 tradeState = TradeIsCreating();
177 final trade = await provider.createTrade(request: request);