diff --git a/.github/workflows/pr_test_build_android.yml b/.github/workflows/pr_test_build_android.yml
index e096545c2..3927f04ce 100644
--- a/.github/workflows/pr_test_build_android.yml
+++ b/.github/workflows/pr_test_build_android.yml
@@ -173,6 +173,8 @@ jobs:
echo "const nanoNowNodesApiKey = '${{ secrets.NANO_NOW_NODES_API_KEY }}';" >> cw_nano/lib/.secrets.g.dart
echo "const tronGridApiKey = '${{ secrets.TRON_GRID_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart
echo "const tronNowNodesApiKey = '${{ secrets.TRON_NOW_NODES_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart
+ echo "const meldTestApiKey = '${{ secrets.MELD_TEST_API_KEY }}';" >> lib/.secrets.g.dart
+ echo "const meldTestPublicKey = '${{ secrets.MELD_TEST_PUBLIC_KEY}}';" >> lib/.secrets.g.dar
echo "const letsExchangeBearerToken = '${{ secrets.LETS_EXCHANGE_TOKEN }}';" >> lib/.secrets.g.dart
echo "const letsExchangeAffiliateId = '${{ secrets.LETS_EXCHANGE_AFFILIATE_ID }}';" >> lib/.secrets.g.dart
echo "const stealthExBearerToken = '${{ secrets.STEALTH_EX_BEARER_TOKEN }}';" >> lib/.secrets.g.dart
diff --git a/.github/workflows/pr_test_build_linux.yml b/.github/workflows/pr_test_build_linux.yml
index 7713cc95d..fc2c0ed8f 100644
--- a/.github/workflows/pr_test_build_linux.yml
+++ b/.github/workflows/pr_test_build_linux.yml
@@ -156,6 +156,8 @@ jobs:
echo "const nanoNowNodesApiKey = '${{ secrets.NANO_NOW_NODES_API_KEY }}';" >> cw_nano/lib/.secrets.g.dart
echo "const tronGridApiKey = '${{ secrets.TRON_GRID_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart
echo "const tronNowNodesApiKey = '${{ secrets.TRON_NOW_NODES_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart
+ echo "const meldTestApiKey = '${{ secrets.MELD_TEST_API_KEY }}';" >> lib/.secrets.g.dart
+ echo "const meldTestPublicKey = '${{ secrets.MELD_TEST_PUBLIC_KEY}}';" >> lib/.secrets.g.dar
echo "const letsExchangeBearerToken = '${{ secrets.LETS_EXCHANGE_TOKEN }}';" >> lib/.secrets.g.dart
echo "const letsExchangeAffiliateId = '${{ secrets.LETS_EXCHANGE_AFFILIATE_ID }}';" >> lib/.secrets.g.dart
echo "const stealthExBearerToken = '${{ secrets.STEALTH_EX_BEARER_TOKEN }}';" >> lib/.secrets.g.dart
diff --git a/assets/images/apple_pay_logo.png b/assets/images/apple_pay_logo.png
new file mode 100644
index 000000000..346007e3b
Binary files /dev/null and b/assets/images/apple_pay_logo.png differ
diff --git a/assets/images/apple_pay_round_dark.svg b/assets/images/apple_pay_round_dark.svg
new file mode 100644
index 000000000..82443bfb4
--- /dev/null
+++ b/assets/images/apple_pay_round_dark.svg
@@ -0,0 +1,9 @@
+
\ No newline at end of file
diff --git a/assets/images/apple_pay_round_light.svg b/assets/images/apple_pay_round_light.svg
new file mode 100644
index 000000000..2beb1248f
--- /dev/null
+++ b/assets/images/apple_pay_round_light.svg
@@ -0,0 +1,9 @@
+
\ No newline at end of file
diff --git a/assets/images/bank.png b/assets/images/bank.png
new file mode 100644
index 000000000..9dc68147a
Binary files /dev/null and b/assets/images/bank.png differ
diff --git a/assets/images/bank_dark.svg b/assets/images/bank_dark.svg
new file mode 100644
index 000000000..670120796
--- /dev/null
+++ b/assets/images/bank_dark.svg
@@ -0,0 +1,8 @@
+
\ No newline at end of file
diff --git a/assets/images/bank_light.svg b/assets/images/bank_light.svg
new file mode 100644
index 000000000..804716289
--- /dev/null
+++ b/assets/images/bank_light.svg
@@ -0,0 +1,8 @@
+
\ No newline at end of file
diff --git a/assets/images/buy_sell.png b/assets/images/buy_sell.png
new file mode 100644
index 000000000..0fbffe56f
Binary files /dev/null and b/assets/images/buy_sell.png differ
diff --git a/assets/images/card.svg b/assets/images/card.svg
new file mode 100644
index 000000000..95530cdc9
--- /dev/null
+++ b/assets/images/card.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/images/card_dark.svg b/assets/images/card_dark.svg
new file mode 100644
index 000000000..2e5bcf986
--- /dev/null
+++ b/assets/images/card_dark.svg
@@ -0,0 +1,7 @@
+
+
+
\ No newline at end of file
diff --git a/assets/images/dollar_coin.svg b/assets/images/dollar_coin.svg
new file mode 100644
index 000000000..22218f332
--- /dev/null
+++ b/assets/images/dollar_coin.svg
@@ -0,0 +1,12 @@
+
+
+
\ No newline at end of file
diff --git a/assets/images/google_pay_icon.png b/assets/images/google_pay_icon.png
new file mode 100644
index 000000000..a3ca38311
Binary files /dev/null and b/assets/images/google_pay_icon.png differ
diff --git a/assets/images/meld_logo.svg b/assets/images/meld_logo.svg
new file mode 100644
index 000000000..1d9211d64
--- /dev/null
+++ b/assets/images/meld_logo.svg
@@ -0,0 +1,30 @@
+
diff --git a/assets/images/revolut.png b/assets/images/revolut.png
new file mode 100644
index 000000000..bbe342592
Binary files /dev/null and b/assets/images/revolut.png differ
diff --git a/assets/images/skrill.svg b/assets/images/skrill.svg
new file mode 100644
index 000000000..b264b57eb
--- /dev/null
+++ b/assets/images/skrill.svg
@@ -0,0 +1,15 @@
+
\ No newline at end of file
diff --git a/assets/images/usd_round_dark.svg b/assets/images/usd_round_dark.svg
new file mode 100644
index 000000000..f329dd617
--- /dev/null
+++ b/assets/images/usd_round_dark.svg
@@ -0,0 +1,4 @@
+
+
\ No newline at end of file
diff --git a/assets/images/usd_round_light.svg b/assets/images/usd_round_light.svg
new file mode 100644
index 000000000..f5965c597
--- /dev/null
+++ b/assets/images/usd_round_light.svg
@@ -0,0 +1,2 @@
+
+
diff --git a/assets/images/wallet_new.png b/assets/images/wallet_new.png
new file mode 100644
index 000000000..47c43bfca
Binary files /dev/null and b/assets/images/wallet_new.png differ
diff --git a/cw_core/lib/currency_for_wallet_type.dart b/cw_core/lib/currency_for_wallet_type.dart
index 630078949..af6037a3b 100644
--- a/cw_core/lib/currency_for_wallet_type.dart
+++ b/cw_core/lib/currency_for_wallet_type.dart
@@ -35,3 +35,34 @@ CryptoCurrency currencyForWalletType(WalletType type, {bool? isTestnet}) {
'Unexpected wallet type: ${type.toString()} for CryptoCurrency currencyForWalletType');
}
}
+
+WalletType? walletTypeForCurrency(CryptoCurrency currency) {
+ switch (currency) {
+ case CryptoCurrency.btc:
+ return WalletType.bitcoin;
+ case CryptoCurrency.xmr:
+ return WalletType.monero;
+ case CryptoCurrency.ltc:
+ return WalletType.litecoin;
+ case CryptoCurrency.xhv:
+ return WalletType.haven;
+ case CryptoCurrency.eth:
+ return WalletType.ethereum;
+ case CryptoCurrency.bch:
+ return WalletType.bitcoinCash;
+ case CryptoCurrency.nano:
+ return WalletType.nano;
+ case CryptoCurrency.banano:
+ return WalletType.banano;
+ case CryptoCurrency.maticpoly:
+ return WalletType.polygon;
+ case CryptoCurrency.sol:
+ return WalletType.solana;
+ case CryptoCurrency.trx:
+ return WalletType.tron;
+ case CryptoCurrency.wow:
+ return WalletType.wownero;
+ default:
+ return null;
+ }
+}
diff --git a/lib/buy/buy_provider.dart b/lib/buy/buy_provider.dart
index 1a37e09b3..8e79e16b8 100644
--- a/lib/buy/buy_provider.dart
+++ b/lib/buy/buy_provider.dart
@@ -1,6 +1,10 @@
import 'package:cake_wallet/buy/buy_amount.dart';
+import 'package:cake_wallet/buy/buy_quote.dart';
import 'package:cake_wallet/buy/order.dart';
+import 'package:cake_wallet/buy/payment_method.dart';
+import 'package:cake_wallet/entities/fiat_currency.dart';
import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
+import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:flutter/material.dart';
@@ -23,14 +27,38 @@ abstract class BuyProvider {
String get darkIcon;
+ bool get isAggregator;
+
@override
String toString() => title;
- Future launchProvider(BuildContext context, bool? isBuyAction);
+ Future? launchProvider(
+ {required BuildContext context,
+ required Quote quote,
+ required double amount,
+ required bool isBuyAction,
+ required String cryptoCurrencyAddress,
+ String? countryCode}) =>
+ null;
Future requestUrl(String amount, String sourceCurrency) => throw UnimplementedError();
Future findOrderById(String id) => throw UnimplementedError();
- Future calculateAmount(String amount, String sourceCurrency) => throw UnimplementedError();
+ Future calculateAmount(String amount, String sourceCurrency) =>
+ throw UnimplementedError();
+
+ Future> getAvailablePaymentTypes(
+ String fiatCurrency, String cryptoCurrency, bool isBuyAction) async =>
+ [];
+
+ Future?> fetchQuote(
+ {required CryptoCurrency cryptoCurrency,
+ required FiatCurrency fiatCurrency,
+ required double amount,
+ required bool isBuyAction,
+ required String walletAddress,
+ PaymentType? paymentType,
+ String? countryCode}) async =>
+ null;
}
diff --git a/lib/buy/buy_quote.dart b/lib/buy/buy_quote.dart
new file mode 100644
index 000000000..99e22775f
--- /dev/null
+++ b/lib/buy/buy_quote.dart
@@ -0,0 +1,236 @@
+import 'package:cake_wallet/buy/buy_provider.dart';
+import 'package:cake_wallet/buy/payment_method.dart';
+import 'package:cake_wallet/core/selectable_option.dart';
+import 'package:cake_wallet/entities/provider_types.dart';
+import 'package:cw_core/currency.dart';
+
+enum ProviderRecommendation { bestRate, lowKyc, successRate }
+
+extension RecommendationTitle on ProviderRecommendation {
+ String get title {
+ switch (this) {
+ case ProviderRecommendation.bestRate:
+ return 'BEST RATE';
+ case ProviderRecommendation.lowKyc:
+ return 'LOW KYC';
+ case ProviderRecommendation.successRate:
+ return 'SUCCESS RATE';
+ }
+ }
+}
+
+ProviderRecommendation? getRecommendationFromString(String title) {
+ switch (title) {
+ case 'BEST RATE':
+ return ProviderRecommendation.bestRate;
+ case 'LowKyc':
+ return ProviderRecommendation.lowKyc;
+ case 'SuccessRate':
+ return ProviderRecommendation.successRate;
+ default:
+ return null;
+ }
+}
+
+class Quote extends SelectableOption {
+ Quote({
+ required this.rate,
+ required this.feeAmount,
+ required this.networkFee,
+ required this.transactionFee,
+ required this.payout,
+ required this.provider,
+ required this.paymentType,
+ required this.recommendations,
+ this.isBuyAction = true,
+ this.quoteId,
+ this.rampId,
+ this.rampName,
+ this.rampIconPath,
+ }) : super(title: provider.isAggregator ? rampName ?? '' : provider.title);
+
+ final double rate;
+ final double feeAmount;
+ final double networkFee;
+ final double transactionFee;
+ final double payout;
+ final PaymentType paymentType;
+ final BuyProvider provider;
+ final String? quoteId;
+ final List recommendations;
+ String? rampId;
+ String? rampName;
+ String? rampIconPath;
+ bool isSelected = false;
+ bool isBestRate = false;
+ bool isBuyAction;
+
+ late Currency sourceCurrency;
+ late Currency destinationCurrency;
+
+ @override
+ bool get isOptionSelected => this.isSelected;
+
+ @override
+ String get lightIconPath =>
+ provider.isAggregator ? rampIconPath ?? provider.lightIcon : provider.lightIcon;
+
+ @override
+ String get darkIconPath =>
+ provider.isAggregator ? rampIconPath ?? provider.darkIcon : provider.darkIcon;
+
+ @override
+ List get badges => recommendations.map((e) => e.title).toList();
+
+ @override
+ String get leftSubTitle => this.rate > 0
+ ? '1 ${isBuyAction ? destinationCurrency : sourceCurrency} = ${rate.toStringAsFixed(2)} ${isBuyAction ? sourceCurrency : destinationCurrency}'
+ : '';
+
+ @override
+ String? get rightSubTitle => '';
+
+ @override
+ String get rightSubTitleLightIconPath => provider.isAggregator ? provider.lightIcon : '';
+
+ @override
+ String get rightSubTitleDarkIconPath => provider.isAggregator ? provider.darkIcon : '';
+
+ String get quoteTitle => '${provider.title} - ${paymentType.name}';
+
+ String get formatedFee => '$feeAmount ${isBuyAction ? sourceCurrency : destinationCurrency}';
+
+ void set setIsSelected(bool isSelected) => this.isSelected = isSelected;
+
+ void set setIsBestRate(bool isBestRate) => this.isBestRate = isBestRate;
+
+ void set setSourceCurrency(Currency sourceCurrency) => this.sourceCurrency = sourceCurrency;
+
+ void set setDestinationCurrency(Currency destinationCurrency) =>
+ this.destinationCurrency = destinationCurrency;
+
+ factory Quote.fromOnramperJson(Map json,
+ bool isBuyAction, Map metaData, PaymentType paymentType) {
+ final rate = _toDouble(json['rate']) ?? 0.0;
+ final networkFee = _toDouble(json['networkFee']) ?? 0.0;
+ final transactionFee = _toDouble(json['transactionFee']) ?? 0.0;
+ final feeAmount = double.parse((networkFee + transactionFee).toStringAsFixed(2));
+
+ final rampId = json['ramp'] as String? ?? '';
+ final rampName = metaData[rampId]['displayName'] as String? ?? '';
+ final rampIconPath = metaData[rampId]['svg'] as String? ?? '';
+
+ final recommendations = json['recommendations'] != null
+ ? List.from(json['recommendations'] as List)
+ : [];
+
+ final enumRecommendations = recommendations
+ .map((e) => getRecommendationFromString(e))
+ .whereType()
+ .toList();
+
+ return Quote(
+ rate: rate,
+ feeAmount: feeAmount,
+ networkFee: networkFee,
+ transactionFee: transactionFee,
+ payout: json['payout'] as double? ?? 0.0,
+ rampId: rampId,
+ rampName: rampName,
+ rampIconPath: rampIconPath,
+ paymentType: paymentType,
+ quoteId: json['quoteId'] as String? ?? '',
+ recommendations: enumRecommendations,
+ provider: ProvidersHelper.getProviderByType(ProviderType.onramper)!,
+ isBuyAction: isBuyAction,
+ );
+ }
+
+ factory Quote.fromMoonPayJson(
+ Map json, bool isBuyAction, PaymentType paymentType) {
+ final rate = isBuyAction
+ ? json['quoteCurrencyPrice'] as double? ?? 0.0
+ : json['baseCurrencyPrice'] as double? ?? 0.0;
+ final fee = _toDouble(json['feeAmount']) ?? 0.0;
+ final networkFee = _toDouble(json['networkFeeAmount']) ?? 0.0;
+ final transactionFee = _toDouble(json['extraFeeAmount']) ?? 0.0;
+ final feeAmount = double.parse((fee + networkFee + transactionFee).toStringAsFixed(2));
+ return Quote(
+ rate: rate,
+ feeAmount: feeAmount,
+ networkFee: networkFee,
+ transactionFee: transactionFee,
+ payout: _toDouble(json['quoteCurrencyAmount']) ?? 0.0,
+ paymentType: paymentType,
+ recommendations: [],
+ quoteId: json['signature'] as String? ?? '',
+ provider: ProvidersHelper.getProviderByType(ProviderType.moonpay)!,
+ isBuyAction: isBuyAction,
+ );
+ }
+
+ factory Quote.fromDFXJson(
+ Map json, bool isBuyAction, PaymentType paymentType) {
+ final rate = _toDouble(json['exchangeRate']) ?? 0.0;
+ final fees = json['fees'] as Map;
+ return Quote(
+ rate: isBuyAction ? rate : 1 / rate,
+ feeAmount: _toDouble(json['feeAmount']) ?? 0.0,
+ networkFee: _toDouble(fees['networkFee']) ?? 0.0,
+ transactionFee: _toDouble(fees['rate']) ?? 0.0,
+ payout: _toDouble(json['payout']) ?? 0.0,
+ paymentType: paymentType,
+ recommendations: [ProviderRecommendation.lowKyc],
+ provider: ProvidersHelper.getProviderByType(ProviderType.dfx)!,
+ isBuyAction: isBuyAction,
+ );
+ }
+
+ factory Quote.fromRobinhoodJson(
+ Map json, bool isBuyAction, PaymentType paymentType) {
+ final networkFee = json['networkFee'] as Map;
+ final processingFee = json['processingFee'] as Map;
+ final networkFeeAmount = _toDouble(networkFee['fiatAmount']) ?? 0.0;
+ final transactionFeeAmount = _toDouble(processingFee['fiatAmount']) ?? 0.0;
+ final feeAmount = double.parse((networkFeeAmount + transactionFeeAmount).toStringAsFixed(2));
+
+ return Quote(
+ rate: _toDouble(json['price']) ?? 0.0,
+ feeAmount: feeAmount,
+ networkFee: _toDouble(networkFee['fiatAmount']) ?? 0.0,
+ transactionFee: _toDouble(processingFee['fiatAmount']) ?? 0.0,
+ payout: _toDouble(json['cryptoAmount']) ?? 0.0,
+ paymentType: paymentType,
+ recommendations: [],
+ provider: ProvidersHelper.getProviderByType(ProviderType.robinhood)!,
+ isBuyAction: isBuyAction,
+ );
+ }
+
+ factory Quote.fromMeldJson(
+ Map json, bool isBuyAction, PaymentType paymentType) {
+ final quotes = json['quotes'][0] as Map;
+ return Quote(
+ rate: quotes['exchangeRate'] as double? ?? 0.0,
+ feeAmount: quotes['totalFee'] as double? ?? 0.0,
+ networkFee: quotes['networkFee'] as double? ?? 0.0,
+ transactionFee: quotes['transactionFee'] as double? ?? 0.0,
+ payout: quotes['payout'] as double? ?? 0.0,
+ paymentType: paymentType,
+ recommendations: [],
+ provider: ProvidersHelper.getProviderByType(ProviderType.meld)!,
+ isBuyAction: isBuyAction,
+ );
+ }
+
+ static double? _toDouble(dynamic value) {
+ if (value is int) {
+ return value.toDouble();
+ } else if (value is double) {
+ return value;
+ } else if (value is String) {
+ return double.tryParse(value);
+ }
+ return null;
+ }
+}
diff --git a/lib/buy/dfx/dfx_buy_provider.dart b/lib/buy/dfx/dfx_buy_provider.dart
index b3ed72498..8bb0648e2 100644
--- a/lib/buy/dfx/dfx_buy_provider.dart
+++ b/lib/buy/dfx/dfx_buy_provider.dart
@@ -1,13 +1,17 @@
import 'dart:convert';
+import 'dart:developer';
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/generated/i18n.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/connect_device/connect_device_page.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
-import 'package:cake_wallet/utils/device_info.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
+import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
@@ -15,10 +19,12 @@ import 'package:http/http.dart' as http;
import 'package:url_launcher/url_launcher.dart';
class DFXBuyProvider extends BuyProvider {
- DFXBuyProvider({required WalletBase wallet, bool isTestEnvironment = false, LedgerViewModel? ledgerVM})
+ DFXBuyProvider(
+ {required WalletBase wallet, bool isTestEnvironment = false, LedgerViewModel? ledgerVM})
: super(wallet: wallet, isTestEnvironment: isTestEnvironment, ledgerVM: ledgerVM);
static const _baseUrl = 'api.dfx.swiss';
+
// static const _signMessagePath = '/v1/auth/signMessage';
static const _authPath = '/v1/auth';
static const walletName = 'CakeWallet';
@@ -35,24 +41,8 @@ class DFXBuyProvider extends BuyProvider {
@override
String get darkIcon => 'assets/images/dfx_dark.png';
- String get assetOut {
- switch (wallet.type) {
- case WalletType.bitcoin:
- return 'BTC';
- case WalletType.bitcoinCash:
- return 'BCH';
- case WalletType.litecoin:
- return 'LTC';
- case WalletType.monero:
- return 'XMR';
- case WalletType.ethereum:
- return 'ETH';
- case WalletType.polygon:
- return 'MATIC';
- default:
- throw Exception("WalletType is not available for DFX ${wallet.type}");
- }
- }
+ @override
+ bool get isAggregator => false;
String get blockchain {
switch (wallet.type) {
@@ -60,14 +50,8 @@ class DFXBuyProvider extends BuyProvider {
case WalletType.bitcoinCash:
case WalletType.litecoin:
return 'Bitcoin';
- case WalletType.monero:
- return 'Monero';
- case WalletType.ethereum:
- return 'Ethereum';
- case WalletType.polygon:
- return 'Polygon';
default:
- throw Exception("WalletType is not available for DFX ${wallet.type}");
+ return walletTypeToString(wallet.type);
}
}
@@ -93,7 +77,8 @@ class DFXBuyProvider extends BuyProvider {
// }
Future auth() async {
- final signMessage = await getSignature(await getSignMessage());
+ final signMessage = await getSignature(
+ await getSignMessage()); //TODO: Sign message does not work for LTC and BCH
final requestBody = jsonEncode({
'wallet': walletName,
@@ -135,8 +120,178 @@ class DFXBuyProvider extends BuyProvider {
}
}
+ Future