diff --git a/.github/workflows/pr_test_build.yml b/.github/workflows/pr_test_build.yml
index ddc8869f0..28af7cefb 100644
--- a/.github/workflows/pr_test_build.yml
+++ b/.github/workflows/pr_test_build.yml
@@ -148,6 +148,7 @@ jobs:
echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> lib/.secrets.g.dart
echo "const polygonScanApiKey = '${{ secrets.POLYGON_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart
echo "const ankrApiKey = '${{ secrets.ANKR_API_KEY }}';" >> cw_solana/lib/.secrets.g.dart
+ echo "const meldApiKey = '${{ secrets.MELD_API_KEY }}';" >> lib/.secrets.g.dart
- name: Rename app
run: |
diff --git a/assets/images/meld_light.svg b/assets/images/meld_light.svg
new file mode 100644
index 000000000..8fa80c378
--- /dev/null
+++ b/assets/images/meld_light.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/lib/buy/meld/meld_provider.dart b/lib/buy/meld/meld_provider.dart
new file mode 100644
index 000000000..fd118b432
--- /dev/null
+++ b/lib/buy/meld/meld_provider.dart
@@ -0,0 +1,160 @@
+
+import 'package:cake_wallet/.secrets.g.dart' as secrets;
+import 'package:cake_wallet/buy/buy_provider.dart';
+import 'package:cake_wallet/palette.dart';
+import 'package:cake_wallet/routes.dart';
+import 'package:cake_wallet/store/settings_store.dart';
+import 'package:cake_wallet/themes/theme_base.dart';
+import 'package:cake_wallet/utils/device_info.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';
+import 'package:url_launcher/url_launcher.dart';
+
+class MeldProvider extends BuyProvider {
+ MeldProvider({
+ required SettingsStore settingsStore,
+ required WalletBase wallet,
+ bool isTestEnvironment = false,
+ }) : baseSellUrl = isTestEnvironment ? _baseSellTestUrl : _baseSellProductUrl,
+ baseBuyUrl = isTestEnvironment ? _baseBuyTestUrl : _baseBuyProductUrl,
+ this._settingsStore = settingsStore,
+ super(wallet: wallet, isTestEnvironment: isTestEnvironment);
+
+ final SettingsStore _settingsStore;
+
+ static const _baseSellTestUrl = 'api-sb.meld.io';
+ static const _baseSellProductUrl = 'api.meld.io';
+ static const _baseBuyTestUrl = 'api-sb.meld.io';
+ static const _baseBuyProductUrl = 'api.meld.io';
+ // static const _cIdBaseUrl = 'exchange-helper.cakewallet.com';
+
+ final String baseBuyUrl;
+ final String baseSellUrl;
+
+ @override
+ String get providerDescription => 'Meld provider description here';
+
+ @override
+ String get title => 'Meld';
+
+ @override
+ String get lightIcon => 'assets/images/meld_light.svg';
+
+ @override
+ String get darkIcon => 'assets/images/moonpay_dark.png';
+
+ static String themeToMoonPayTheme(ThemeBase theme) {
+ switch (theme.type) {
+ case ThemeType.bright:
+ case ThemeType.light:
+ return 'light';
+ case ThemeType.dark:
+ return 'dark';
+ }
+ }
+
+ static String get _apiKey => secrets.moonPayApiKey;
+
+ String get currencyCode => walletTypeToCryptoCurrency(wallet.type).title.toLowerCase();
+
+
+ static String get _exchangeHelperApiKey => secrets.exchangeHelperApiKey;
+
+ Future requestSellUrl({
+ required CryptoCurrency currency,
+ required String refundWalletAddress,
+ required SettingsStore settingsStore,
+ }) async {
+ throw UnimplementedError();
+ }
+
+ // BUY:
+ static const _currenciesSuffix = '/v3/currencies';
+ static const _quoteSuffix = '/buy_quote';
+ static const _transactionsSuffix = '/v1/transactions';
+ static const _ipAddressSuffix = '/v4/ip_address';
+
+ Future requestBuyUrl({
+ required CryptoCurrency currency,
+ required SettingsStore settingsStore,
+ required String walletAddress,
+ String? amount,
+ }) async {
+ final params = {
+ 'theme': themeToMoonPayTheme(settingsStore.currentTheme),
+ 'language': settingsStore.languageCode,
+ 'colorCode': settingsStore.currentTheme.type == ThemeType.dark
+ ? '#${Palette.blueCraiola.value.toRadixString(16).substring(2, 8)}'
+ : '#${Palette.moderateSlateBlue.value.toRadixString(16).substring(2, 8)}',
+ 'defaultCurrencyCode': _normalizeCurrency(currency),
+ 'baseCurrencyCode': _normalizeCurrency(currency),
+ 'baseCurrencyAmount': amount ?? '0',
+ 'currencyCode': currencyCode,
+ 'walletAddress': walletAddress,
+ 'lockAmount': 'true',
+ 'showAllCurrencies': 'false',
+ 'showWalletAddressForm': 'false',
+ 'enabledPaymentMethods':
+ 'credit_debit_card,apple_pay,google_pay,samsung_pay,sepa_bank_transfer,gbp_bank_transfer,gbp_open_banking_payment',
+ };
+
+ if (_apiKey.isNotEmpty) {
+ params['apiKey'] = _apiKey;
+ }
+
+ final originalUri = Uri.https(
+ baseBuyUrl,
+ '',
+ params,
+ );
+
+ if (isTestEnvironment) {
+ return originalUri;
+ }
+
+ return originalUri;
+ // final signature = await getMoonpaySignature('?${originalUri.query}');
+ // final query = Map.from(originalUri.queryParameters);
+ // query['signature'] = signature;
+ // final signedUri = originalUri.replace(queryParameters: query);
+ // return signedUri;
+ }
+
+ @override
+ Future launchProvider(BuildContext context, bool? isBuyAction) async {
+ late final Uri uri;
+ if (isBuyAction ?? true) {
+ uri = await requestBuyUrl(
+ currency: wallet.currency,
+ walletAddress: wallet.walletAddresses.address,
+ settingsStore: _settingsStore,
+ );
+ } else {
+ uri = await requestSellUrl(
+ currency: wallet.currency,
+ refundWalletAddress: wallet.walletAddresses.address,
+ settingsStore: _settingsStore,
+ );
+ }
+
+ if (await canLaunchUrl(uri)) {
+ if (DeviceInfo.instance.isMobile) {
+ Navigator.of(context).pushNamed(Routes.webViewPage, arguments: ['Meld', uri]);
+ } else {
+ await launchUrl(uri, mode: LaunchMode.externalApplication);
+ }
+ } else {
+ throw Exception('Could not launch URL');
+ }
+ }
+
+ String _normalizeCurrency(CryptoCurrency currency) {
+ if (currency == CryptoCurrency.maticpoly) {
+ return "MATIC_POLYGON";
+ }
+
+ return currency.toString().toLowerCase();
+ }
+}
diff --git a/lib/di.dart b/lib/di.dart
index 291555330..e67b1a34c 100644
--- a/lib/di.dart
+++ b/lib/di.dart
@@ -1,6 +1,7 @@
import 'package:cake_wallet/anonpay/anonpay_api.dart';
import 'package:cake_wallet/anonpay/anonpay_info_base.dart';
import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart';
+import 'package:cake_wallet/buy/meld/meld_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/bitcoin_cash/bitcoin_cash.dart';
@@ -813,6 +814,12 @@ Future setup({
isTestEnvironment: kDebugMode,
));
+ getIt.registerFactory(() => MeldProvider(
+ settingsStore: getIt.get().settingsStore,
+ wallet: getIt.get().wallet!,
+ isTestEnvironment: kDebugMode,
+ ));
+
getIt.registerFactory(() => OnRamperBuyProvider(
getIt.get().settingsStore,
wallet: getIt.get().wallet!,
diff --git a/lib/entities/provider_types.dart b/lib/entities/provider_types.dart
index 701781cc2..3950ba5d1 100644
--- a/lib/entities/provider_types.dart
+++ b/lib/entities/provider_types.dart
@@ -1,5 +1,6 @@
import 'package:cake_wallet/buy/buy_provider.dart';
import 'package:cake_wallet/buy/dfx/dfx_buy_provider.dart';
+import 'package:cake_wallet/buy/meld/meld_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/robinhood/robinhood_buy_provider.dart';
@@ -12,6 +13,7 @@ enum ProviderType {
dfx,
onramper,
moonpay,
+ meld,
}
extension ProviderTypeName on ProviderType {
@@ -27,6 +29,8 @@ extension ProviderTypeName on ProviderType {
return 'Onramper';
case ProviderType.moonpay:
return 'MoonPay';
+ case ProviderType.meld:
+ return 'Meld';
}
}
@@ -42,6 +46,8 @@ extension ProviderTypeName on ProviderType {
return 'onramper_provider';
case ProviderType.moonpay:
return 'moonpay_provider';
+ case ProviderType.meld:
+ return 'meld_provider';
}
}
}
@@ -63,10 +69,16 @@ class ProvidersHelper {
ProviderType.dfx,
ProviderType.robinhood,
ProviderType.moonpay,
+ ProviderType.meld,
];
case WalletType.litecoin:
case WalletType.bitcoinCash:
- return [ProviderType.askEachTime, ProviderType.onramper, ProviderType.robinhood, ProviderType.moonpay];
+ return [
+ ProviderType.askEachTime,
+ ProviderType.onramper,
+ ProviderType.robinhood,
+ ProviderType.moonpay
+ ];
case WalletType.solana:
return [ProviderType.askEachTime, ProviderType.onramper, ProviderType.robinhood];
case WalletType.none:
@@ -115,6 +127,8 @@ class ProvidersHelper {
return getIt.get();
case ProviderType.moonpay:
return getIt.get();
+ case ProviderType.meld:
+ return getIt.get();
case ProviderType.askEachTime:
return null;
}