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>
This commit is contained in:
Serhii 2025-03-06 19:39:41 +02:00 committed by GitHub
parent 09f20b2a7b
commit 1aad8366c4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 285 additions and 10 deletions

View file

@ -172,6 +172,7 @@ jobs:
# end of test secrets # end of test secrets
echo "const chainflipApiKey = '${{ secrets.CHAINFLIP_API_KEY }}';" >> lib/.secrets.g.dart echo "const chainflipApiKey = '${{ secrets.CHAINFLIP_API_KEY }}';" >> lib/.secrets.g.dart
echo "const chainflipAffiliateFee = '${{ secrets.CHAINFLIP_AFFILIATE_FEE }}';" >> lib/.secrets.g.dart echo "const chainflipAffiliateFee = '${{ secrets.CHAINFLIP_AFFILIATE_FEE }}';" >> lib/.secrets.g.dart
echo "const kryptonimApiKey = '${{ secrets.KRYPTONIM_API_KEY }}';" >> lib/.secrets.g.dart
echo "const walletGroupSalt = '${{ secrets.WALLET_GROUP_SALT }}';" >> lib/.secrets.g.dart echo "const walletGroupSalt = '${{ secrets.WALLET_GROUP_SALT }}';" >> lib/.secrets.g.dart
- name: prepare monero_c and cache - name: prepare monero_c and cache

View file

@ -168,6 +168,7 @@ jobs:
# end of test secrets # end of test secrets
echo "const chainflipApiKey = '${{ secrets.CHAINFLIP_API_KEY }}';" >> lib/.secrets.g.dart echo "const chainflipApiKey = '${{ secrets.CHAINFLIP_API_KEY }}';" >> lib/.secrets.g.dart
echo "const chainflipAffiliateFee = '${{ secrets.CHAINFLIP_AFFILIATE_FEE }}';" >> lib/.secrets.g.dart echo "const chainflipAffiliateFee = '${{ secrets.CHAINFLIP_AFFILIATE_FEE }}';" >> lib/.secrets.g.dart
echo "const kryptonimApiKey = '${{ secrets.KRYPTONIM_API_KEY }}';" >> lib/.secrets.g.dart
echo "const walletGroupSalt = '${{ secrets.WALLET_GROUP_SALT }}';" >> lib/.secrets.g.dart echo "const walletGroupSalt = '${{ secrets.WALLET_GROUP_SALT }}';" >> lib/.secrets.g.dart
- name: prepare monero_c and cache - name: prepare monero_c and cache

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View file

@ -49,7 +49,7 @@ abstract class BuyProvider {
throw UnimplementedError(); throw UnimplementedError();
Future<List<PaymentMethod>> getAvailablePaymentTypes( Future<List<PaymentMethod>> getAvailablePaymentTypes(
String fiatCurrency, String cryptoCurrency, bool isBuyAction) async => String fiatCurrency, CryptoCurrency cryptoCurrency, bool isBuyAction) async =>
[]; [];
Future<List<Quote>?> fetchQuote( Future<List<Quote>?> fetchQuote(

View file

@ -289,6 +289,29 @@ class Quote extends SelectableOption {
); );
} }
factory Quote.fromKryptonimJson(
Map<String, dynamic> json, bool isBuyAction, PaymentType paymentType) {
final fees = json['fees'] as Map<String, dynamic>;
final rate = _toDouble(json['rate']) ?? 0.0;
final limits = json['limits'] as Map<String, dynamic>;
final minLimit = _toDouble(limits['min_amount']) ?? 0.0;
final maxLimit = _toDouble(limits['max_amount']) ?? double.infinity;
final convertedAmount = _toDouble(json['converted_amount']) ?? 0.0;
final amount = _toDouble(json['amount']) ?? 0.0;
final calculatedRate = amount / convertedAmount;
return Quote(
rate: calculatedRate,
feeAmount: _toDouble(fees['totalFee']) ?? 0.0,
networkFee: _toDouble(fees['network_fee']) ?? 0.0,
transactionFee: _toDouble(fees['operation_fee']) ?? 0.0,
payout: _toDouble(json['amount']) ?? 0.0,
paymentType: paymentType,
recommendations: [],
provider: ProvidersHelper.getProviderByType(ProviderType.kriptonim)!,
isBuyAction: isBuyAction,
limits: Limits(min: minLimit, max: maxLimit));
}
static double? _toDouble(dynamic value) { static double? _toDouble(dynamic value) {
if (value is int) { if (value is int) {
return value.toDouble(); return value.toDouble();
@ -299,4 +322,7 @@ class Quote extends SelectableOption {
} }
return null; return null;
} }
@override
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}]';
} }

