diff --git a/lib/buy/moonpay/moonpay_provider.dart b/lib/buy/moonpay/moonpay_provider.dart index 02bdedaec..52a4f6187 100644 --- a/lib/buy/moonpay/moonpay_provider.dart +++ b/lib/buy/moonpay/moonpay_provider.dart @@ -22,20 +22,24 @@ import 'package:flutter/material.dart'; import 'package:http/http.dart'; import 'package:url_launcher/url_launcher.dart'; -class MoonPaySellProvider extends BuyProvider { - MoonPaySellProvider({ +class MoonPayProvider extends BuyProvider { + MoonPayProvider({ required SettingsStore settingsStore, required WalletBase wallet, bool isTestEnvironment = false, - }) : baseUrl = isTestEnvironment ? _baseTestUrl : _baseProductUrl, + }) : baseSellUrl = isTestEnvironment ? _baseSellTestUrl : _baseSellProductUrl, + baseBuyUrl = isTestEnvironment ? _baseBuyTestUrl : _baseBuyProductUrl, this._settingsStore = settingsStore, super(wallet: wallet, isTestEnvironment: isTestEnvironment); final SettingsStore _settingsStore; - static const _baseTestUrl = 'sell-sandbox.moonpay.com'; - static const _baseProductUrl = 'sell.moonpay.com'; + static const _baseSellTestUrl = 'sell-sandbox.moonpay.com'; + static const _baseSellProductUrl = 'sell.moonpay.com'; + static const _baseBuyTestUrl = 'buy-staging.moonpay.com'; + static const _baseBuyProductUrl = 'buy.moonpay.com'; static const _cIdBaseUrl = 'exchange-helper.cakewallet.com'; + static const _apiUrl = 'https://api.moonpay.com'; @override String get providerDescription => @@ -62,8 +66,14 @@ class MoonPaySellProvider extends BuyProvider { static String get _apiKey => secrets.moonPayApiKey; + final String baseBuyUrl; + final String baseSellUrl; + + String get currencyCode => walletTypeToCryptoCurrency(wallet.type).title.toLowerCase(); + + String get trackUrl => baseBuyUrl + '/transaction_receipt?transactionId='; + static String get _exchangeHelperApiKey => secrets.exchangeHelperApiKey; - final String baseUrl; Future getMoonpaySignature(String query) async { final uri = Uri.https(_cIdBaseUrl, "/api/moonpay"); @@ -85,147 +95,92 @@ class MoonPaySellProvider extends BuyProvider { } } - Future requestMoonPayUrl({ + Future requestSellMoonPayUrl({ required CryptoCurrency currency, required String refundWalletAddress, required SettingsStore settingsStore, }) async { - final customParams = { + 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), + 'refundWalletAddress': refundWalletAddress, }; - final originalUri = Uri.https( - baseUrl, - '', - { - 'apiKey': _apiKey, - 'defaultBaseCurrencyCode': _normalizeCurrency(currency), - 'refundWalletAddress': refundWalletAddress, - }..addAll(customParams), - ); + if (_apiKey.isNotEmpty) { + params['apiKey'] = _apiKey; + } - final signature = await getMoonpaySignature('?${originalUri.query}'); + final originalUri = Uri.https( + baseSellUrl, + '', + params, + ); if (isTestEnvironment) { 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 { - try { - final uri = await requestMoonPayUrl( - currency: wallet.currency, - refundWalletAddress: wallet.walletAddresses.address, - settingsStore: _settingsStore, - ); - - if (await canLaunchUrl(uri)) { - if (DeviceInfo.instance.isMobile) { - Navigator.of(context).pushNamed(Routes.webViewPage, arguments: ['MoonPay', uri]); - } else { - await launchUrl(uri, mode: LaunchMode.externalApplication); - } - } else { - throw Exception('Could not launch URL'); - } - } catch (e) { - await showDialog( - context: context, - builder: (BuildContext context) { - return AlertWithOneAction( - alertTitle: 'MoonPay', - alertContent: 'The MoonPay service is currently unavailable: $e', - buttonText: S.of(context).ok, - buttonAction: () => Navigator.of(context).pop(), - ); - }, - ); - } - } - - String _normalizeCurrency(CryptoCurrency currency) { - if (currency == CryptoCurrency.maticpoly) { - return "MATIC_POLYGON"; - } - - return currency.toString().toLowerCase(); - } -} - -class MoonPayBuyProvider extends BuyProvider { - MoonPayBuyProvider({required WalletBase wallet, bool isTestEnvironment = false}) - : baseUrl = isTestEnvironment ? _baseTestUrl : _baseProductUrl, - super(wallet: wallet, isTestEnvironment: isTestEnvironment); - - static const _baseTestUrl = 'https://buy-staging.moonpay.com'; - static const _baseProductUrl = 'https://buy.moonpay.com'; - static const _apiUrl = 'https://api.moonpay.com'; + // BUY: static const _currenciesSuffix = '/v3/currencies'; static const _quoteSuffix = '/buy_quote'; static const _transactionsSuffix = '/v1/transactions'; static const _ipAddressSuffix = '/v4/ip_address'; - static const _apiKey = secrets.moonPayApiKey; - static const _secretKey = secrets.moonPaySecretKey; - @override - String get title => 'MoonPay'; + Future requestBuyMoonPayUrl({ + 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', + }; - @override - String get providerDescription => - 'MoonPay offers a fast and simple way to buy and sell cryptocurrencies'; + if (_apiKey.isNotEmpty) { + params['apiKey'] = _apiKey; + } - @override - String get lightIcon => 'assets/images/moonpay_light.png'; + final originalUri = Uri.https( + baseBuyUrl, + '', + params, + ); - @override - String get darkIcon => 'assets/images/moonpay_dark.png'; + if (isTestEnvironment) { + return originalUri; + } - String get currencyCode => walletTypeToCryptoCurrency(wallet.type).title.toLowerCase(); - - String get trackUrl => baseUrl + '/transaction_receipt?transactionId='; - - String baseUrl; - - Future requestUrl(String amount, String sourceCurrency) async { - final enabledPaymentMethods = 'credit_debit_card%2Capple_pay%2Cgoogle_pay%2Csamsung_pay' - '%2Csepa_bank_transfer%2Cgbp_bank_transfer%2Cgbp_open_banking_payment'; - - final suffix = '?apiKey=' + - _apiKey + - '¤cyCode=' + - currencyCode + - '&enabledPaymentMethods=' + - enabledPaymentMethods + - '&walletAddress=' + - wallet.walletAddresses.address + - '&baseCurrencyCode=' + - sourceCurrency.toLowerCase() + - '&baseCurrencyAmount=' + - amount + - '&lockAmount=true' + - '&showAllCurrencies=false' + - '&showWalletAddressForm=false'; - - final originalUrl = baseUrl + suffix; - - final messageBytes = utf8.encode(suffix); - final key = utf8.encode(_secretKey); - final hmac = Hmac(sha256, key); - final digest = hmac.convert(messageBytes); - final signature = base64.encode(digest.bytes); - final urlWithSignature = originalUrl + '&signature=${Uri.encodeComponent(signature)}'; - - return isTestEnvironment ? originalUrl : urlWithSignature; + final signature = await getMoonpaySignature('?${originalUri.query}'); + final query = Map.from(originalUri.queryParameters); + query['signature'] = signature; + final signedUri = originalUri.replace(queryParameters: query); + return signedUri; } Future calculateAmount(String amount, String sourceCurrency) async { @@ -300,6 +255,52 @@ class MoonPayBuyProvider extends BuyProvider { } @override - Future launchProvider(BuildContext context, bool? isBuyAction) => - throw UnimplementedError(); + Future launchProvider(BuildContext context, bool? isBuyAction) async { + // try { + late final Uri uri; + if (isBuyAction ?? true) { + uri = await requestBuyMoonPayUrl( + currency: wallet.currency, + walletAddress: wallet.walletAddresses.address, + settingsStore: _settingsStore, + ); + } else { + uri = await requestSellMoonPayUrl( + currency: wallet.currency, + refundWalletAddress: wallet.walletAddresses.address, + settingsStore: _settingsStore, + ); + } + + if (await canLaunchUrl(uri)) { + if (DeviceInfo.instance.isMobile) { + Navigator.of(context).pushNamed(Routes.webViewPage, arguments: ['MoonPay', uri]); + } else { + await launchUrl(uri, mode: LaunchMode.externalApplication); + } + } else { + throw Exception('Could not launch URL'); + } + // } catch (e) { + // await showDialog( + // context: context, + // builder: (BuildContext context) { + // return AlertWithOneAction( + // alertTitle: 'MoonPay', + // alertContent: 'The MoonPay service is currently unavailable: $e', + // buttonText: S.of(context).ok, + // buttonAction: () => Navigator.of(context).pop(), + // ); + // }, + // ); + // } + } + + 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 782c0f1f4..291555330 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -198,6 +198,7 @@ import 'package:cake_wallet/view_model/wallet_list/wallet_list_view_model.dart'; import 'package:cake_wallet/view_model/wallet_restore_view_model.dart'; import 'package:cake_wallet/view_model/wallet_seed_view_model.dart'; import 'package:cake_wallet/view_model/exchange/exchange_view_model.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; import 'package:get_it/get_it.dart'; import 'package:hive/hive.dart'; @@ -806,8 +807,11 @@ Future setup({ getIt .registerFactory(() => DFXBuyProvider(wallet: getIt.get().wallet!)); - getIt.registerFactory(() => MoonPaySellProvider( - settingsStore: getIt.get().settingsStore, wallet: getIt.get().wallet!)); + getIt.registerFactory(() => MoonPayProvider( + settingsStore: getIt.get().settingsStore, + wallet: getIt.get().wallet!, + isTestEnvironment: kDebugMode, + )); getIt.registerFactory(() => OnRamperBuyProvider( getIt.get().settingsStore, diff --git a/lib/entities/provider_types.dart b/lib/entities/provider_types.dart index f3993e129..701781cc2 100644 --- a/lib/entities/provider_types.dart +++ b/lib/entities/provider_types.dart @@ -11,7 +11,7 @@ enum ProviderType { robinhood, dfx, onramper, - moonpaySell, + moonpay, } extension ProviderTypeName on ProviderType { @@ -25,7 +25,7 @@ extension ProviderTypeName on ProviderType { return 'DFX Connect'; case ProviderType.onramper: return 'Onramper'; - case ProviderType.moonpaySell: + case ProviderType.moonpay: return 'MoonPay'; } } @@ -40,7 +40,7 @@ extension ProviderTypeName on ProviderType { return 'dfx_connect_provider'; case ProviderType.onramper: return 'onramper_provider'; - case ProviderType.moonpaySell: + case ProviderType.moonpay: return 'moonpay_provider'; } } @@ -62,10 +62,11 @@ class ProvidersHelper { ProviderType.onramper, ProviderType.dfx, ProviderType.robinhood, + ProviderType.moonpay, ]; case WalletType.litecoin: case WalletType.bitcoinCash: - return [ProviderType.askEachTime, ProviderType.onramper, ProviderType.robinhood]; + return [ProviderType.askEachTime, ProviderType.onramper, ProviderType.robinhood, ProviderType.moonpay]; case WalletType.solana: return [ProviderType.askEachTime, ProviderType.onramper, ProviderType.robinhood]; case WalletType.none: @@ -82,18 +83,18 @@ class ProvidersHelper { return [ ProviderType.askEachTime, ProviderType.onramper, - ProviderType.moonpaySell, + ProviderType.moonpay, ProviderType.dfx, ]; case WalletType.litecoin: case WalletType.bitcoinCash: - return [ProviderType.askEachTime, ProviderType.moonpaySell]; + return [ProviderType.askEachTime, ProviderType.moonpay]; case WalletType.solana: return [ ProviderType.askEachTime, ProviderType.onramper, ProviderType.robinhood, - ProviderType.moonpaySell, + ProviderType.moonpay, ]; case WalletType.monero: case WalletType.nano: @@ -112,10 +113,10 @@ class ProvidersHelper { return getIt.get(); case ProviderType.onramper: return getIt.get(); + case ProviderType.moonpay: + return getIt.get(); case ProviderType.askEachTime: return null; - case ProviderType.moonpaySell: - return getIt.get(); } } } diff --git a/lib/src/screens/buy/buy_options_page.dart b/lib/src/screens/buy/buy_options_page.dart index 50f041d2e..38f3ed968 100644 --- a/lib/src/screens/buy/buy_options_page.dart +++ b/lib/src/screens/buy/buy_options_page.dart @@ -1,6 +1,7 @@ import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/src/screens/base_page.dart'; import 'package:cake_wallet/src/widgets/option_tile.dart'; +import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart'; import 'package:cake_wallet/themes/extensions/option_tile_theme.dart'; import 'package:cake_wallet/themes/extensions/transaction_trade_theme.dart'; import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart'; @@ -25,45 +26,46 @@ class BuySellOptionsPage extends BasePage { ? dashboardViewModel.availableBuyProviders : dashboardViewModel.availableSellProviders; - return Container( - child: Center( - child: ConstrainedBox( - constraints: BoxConstraints(maxWidth: 330), - child: Column( - children: [ - ...availableProviders.map((provider) { - final icon = Image.asset( - isLightMode ? provider.lightIcon : provider.darkIcon, - height: 40, - width: 40, - ); + return ScrollableWithBottomSection( + content: Container( + child: Center( + child: ConstrainedBox( + constraints: BoxConstraints(maxWidth: 330), + child: Column( + children: [ + ...availableProviders.map((provider) { + final icon = Image.asset( + isLightMode ? provider.lightIcon : provider.darkIcon, + height: 40, + width: 40, + ); - return Padding( - padding: EdgeInsets.only(top: 24), - child: OptionTile( - image: icon, - title: provider.toString(), - description: provider.providerDescription, - onPressed: () => provider.launchProvider(context, isBuyAction), - ), - ); - }).toList(), - Spacer(), - Padding( - padding: EdgeInsets.fromLTRB(24, 24, 24, 32), - child: Text( - isBuyAction - ? S.of(context).select_buy_provider_notice - : S.of(context).select_sell_provider_notice, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.normal, - color: Theme.of(context).extension()!.detailsTitlesColor, - ), - ), - ), - ], + return Padding( + padding: EdgeInsets.only(top: 24), + child: OptionTile( + image: icon, + title: provider.toString(), + description: provider.providerDescription, + onPressed: () => provider.launchProvider(context, isBuyAction), + ), + ); + }).toList(), + ], + ), + ), + ), + ), + bottomSection: Padding( + padding: EdgeInsets.fromLTRB(24, 24, 24, 32), + child: Text( + isBuyAction + ? S.of(context).select_buy_provider_notice + : S.of(context).select_sell_provider_notice, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.normal, + color: Theme.of(context).extension()!.detailsTitlesColor, ), ), ), diff --git a/lib/src/screens/buy/buy_webview_page.dart b/lib/src/screens/buy/buy_webview_page.dart index 829bff3d9..ad6970861 100644 --- a/lib/src/screens/buy/buy_webview_page.dart +++ b/lib/src/screens/buy/buy_webview_page.dart @@ -60,7 +60,7 @@ class BuyWebViewPageBodyState extends State { _saveOrder(keyword: 'completed', splitSymbol: '/'); } - if (widget.buyViewModel.selectedProvider is MoonPayBuyProvider) { + if (widget.buyViewModel.selectedProvider is MoonPayProvider) { _saveOrder(keyword: 'transactionId', splitSymbol: '='); } } diff --git a/lib/view_model/buy/buy_view_model.dart b/lib/view_model/buy/buy_view_model.dart index d73396e1b..7c2591cbb 100644 --- a/lib/view_model/buy/buy_view_model.dart +++ b/lib/view_model/buy/buy_view_model.dart @@ -93,18 +93,6 @@ abstract class BuyViewModelBase with Store { _providerList.add(WyreBuyProvider(wallet: wallet)); } - var isMoonPayEnabled = false; - try { - isMoonPayEnabled = await MoonPayBuyProvider.onEnabled(); - } catch (e) { - isMoonPayEnabled = false; - print(e.toString()); - } - - if (isMoonPayEnabled) { - _providerList.add(MoonPayBuyProvider(wallet: wallet)); - } - items = _providerList.map((provider) => BuyItem(provider: provider, buyAmountViewModel: buyAmountViewModel)) .toList(); diff --git a/lib/view_model/order_details_view_model.dart b/lib/view_model/order_details_view_model.dart index 384c0541d..412f1b962 100644 --- a/lib/view_model/order_details_view_model.dart +++ b/lib/view_model/order_details_view_model.dart @@ -27,7 +27,7 @@ abstract class OrderDetailsViewModelBase with Store { _provider = WyreBuyProvider(wallet: wallet); break; case BuyProviderDescription.moonPay: - _provider = MoonPayBuyProvider(wallet: wallet); + // _provider = MoonPayProvider(wallet: wallet);// TODO: CW-521 break; } } @@ -50,9 +50,9 @@ abstract class OrderDetailsViewModelBase with Store { @action Future _updateOrder() async { try { - if (_provider != null && (_provider is MoonPayBuyProvider || _provider is WyreBuyProvider)) { - final updatedOrder = _provider is MoonPayBuyProvider - ? await (_provider as MoonPayBuyProvider).findOrderById(order.id) + if (_provider != null && (_provider is MoonPayProvider || _provider is WyreBuyProvider)) { + final updatedOrder = _provider is MoonPayProvider + ? await (_provider as MoonPayProvider).findOrderById(order.id) : await (_provider as WyreBuyProvider).findOrderById(order.id); updatedOrder.from = order.from; updatedOrder.to = order.to; @@ -89,10 +89,10 @@ abstract class OrderDetailsViewModelBase with Store { value: order.provider.title) ); - if (_provider != null && (_provider is MoonPayBuyProvider || _provider is WyreBuyProvider)) { + if (_provider != null && (_provider is MoonPayProvider || _provider is WyreBuyProvider)) { - final trackUrl = _provider is MoonPayBuyProvider - ? (_provider as MoonPayBuyProvider).trackUrl + final trackUrl = _provider is MoonPayProvider + ? (_provider as MoonPayProvider).trackUrl : (_provider as WyreBuyProvider).trackUrl; if (trackUrl.isNotEmpty ?? false) {