Cw 793 implement kryptonim dfx to fiat buy sell option (#2068)
* init commit * add authorization data * Update lib/buy/kryptonim/kryptonim.dart --------- Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>
Serhii committed
Mar 6, 2025 at 19:39 UTC
1aad8366c4786ffddbd0afcd5b18cd2a6257eff8
16 files changed
+285
-10
.github/workflows/pr_test_build_android.yml
+1
@@ -172,6 +172,7 @@ jobs:
172
# end of test secrets
173
echo "const chainflipApiKey = '${{ secrets.CHAINFLIP_API_KEY }}';" >> lib/.secrets.g.dart
174
echo "const chainflipAffiliateFee = '${{ secrets.CHAINFLIP_AFFILIATE_FEE }}';" >> lib/.secrets.g.dart
175
+ echo "const kryptonimApiKey = '${{ secrets.KRYPTONIM_API_KEY }}';" >> lib/.secrets.g.dart
176
echo "const walletGroupSalt = '${{ secrets.WALLET_GROUP_SALT }}';" >> lib/.secrets.g.dart
177
178
- name: prepare monero_c and cache
.github/workflows/pr_test_build_linux.yml
+1
@@ -168,6 +168,7 @@ jobs:
168
# end of test secrets
169
echo "const chainflipApiKey = '${{ secrets.CHAINFLIP_API_KEY }}';" >> lib/.secrets.g.dart
170
echo "const chainflipAffiliateFee = '${{ secrets.CHAINFLIP_AFFILIATE_FEE }}';" >> lib/.secrets.g.dart
171
+ echo "const kryptonimApiKey = '${{ secrets.KRYPTONIM_API_KEY }}';" >> lib/.secrets.g.dart
172
echo "const walletGroupSalt = '${{ secrets.WALLET_GROUP_SALT }}';" >> lib/.secrets.g.dart
173
174
- name: prepare monero_c and cache
assets/images/kryptonim_dark.png
Binary files /dev/null and b/assets/images/kryptonim_dark.png differ
assets/images/kryptonim_light.png
Binary files /dev/null and b/assets/images/kryptonim_light.png differ
lib/buy/buy_provider.dart
+1
-1
@@ -49,7 +49,7 @@ abstract class BuyProvider {
49
throw UnimplementedError();
50
51
Future<List<PaymentMethod>> getAvailablePaymentTypes(
52
- String fiatCurrency, String cryptoCurrency, bool isBuyAction) async =>
52
+ String fiatCurrency, CryptoCurrency cryptoCurrency, bool isBuyAction) async =>
53
[];
54
55
Future<List<Quote>?> fetchQuote(
lib/buy/buy_quote.dart
+26
@@ -289,6 +289,29 @@ class Quote extends SelectableOption {
289
);
290
}
291
292
+ factory Quote.fromKryptonimJson(
293
+ Map<String, dynamic> json, bool isBuyAction, PaymentType paymentType) {
294
+ final fees = json['fees'] as Map<String, dynamic>;
295
+ final rate = _toDouble(json['rate']) ?? 0.0;
296
+ final limits = json['limits'] as Map<String, dynamic>;
297
+ final minLimit = _toDouble(limits['min_amount']) ?? 0.0;
298
+ final maxLimit = _toDouble(limits['max_amount']) ?? double.infinity;
299
+ final convertedAmount = _toDouble(json['converted_amount']) ?? 0.0;
300
+ final amount = _toDouble(json['amount']) ?? 0.0;
301
+ final calculatedRate = amount / convertedAmount;
302
+ return Quote(
303
+ rate: calculatedRate,
304
+ feeAmount: _toDouble(fees['totalFee']) ?? 0.0,
305
+ networkFee: _toDouble(fees['network_fee']) ?? 0.0,
306
+ transactionFee: _toDouble(fees['operation_fee']) ?? 0.0,
307
+ payout: _toDouble(json['amount']) ?? 0.0,
308
+ paymentType: paymentType,
309
+ recommendations: [],
310
+ provider: ProvidersHelper.getProviderByType(ProviderType.kriptonim)!,
311
+ isBuyAction: isBuyAction,
312
+ limits: Limits(min: minLimit, max: maxLimit));
313
+ }
314
+
315
static double? _toDouble(dynamic value) {
316
if (value is int) {
317
return value.toDouble();
@@ -299,4 +322,7 @@ class Quote extends SelectableOption {
322
}
323
return null;
324
}
325
+
326
+ @override
327
+ String toString() => 'Quote: rate: $rate, feeAmount: $feeAmount, networkFee: $networkFee, transactionFee: $transactionFee, payout: $payout, paymentType: $paymentType, provider: $provider, quoteId: $quoteId, recommendations: $recommendations, isBuyAction: $isBuyAction, rampId: $rampId, rampName: $rampName, rampIconPath: $rampIconPath, [limits: min: ${limits?.min}, max: ${limits?.max}]';
328
}
lib/buy/dfx/dfx_buy_provider.dart
+2
-2
@@ -168,7 +168,7 @@ class DFXBuyProvider extends BuyProvider {
168
}
169
170
Future<List<PaymentMethod>> getAvailablePaymentTypes(
171
- String fiatCurrency, String cryptoCurrency, bool isBuyAction) async {
171
+ String fiatCurrency, CryptoCurrency cryptoCurrency, bool isBuyAction) async {
172
final List<PaymentMethod> paymentMethods = [];
173
174
if (isBuyAction) {
@@ -190,7 +190,7 @@ class DFXBuyProvider extends BuyProvider {
190
});
191
}
192
} else {
193
- final assetCredentials = await fetchAssetCredential(cryptoCurrency);
193
+ final assetCredentials = await fetchAssetCredential(cryptoCurrency.title);
194
if (assetCredentials.isNotEmpty) {
195
if (assetCredentials['sellable'] == true) {
196
final availablePaymentTypes = [
lib/buy/kryptonim/kryptonim.dart
new
+222
@@ -0,0 +1,222 @@
1
+import 'dart:convert';
2
+
3
+import 'package:cake_wallet/.secrets.g.dart' as secrets;
4
+import 'package:cake_wallet/buy/buy_provider.dart';
5
+import 'package:cake_wallet/buy/buy_quote.dart';
6
+import 'package:cake_wallet/buy/payment_method.dart';
7
+import 'package:cake_wallet/entities/fiat_currency.dart';
8
+import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
9
+import 'package:cake_wallet/utils/show_pop_up.dart';
10
+import 'package:cw_core/crypto_currency.dart';
11
+import 'package:cw_core/wallet_base.dart';
12
+import 'package:flutter/material.dart';
13
+import 'dart:developer';
14
+import 'package:http/http.dart' as http;
15
+import 'package:url_launcher/url_launcher.dart';
16
+
17
+class KryptonimBuyProvider extends BuyProvider {
18
+ KryptonimBuyProvider({required WalletBase wallet, bool isTestEnvironment = false})
19
+ : super(wallet: wallet, isTestEnvironment: isTestEnvironment, ledgerVM: null);
20
+
21
+ static const _isProduction = true;
22
+
23
+ static const _baseUrl = _isProduction ? 'app.kryptonim.com' : 'intg-api.kryptonim.com';
24
+ static const _baseWidgetUrl = _isProduction ? 'buy.kryptonim.com' : 'intg.kryptonim.com';
25
+ static const _quotePath = '/v2/ramp/buy/quotes';
26
+ static const _merchantId = 'a70fe053';
27
+
28
+ static String get _kryptonimApiKey => secrets.kryptonimApiKey;
29
+
30
+ @override
31
+ String get title => 'Kryptonim';
32
+
33
+ @override
34
+ String get providerDescription => 'Kryptonim Buy Provider';
35
+
36
+ @override
37
+ String get lightIcon => 'assets/images/kryptonim_light.png';
38
+
39
+ @override
40
+ String get darkIcon => 'assets/images/kryptonim_dark.png';
41
+
42
+ @override
43
+ bool get isAggregator => false;
44
+
45
+ Future<Map<String, dynamic>> getExchangeRates(
46
+ {required CryptoCurrency cryptoCurrency,
47
+ required String fiatCurrency,
48
+ required double amount}) async {
49
+ final url = Uri.https(_baseUrl, _quotePath, {'m': _merchantId});
50
+
51
+ final headers = {
52
+ 'accept': 'application/json',
53
+ 'Content-Type': 'application/json',
54
+ 'Authorization': _kryptonimApiKey,
55
+ };
56
+
57
+ final body = jsonEncode({
58
+ 'amount': amount,
59
+ 'currency': fiatCurrency,
60
+ 'converted_currency': cryptoCurrency.title,
61
+ 'blockchain': _normalizeBlockChain(cryptoCurrency),
62
+ 'quote_currency': fiatCurrency,
63
+ });
64
+
65
+ try {
66
+ final response = await http.post(url, headers: headers, body: body);
67
+
68
+ if (response.statusCode == 200 || response.statusCode == 201 || response.statusCode == 401) {
69
+ return jsonDecode(response.body) as Map<String, dynamic>;
70
+ } else {
71
+ return {};
72
+ }
73
+ } catch (e) {
74
+ return {};
75
+ }
76
+ }
77
+
78
+ @override
79
+ Future<List<PaymentMethod>> getAvailablePaymentTypes(
80
+ String fiatCurrency, CryptoCurrency cryptoCurrency, bool isBuyAction) async {
81
+
82
+ final data = await getExchangeRates(
83
+ cryptoCurrency: cryptoCurrency,
84
+ fiatCurrency: fiatCurrency,
85
+ amount: 100.0,
86
+ );
87
+
88
+ if (data.isEmpty || !data.containsKey('data')) return [];
89
+
90
+ final paymentMethods = (data['data'] as List<dynamic>)
91
+ .map((e) => PaymentMethod.fromKryptonimJson(e as Map<String, dynamic>))
92
+ .toList();
93
+
94
+ return paymentMethods;
95
+ }
96
+
97
+ @override
98
+ Future<List<Quote>?> fetchQuote({
99
+ required CryptoCurrency cryptoCurrency,
100
+ required FiatCurrency fiatCurrency,
101
+ required double amount,
102
+ required bool isBuyAction,
103
+ required String walletAddress,
104
+ PaymentType? paymentType,
105
+ String? countryCode,
106
+ }) async {
107
+ log('Kryptonim: Fetching quote: ${isBuyAction ? cryptoCurrency : fiatCurrency} -> ${isBuyAction ? fiatCurrency : cryptoCurrency}, amount: $amount');
108
+
109
+ final data = await getExchangeRates(
110
+ cryptoCurrency: cryptoCurrency,
111
+ fiatCurrency: fiatCurrency.toString(),
112
+ amount: amount,
113
+ );
114
+
115
+ if (!data.containsKey('data') || (data['data'] as List).isEmpty) {
116
+ return null;
117
+ }
118
+
119
+ final quotesList = data['data'] as List<dynamic>;
120
+
121
+ Map<String, dynamic>? selectedPaymentMethod;
122
+
123
+ if (paymentType == PaymentType.all || paymentType == null) {
124
+ selectedPaymentMethod = quotesList.first as Map<String, dynamic>;
125
+ } else {
126
+ for (var quote in quotesList) {
127
+ final quotePaymentType = PaymentMethod.getPaymentTypeId(quote['payment_method'] as String?);
128
+ if (quotePaymentType == paymentType) {
129
+ selectedPaymentMethod = quote as Map<String, dynamic>;
130
+ break;
131
+ }
132
+ }
133
+ }
134
+
135
+ if (selectedPaymentMethod == null) {
136
+ return null;
137
+ }
138
+
139
+ final selectedPaymentType =
140
+ PaymentMethod.getPaymentTypeId(selectedPaymentMethod['payment_method'] as String?);
141
+ final quote = Quote.fromKryptonimJson(selectedPaymentMethod, isBuyAction, selectedPaymentType);
142
+
143
+ quote.setFiatCurrency = fiatCurrency;
144
+ quote.setCryptoCurrency = cryptoCurrency;
145
+
146
+ return [quote];
147
+ }
148
+
149
+ @override
150
+ Future<void>? launchProvider(
151
+ {required BuildContext context,
152
+ required Quote quote,
153
+ required double amount,
154
+ required bool isBuyAction,
155
+ required String cryptoCurrencyAddress,
156
+ String? countryCode}) async {
157
+ final params = {
158
+ 'amount': amount.toInt().toString(),
159
+ 'currency': quote.fiatCurrency.name,
160
+ 'convertedCurrency': quote.cryptoCurrency.title,
161
+ 'blockchain': _normalizeBlockChain(quote.cryptoCurrency),
162
+ 'address': cryptoCurrencyAddress,
163
+ 'paymentMethod': normalizePaymentMethod(quote.paymentType),
164
+ };
165
+
166
+ final uri = Uri.https(_baseWidgetUrl, '/redirect-form', params);
167
+
168
+ try {
169
+ if (await canLaunchUrl(uri)) {
170
+ await launchUrl(uri, mode: LaunchMode.externalApplication);
171
+ } else {
172
+ throw Exception('Could not launch URL');
173
+ }
174
+ } catch (e) {
175
+ await showPopUp<void>(
176
+ context: context,
177
+ builder: (BuildContext context) {
178
+ return AlertWithOneAction(
179
+ alertTitle: "Kryptonim",
180
+ alertContent: "Payment provider is unavailable: $e",
181
+ buttonText: "OK",
182
+ buttonAction: () => Navigator.of(context).pop(),
183
+ );
184
+ },
185
+ );
186
+ }
187
+ }
188
+
189
+ String normalizePaymentMethod(PaymentType paymentType) {
190
+ switch (paymentType) {
191
+ case PaymentType.bankTransfer:
192
+ return 'bank';
193
+ case PaymentType.creditCard:
194
+ case PaymentType.debitCard:
195
+ return 'card';
196
+ default:
197
+ return paymentType.name.toLowerCase();
198
+ }
199
+ }
200
+
201
+ String _normalizeBlockChain(CryptoCurrency cur) {
202
+ String? blockchain = switch (cur.tag) {
203
+ 'ETH' => 'Ethereum',
204
+ 'POL' => 'Polygon',
205
+ 'AVAXC' => 'Avalanche',
206
+ 'SOL' => 'Solana',
207
+ _ => null,
208
+ };
209
+
210
+ if (blockchain == null) {
211
+ blockchain = switch (cur) {
212
+ CryptoCurrency.btc => 'Bitcoin',
213
+ CryptoCurrency.ltc => 'Litecoin',
214
+ CryptoCurrency.eth => 'Ethereum',
215
+ CryptoCurrency.maticpoly => 'Matic',
216
+ _ => null,
217
+ };
218
+ }
219
+
220
+ return blockchain ?? cur.fullName ?? '';
221
+ }
222
+}
lib/buy/meld/meld_buy_provider.dart
+1
-1
@@ -53,7 +53,7 @@ class MeldBuyProvider extends BuyProvider {
53
54
@override
55
Future<List<PaymentMethod>> getAvailablePaymentTypes(
56
- String fiatCurrency, String cryptoCurrency, bool isBuyAction) async {
56
+ String fiatCurrency, CryptoCurrency cryptoCurrency, bool isBuyAction) async {
57
final params = {'fiatCurrencies': fiatCurrency, 'statuses': 'LIVE,RECENTLY_ADDED,BUILDING'};
58
59
final path = '$_providersProperties$_paymentMethodsPath';
lib/buy/moonpay/moonpay_provider.dart
+2
-2
@@ -126,11 +126,11 @@ class MoonPayProvider extends BuyProvider {
126
}
127
128
Future<List<PaymentMethod>> getAvailablePaymentTypes(
129
- String fiatCurrency, String cryptoCurrency, bool isBuyAction) async {
129
+ String fiatCurrency, CryptoCurrency cryptoCurrency, bool isBuyAction) async {
130
final List<PaymentMethod> paymentMethods = [];
131
132
if (isBuyAction) {
133
- final fiatBuyCredentials = await fetchFiatCredentials(fiatCurrency, cryptoCurrency, null);
133
+ final fiatBuyCredentials = await fetchFiatCredentials(fiatCurrency, cryptoCurrency.title, null);
134
if (fiatBuyCredentials.isNotEmpty) {
135
final paymentMethod = fiatBuyCredentials['paymentMethod'] as String?;
136
paymentMethods.add(PaymentMethod.fromMoonPayJson(
lib/buy/onramper/onramper_buy_provider.dart
+1
-1
@@ -48,7 +48,7 @@ class OnRamperBuyProvider extends BuyProvider {
48
bool get isAggregator => true;
49
50
Future<List<PaymentMethod>> getAvailablePaymentTypes(
51
- String fiatCurrency, String cryptoCurrency, bool isBuyAction) async {
51
+ String fiatCurrency, CryptoCurrency cryptoCurrency, bool isBuyAction) async {
52
final params = {
53
'fiatCurrency': fiatCurrency,
54
'type': isBuyAction ? 'buy' : 'sell',
lib/buy/payment_method.dart
+9
@@ -218,6 +218,15 @@ class PaymentMethod extends SelectableOption {
218
customDescription: json['description'] as String?);
219
}
220
221
+ factory PaymentMethod.fromKryptonimJson(Map<String, dynamic> json) {
222
+ final type = PaymentMethod.getPaymentTypeId(json['payment_method'] as String?);
223
+ return PaymentMethod(
224
+ paymentMethodType: type,
225
+ customTitle: json['payment_method'] as String? ?? 'Unknown',
226
+ customIconPath: 'assets/images/card.png',
227
+ );
228
+ }
229
+
230
static PaymentType getPaymentTypeId(String? type) {
231
switch (type?.toLowerCase()) {
232
case 'banktransfer':
lib/di.dart
+5
@@ -255,6 +255,7 @@ import 'package:get_it/get_it.dart';
255
import 'package:hive/hive.dart';
256
import 'package:mobx/mobx.dart';
257
import 'package:shared_preferences/shared_preferences.dart';
258
+import 'buy/kryptonim/kryptonim.dart';
259
import 'buy/meld/meld_buy_provider.dart';
260
import 'src/screens/buy/buy_sell_page.dart';
261
import 'cake_pay/cake_pay_payment_credantials.dart';
@@ -1019,6 +1020,10 @@ Future<void> setup({
1020
wallet: getIt.get<AppStore>().wallet!,
1021
));
1022
1023
+ getIt.registerFactory<KryptonimBuyProvider>(() => KryptonimBuyProvider(
1024
+ wallet: getIt.get<AppStore>().wallet!,
1025
+ ));
1026
+
1027
getIt.registerFactoryParam<WebViewPage, String, Uri>((title, uri) => WebViewPage(title, uri));
1028
1029
getIt.registerFactory(() => ExchangeViewModel(
lib/entities/provider_types.dart
+12
-2
@@ -1,5 +1,6 @@
1
import 'package:cake_wallet/buy/buy_provider.dart';
2
import 'package:cake_wallet/buy/dfx/dfx_buy_provider.dart';
3
+import 'package:cake_wallet/buy/kryptonim/kryptonim.dart';
4
import 'package:cake_wallet/buy/meld/meld_buy_provider.dart';
5
import 'package:cake_wallet/buy/moonpay/moonpay_provider.dart';
6
import 'package:cake_wallet/buy/onramper/onramper_buy_provider.dart';
@@ -8,7 +9,7 @@ import 'package:cake_wallet/di.dart';
9
import 'package:cw_core/wallet_type.dart';
10
import 'package:http/http.dart';
11
11
-enum ProviderType { robinhood, dfx, onramper, moonpay, meld }
12
+enum ProviderType { robinhood, dfx, onramper, moonpay, meld, kriptonim }
13
14
extension ProviderTypeName on ProviderType {
15
String get title {
@@ -23,6 +24,8 @@ extension ProviderTypeName on ProviderType {
24
return 'MoonPay';
25
case ProviderType.meld:
26
return 'Meld';
27
+ case ProviderType.kriptonim:
28
+ return 'Kriptonim';
29
}
30
}
31
@@ -38,6 +41,8 @@ extension ProviderTypeName on ProviderType {
41
return 'moonpay_provider';
42
case ProviderType.meld:
43
return 'meld_provider';
44
+ case ProviderType.kriptonim:
45
+ return 'kriptonim_provider';
46
}
47
}
48
}
@@ -59,6 +64,7 @@ class ProvidersHelper {
64
ProviderType.dfx,
65
ProviderType.robinhood,
66
ProviderType.moonpay,
67
+ ProviderType.kriptonim
68
];
69
case WalletType.litecoin:
70
case WalletType.bitcoinCash:
@@ -66,13 +72,15 @@ class ProvidersHelper {
72
return [
73
ProviderType.onramper,
74
ProviderType.robinhood,
69
- ProviderType.moonpay
75
+ ProviderType.moonpay,
76
+ ProviderType.kriptonim
77
];
78
case WalletType.tron:
79
return [
80
ProviderType.onramper,
81
ProviderType.robinhood,
82
ProviderType.moonpay,
83
+ ProviderType.kriptonim
84
];
85
case WalletType.none:
86
case WalletType.haven:
@@ -127,6 +135,8 @@ class ProvidersHelper {
135
return getIt.get<MoonPayProvider>();
136
case ProviderType.meld:
137
return getIt.get<MeldBuyProvider>();
138
+ case ProviderType.kriptonim:
139
+ return getIt.get<KryptonimBuyProvider>();
140
default:
141
return null;
142
}
lib/view_model/buy/buy_sell_view_model.dart
+1
-1
@@ -351,7 +351,7 @@ abstract class BuySellViewModelBase extends WalletChangeListenerViewModel with S
351
paymentMethodState = PaymentMethodLoading();
352
selectedPaymentMethod = null;
353
final result = await Future.wait(providerList.map((element) => element
354
- .getAvailablePaymentTypes(fiatCurrency.title, cryptoCurrency.title, isBuyAction)
354
+ .getAvailablePaymentTypes(fiatCurrency.title, cryptoCurrency, isBuyAction)
355
.timeout(
356
Duration(seconds: 10),
357
onTimeout: () => [],
tool/utils/secret_key.dart
+1
@@ -77,6 +77,7 @@ class SecretKey {
77
SecretKey('moneroTestWalletBlockHeight', () => ''),
78
SecretKey('chainflipApiKey', () => ''),
79
SecretKey('chainflipAffiliateFee', () => ''),
80
+ SecretKey('kryptonimApiKey', () => ''),
81
SecretKey('walletGroupSalt', () => hex.encode(encrypt.Key.fromSecureRandom(16).bytes)),
82
];
83