View file

@ -168,7 +168,7 @@ class DFXBuyProvider extends BuyProvider {
} }
Future<List<PaymentMethod>> getAvailablePaymentTypes( Future<List<PaymentMethod>> getAvailablePaymentTypes(
String fiatCurrency, String cryptoCurrency, bool isBuyAction) async { String fiatCurrency, CryptoCurrency cryptoCurrency, bool isBuyAction) async {
final List<PaymentMethod> paymentMethods = []; final List<PaymentMethod> paymentMethods = [];
if (isBuyAction) { if (isBuyAction) {
@ -190,7 +190,7 @@ class DFXBuyProvider extends BuyProvider {
}); });
} }
} else { } else {
final assetCredentials = await fetchAssetCredential(cryptoCurrency); final assetCredentials = await fetchAssetCredential(cryptoCurrency.title);
if (assetCredentials.isNotEmpty) { if (assetCredentials.isNotEmpty) {
if (assetCredentials['sellable'] == true) { if (assetCredentials['sellable'] == true) {
final availablePaymentTypes = [ final availablePaymentTypes = [

View file

@ -0,0 +1,222 @@
import 'dart:convert';
import 'package:cake_wallet/.secrets.g.dart' as secrets;
import 'package:cake_wallet/buy/buy_provider.dart';
import 'package:cake_wallet/buy/buy_quote.dart';
import 'package:cake_wallet/buy/payment_method.dart';
import 'package:cake_wallet/entities/fiat_currency.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:flutter/material.dart';
import 'dart:developer';
import 'package:http/http.dart' as http;
import 'package:url_launcher/url_launcher.dart';
class KryptonimBuyProvider extends BuyProvider {
KryptonimBuyProvider({required WalletBase wallet, bool isTestEnvironment = false})
: super(wallet: wallet, isTestEnvironment: isTestEnvironment, ledgerVM: null);
static const _isProduction = true;
static const _baseUrl = _isProduction ? 'app.kryptonim.com' : 'intg-api.kryptonim.com';
static const _baseWidgetUrl = _isProduction ? 'buy.kryptonim.com' : 'intg.kryptonim.com';
static const _quotePath = '/v2/ramp/buy/quotes';
static const _merchantId = 'a70fe053';
static String get _kryptonimApiKey => secrets.kryptonimApiKey;
@override
String get title => 'Kryptonim';
@override
String get providerDescription => 'Kryptonim Buy Provider';
@override
String get lightIcon => 'assets/images/kryptonim_light.png';
@override
String get darkIcon => 'assets/images/kryptonim_dark.png';
@override
bool get isAggregator => false;
Future<Map<String, dynamic>> getExchangeRates(
{required CryptoCurrency cryptoCurrency,
required String fiatCurrency,
required double amount}) async {
final url = Uri.https(_baseUrl, _quotePath, {'m': _merchantId});
final headers = {
'accept': 'application/json',
'Content-Type': 'application/json',
'Authorization': _kryptonimApiKey,
};
final body = jsonEncode({
'amount': amount,
'currency': fiatCurrency,
'converted_currency': cryptoCurrency.title,
'blockchain': _normalizeBlockChain(cryptoCurrency),
'quote_currency': fiatCurrency,
});
try {
final response = await http.post(url, headers: headers, body: body);
if (response.statusCode == 200 || response.statusCode == 201 || response.statusCode == 401) {
return jsonDecode(response.body) as Map<String, dynamic>;
} else {
return {};
}
} catch (e) {
return {};
}
}
@override
Future<List<PaymentMethod>> getAvailablePaymentTypes(
String fiatCurrency, CryptoCurrency cryptoCurrency, bool isBuyAction) async {
final data = await getExchangeRates(
cryptoCurrency: cryptoCurrency,
fiatCurrency: fiatCurrency,
amount: 100.0,
);
if (data.isEmpty || !data.containsKey('data')) return [];
final paymentMethods = (data['data'] as List<dynamic>)
.map((e) => PaymentMethod.fromKryptonimJson(e as Map<String, dynamic>))
.toList();
return paymentMethods;
}
@override
Future<List<Quote>?> fetchQuote({
required CryptoCurrency cryptoCurrency,
required FiatCurrency fiatCurrency,
required double amount,
required bool isBuyAction,
required String walletAddress,
PaymentType? paymentType,
String? countryCode,
}) async {
log('Kryptonim: Fetching quote: ${isBuyAction ? cryptoCurrency : fiatCurrency} -> ${isBuyAction ? fiatCurrency : cryptoCurrency}, amount: $amount');
final data = await getExchangeRates(
cryptoCurrency: cryptoCurrency,
fiatCurrency: fiatCurrency.toString(),
amount: amount,
);
if (!data.containsKey('data') || (data['data'] as List).isEmpty) {
return null;
}
final quotesList = data['data'] as List<dynamic>;
Map<String, dynamic>? selectedPaymentMethod;
if (paymentType == PaymentType.all || paymentType == null) {
selectedPaymentMethod = quotesList.first as Map<String, dynamic>;
} else {
for (var quote in quotesList) {
final quotePaymentType = PaymentMethod.getPaymentTypeId(quote['payment_method'] as String?);
if (quotePaymentType == paymentType) {
selectedPaymentMethod = quote as Map<String, dynamic>;
break;
}
}
}
if (selectedPaymentMethod == null) {
return null;
}
final selectedPaymentType =
PaymentMethod.getPaymentTypeId(selectedPaymentMethod['payment_method'] as String?);
final quote = Quote.fromKryptonimJson(selectedPaymentMethod, isBuyAction, selectedPaymentType);
quote.setFiatCurrency = fiatCurrency;
quote.setCryptoCurrency = cryptoCurrency;
return [quote];
}
@override
Future<void>? launchProvider(
{required BuildContext context,
required Quote quote,
required double amount,
required bool isBuyAction,
required String cryptoCurrencyAddress,
String? countryCode}) async {
final params = {
'amount': amount.toInt().toString(),
'currency': quote.fiatCurrency.name,
'convertedCurrency': quote.cryptoCurrency.title,
'blockchain': _normalizeBlockChain(quote.cryptoCurrency),
'address': cryptoCurrencyAddress,
'paymentMethod': normalizePaymentMethod(quote.paymentType),
};
final uri = Uri.https(_baseWidgetUrl, '/redirect-form', params);
try {
if (await canLaunchUrl(uri)) {
await launchUrl(uri, mode: LaunchMode.externalApplication);
} else {
throw Exception('Could not launch URL');
}
} catch (e) {
await showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: "Kryptonim",
alertContent: "Payment provider is unavailable: $e",
buttonText: "OK",
buttonAction: () => Navigator.of(context).pop(),
);
},
);
}
}
String normalizePaymentMethod(PaymentType paymentType) {
switch (paymentType) {
case PaymentType.bankTransfer:
return 'bank';
case PaymentType.creditCard:
case PaymentType.debitCard:
return 'card';
default:
return paymentType.name.toLowerCase();
}
}
String _normalizeBlockChain(CryptoCurrency cur) {
String? blockchain = switch (cur.tag) {
'ETH' => 'Ethereum',
'POL' => 'Polygon',
'AVAXC' => 'Avalanche',
'SOL' => 'Solana',
_ => null,
};
if (blockchain == null) {
blockchain = switch (cur) {
CryptoCurrency.btc => 'Bitcoin',
CryptoCurrency.ltc => 'Litecoin',
CryptoCurrency.eth => 'Ethereum',
CryptoCurrency.maticpoly => 'Matic',
_ => null,
};
}
return blockchain ?? cur.fullName ?? '';
}
}

View file

@ -53,7 +53,7 @@ class MeldBuyProvider extends BuyProvider {
@override @override
Future<List<PaymentMethod>> getAvailablePaymentTypes( Future<List<PaymentMethod>> getAvailablePaymentTypes(
String fiatCurrency, String cryptoCurrency, bool isBuyAction) async { String fiatCurrency, CryptoCurrency cryptoCurrency, bool isBuyAction) async {
final params = {'fiatCurrencies': fiatCurrency, 'statuses': 'LIVE,RECENTLY_ADDED,BUILDING'}; final params = {'fiatCurrencies': fiatCurrency, 'statuses': 'LIVE,RECENTLY_ADDED,BUILDING'};
final path = '$_providersProperties$_paymentMethodsPath'; final path = '$_providersProperties$_paymentMethodsPath';

View file

@ -126,11 +126,11 @@ class MoonPayProvider extends BuyProvider {
} }
Future<List<PaymentMethod>> getAvailablePaymentTypes( Future<List<PaymentMethod>> getAvailablePaymentTypes(
String fiatCurrency, String cryptoCurrency, bool isBuyAction) async { String fiatCurrency, CryptoCurrency cryptoCurrency, bool isBuyAction) async {
final List<PaymentMethod> paymentMethods = []; final List<PaymentMethod> paymentMethods = [];
if (isBuyAction) { if (isBuyAction) {
final fiatBuyCredentials = await fetchFiatCredentials(fiatCurrency, cryptoCurrency, null); final fiatBuyCredentials = await fetchFiatCredentials(fiatCurrency, cryptoCurrency.title, null);
if (fiatBuyCredentials.isNotEmpty) { if (fiatBuyCredentials.isNotEmpty) {
final paymentMethod = fiatBuyCredentials['paymentMethod'] as String?; final paymentMethod = fiatBuyCredentials['paymentMethod'] as String?;
paymentMethods.add(PaymentMethod.fromMoonPayJson( paymentMethods.add(PaymentMethod.fromMoonPayJson(

View file

@ -48,7 +48,7 @@ class OnRamperBuyProvider extends BuyProvider {
bool get isAggregator => true; bool get isAggregator => true;
Future<List<PaymentMethod>> getAvailablePaymentTypes( Future<List<PaymentMethod>> getAvailablePaymentTypes(
String fiatCurrency, String cryptoCurrency, bool isBuyAction) async { String fiatCurrency, CryptoCurrency cryptoCurrency, bool isBuyAction) async {
final params = { final params = {
'fiatCurrency': fiatCurrency, 'fiatCurrency': fiatCurrency,
'type': isBuyAction ? 'buy' : 'sell', 'type': isBuyAction ? 'buy' : 'sell',

View file

@ -218,6 +218,15 @@ class PaymentMethod extends SelectableOption {
customDescription: json['description'] as String?); customDescription: json['description'] as String?);
} }
factory PaymentMethod.fromKryptonimJson(Map<String, dynamic> json) {
final type = PaymentMethod.getPaymentTypeId(json['payment_method'] as String?);
return PaymentMethod(
paymentMethodType: type,
customTitle: json['payment_method'] as String? ?? 'Unknown',
customIconPath: 'assets/images/card.png',
);
}
static PaymentType getPaymentTypeId(String? type) { static PaymentType getPaymentTypeId(String? type) {
switch (type?.toLowerCase()) { switch (type?.toLowerCase()) {
case 'banktransfer': case 'banktransfer':

View file

@ -255,6 +255,7 @@ import 'package:get_it/get_it.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'buy/kryptonim/kryptonim.dart';
import 'buy/meld/meld_buy_provider.dart'; import 'buy/meld/meld_buy_provider.dart';
import 'src/screens/buy/buy_sell_page.dart'; import 'src/screens/buy/buy_sell_page.dart';
import 'cake_pay/cake_pay_payment_credantials.dart'; import 'cake_pay/cake_pay_payment_credantials.dart';
@ -1019,6 +1020,10 @@ Future<void> setup({
wallet: getIt.get<AppStore>().wallet!, wallet: getIt.get<AppStore>().wallet!,
)); ));
getIt.registerFactory<KryptonimBuyProvider>(() => KryptonimBuyProvider(
wallet: getIt.get<AppStore>().wallet!,
));
getIt.registerFactoryParam<WebViewPage, String, Uri>((title, uri) => WebViewPage(title, uri)); getIt.registerFactoryParam<WebViewPage, String, Uri>((title, uri) => WebViewPage(title, uri));
getIt.registerFactory(() => ExchangeViewModel( getIt.registerFactory(() => ExchangeViewModel(

View file

@ -1,5 +1,6 @@
import 'package:cake_wallet/buy/buy_provider.dart'; import 'package:cake_wallet/buy/buy_provider.dart';
import 'package:cake_wallet/buy/dfx/dfx_buy_provider.dart'; import 'package:cake_wallet/buy/dfx/dfx_buy_provider.dart';
import 'package:cake_wallet/buy/kryptonim/kryptonim.dart';
import 'package:cake_wallet/buy/meld/meld_buy_provider.dart'; import 'package:cake_wallet/buy/meld/meld_buy_provider.dart';
import 'package:cake_wallet/buy/moonpay/moonpay_provider.dart'; import 'package:cake_wallet/buy/moonpay/moonpay_provider.dart';
import 'package:cake_wallet/buy/onramper/onramper_buy_provider.dart'; import 'package:cake_wallet/buy/onramper/onramper_buy_provider.dart';
@ -8,7 +9,7 @@ import 'package:cake_wallet/di.dart';
import 'package:cw_core/wallet_type.dart'; import 'package:cw_core/wallet_type.dart';
import 'package:http/http.dart'; import 'package:http/http.dart';
enum ProviderType { robinhood, dfx, onramper, moonpay, meld } enum ProviderType { robinhood, dfx, onramper, moonpay, meld, kriptonim }
extension ProviderTypeName on ProviderType { extension ProviderTypeName on ProviderType {
String get title { String get title {
@ -23,6 +24,8 @@ extension ProviderTypeName on ProviderType {
return 'MoonPay'; return 'MoonPay';
case ProviderType.meld: case ProviderType.meld:
return 'Meld'; return 'Meld';
case ProviderType.kriptonim:
return 'Kriptonim';
} }
} }
@ -38,6 +41,8 @@ extension ProviderTypeName on ProviderType {
return 'moonpay_provider'; return 'moonpay_provider';
case ProviderType.meld: case ProviderType.meld:
return 'meld_provider'; return 'meld_provider';
case ProviderType.kriptonim:
return 'kriptonim_provider';
} }
} }
} }
@ -59,6 +64,7 @@ class ProvidersHelper {
ProviderType.dfx, ProviderType.dfx,
ProviderType.robinhood, ProviderType.robinhood,
ProviderType.moonpay, ProviderType.moonpay,
ProviderType.kriptonim
]; ];
case WalletType.litecoin: case WalletType.litecoin:
case WalletType.bitcoinCash: case WalletType.bitcoinCash:
@ -66,13 +72,15 @@ class ProvidersHelper {
return [ return [
ProviderType.onramper, ProviderType.onramper,
ProviderType.robinhood, ProviderType.robinhood,
ProviderType.moonpay ProviderType.moonpay,
ProviderType.kriptonim
]; ];
case WalletType.tron: case WalletType.tron:
return [ return [
ProviderType.onramper, ProviderType.onramper,
ProviderType.robinhood, ProviderType.robinhood,
ProviderType.moonpay, ProviderType.moonpay,
ProviderType.kriptonim
]; ];
case WalletType.none: case WalletType.none:
case WalletType.haven: case WalletType.haven:
@ -127,6 +135,8 @@ class ProvidersHelper {
return getIt.get<MoonPayProvider>(); return getIt.get<MoonPayProvider>();
case ProviderType.meld: case ProviderType.meld:
return getIt.get<MeldBuyProvider>(); return getIt.get<MeldBuyProvider>();
case ProviderType.kriptonim:
return getIt.get<KryptonimBuyProvider>();
default: default:
return null; return null;
} }

View file

@ -351,7 +351,7 @@ abstract class BuySellViewModelBase extends WalletChangeListenerViewModel with S
paymentMethodState = PaymentMethodLoading(); paymentMethodState = PaymentMethodLoading();
selectedPaymentMethod = null; selectedPaymentMethod = null;
final result = await Future.wait(providerList.map((element) => element final result = await Future.wait(providerList.map((element) => element
.getAvailablePaymentTypes(fiatCurrency.title, cryptoCurrency.title, isBuyAction) .getAvailablePaymentTypes(fiatCurrency.title, cryptoCurrency, isBuyAction)
.timeout( .timeout(
Duration(seconds: 10), Duration(seconds: 10),
onTimeout: () => [], onTimeout: () => [],

View file

@ -77,6 +77,7 @@ class SecretKey {
SecretKey('moneroTestWalletBlockHeight', () => ''), SecretKey('moneroTestWalletBlockHeight', () => ''),
SecretKey('chainflipApiKey', () => ''), SecretKey('chainflipApiKey', () => ''),
SecretKey('chainflipAffiliateFee', () => ''), SecretKey('chainflipAffiliateFee', () => ''),
SecretKey('kryptonimApiKey', () => ''),
SecretKey('walletGroupSalt', () => hex.encode(encrypt.Key.fromSecureRandom(16).bytes)), SecretKey('walletGroupSalt', () => hex.encode(encrypt.Key.fromSecureRandom(16).bytes)),
]; ];