add moonpay swaps + logo

This commit is contained in:
fosse 2023-11-10 11:52:01 -05:00
parent 9ec112e5c6
commit 0dd121a56d
7 changed files with 149 additions and 204 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

View file

@ -1,9 +1,13 @@
import 'dart:convert';
import 'package:cake_wallet/generated/i18n.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:crypto/crypto.dart';
import 'package:cake_wallet/buy/buy_exception.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart';
import 'package:cake_wallet/buy/buy_amount.dart';
import 'package:cake_wallet/buy/buy_provider.dart';
@ -14,10 +18,10 @@ import 'package:cw_core/wallet_type.dart';
import 'package:cake_wallet/exchange/trade_state.dart';
import 'package:cake_wallet/.secrets.g.dart' as secrets;
import 'package:cw_core/crypto_currency.dart';
import 'package:url_launcher/url_launcher.dart';
class MoonPaySellProvider {
MoonPaySellProvider({this.isTest = false})
: baseUrl = isTest ? _baseTestUrl : _baseProductUrl;
MoonPaySellProvider({this.isTest = false}) : baseUrl = isTest ? _baseTestUrl : _baseProductUrl;
static const _baseTestUrl = 'sell-sandbox.moonpay.com';
static const _baseProductUrl = 'sell.moonpay.com';
@ -31,16 +35,16 @@ class MoonPaySellProvider {
return 'dark';
}
}
static String get _apiKey => secrets.moonPayApiKey;
static String get _secretKey => secrets.moonPaySecretKey;
static String get _apiKey => secrets.moonPayApiKey;
static String get _secretKey => secrets.moonPaySecretKey;
final bool isTest;
final String baseUrl;
Future<Uri> requestUrl(
{required CryptoCurrency currency,
required String refundWalletAddress,
required SettingsStore settingsStore}) async {
required String refundWalletAddress,
required SettingsStore settingsStore}) async {
final customParams = {
'theme': themeToMoonPayTheme(settingsStore.currentTheme),
'language': settingsStore.languageCode,
@ -50,11 +54,13 @@ class MoonPaySellProvider {
};
final originalUri = Uri.https(
baseUrl, '', <String, dynamic>{
'apiKey': _apiKey,
'defaultBaseCurrencyCode': currency.toString().toLowerCase(),
'refundWalletAddress': refundWalletAddress
}..addAll(customParams));
baseUrl,
'',
<String, dynamic>{
'apiKey': _apiKey,
'defaultBaseCurrencyCode': currency.toString().toLowerCase(),
'refundWalletAddress': refundWalletAddress
}..addAll(customParams));
final messageBytes = utf8.encode('?${originalUri.query}');
final key = utf8.encode(_secretKey);
final hmac = Hmac(sha256, key);
@ -93,8 +99,7 @@ class MoonPayBuyProvider extends BuyProvider {
@override
BuyProviderDescription get description => BuyProviderDescription.moonPay;
String get currencyCode =>
walletTypeToCryptoCurrency(walletType).title.toLowerCase();
String get currencyCode => walletTypeToCryptoCurrency(walletType).title.toLowerCase();
@override
String get trackUrl => baseUrl + '/transaction_receipt?transactionId=';
@ -103,16 +108,24 @@ class MoonPayBuyProvider extends BuyProvider {
@override
Future<String> requestUrl(String amount, String sourceCurrency) async {
final enabledPaymentMethods =
'credit_debit_card%2Capple_pay%2Cgoogle_pay%2Csamsung_pay'
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 + '&currencyCode=' +
currencyCode + '&enabledPaymentMethods=' + enabledPaymentMethods +
'&walletAddress=' + walletAddress +
'&baseCurrencyCode=' + sourceCurrency.toLowerCase() +
'&baseCurrencyAmount=' + amount + '&lockAmount=true' +
'&showAllCurrencies=false' + '&showWalletAddressForm=false';
final suffix = '?apiKey=' +
_apiKey +
'&currencyCode=' +
currencyCode +
'&enabledPaymentMethods=' +
enabledPaymentMethods +
'&walletAddress=' +
walletAddress +
'&baseCurrencyCode=' +
sourceCurrency.toLowerCase() +
'&baseCurrencyAmount=' +
amount +
'&lockAmount=true' +
'&showAllCurrencies=false' +
'&showWalletAddressForm=false';
final originalUrl = baseUrl + suffix;
@ -121,25 +134,28 @@ class MoonPayBuyProvider extends BuyProvider {
final hmac = Hmac(sha256, key);
final digest = hmac.convert(messageBytes);
final signature = base64.encode(digest.bytes);
final urlWithSignature = originalUrl +
'&signature=${Uri.encodeComponent(signature)}';
final urlWithSignature = originalUrl + '&signature=${Uri.encodeComponent(signature)}';
return isTestEnvironment ? originalUrl : urlWithSignature;
}
@override
Future<BuyAmount> calculateAmount(String amount, String sourceCurrency) async {
final url = _apiUrl + _currenciesSuffix + '/$currencyCode' +
_quoteSuffix + '/?apiKey=' + _apiKey +
'&baseCurrencyAmount=' + amount +
'&baseCurrencyCode=' + sourceCurrency.toLowerCase();
final url = _apiUrl +
_currenciesSuffix +
'/$currencyCode' +
_quoteSuffix +
'/?apiKey=' +
_apiKey +
'&baseCurrencyAmount=' +
amount +
'&baseCurrencyCode=' +
sourceCurrency.toLowerCase();
final uri = Uri.parse(url);
final response = await get(uri);
if (response.statusCode != 200) {
throw BuyException(
description: description,
text: 'Quote is not found!');
throw BuyException(description: description, text: 'Quote is not found!');
}
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
@ -148,22 +164,17 @@ class MoonPayBuyProvider extends BuyProvider {
final minSourceAmount = responseJSON['baseCurrency']['minAmount'] as int;
return BuyAmount(
sourceAmount: sourceAmount,
destAmount: destAmount,
minAmount: minSourceAmount);
sourceAmount: sourceAmount, destAmount: destAmount, minAmount: minSourceAmount);
}
@override
Future<Order> findOrderById(String id) async {
final url = _apiUrl + _transactionsSuffix + '/$id' +
'?apiKey=' + _apiKey;
final url = _apiUrl + _transactionsSuffix + '/$id' + '?apiKey=' + _apiKey;
final uri = Uri.parse(url);
final response = await get(uri);
if (response.statusCode != 200) {
throw BuyException(
description: description,
text: 'Transaction $id is not found!');
throw BuyException(description: description, text: 'Transaction $id is not found!');
}
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
@ -181,8 +192,7 @@ class MoonPayBuyProvider extends BuyProvider {
createdAt: createdAt,
amount: amount.toString(),
receiveAddress: walletAddress,
walletId: walletId
);
walletId: walletId);
}
static Future<bool> onEnabled() async {
@ -201,4 +211,77 @@ class MoonPayBuyProvider extends BuyProvider {
return isBuyEnable;
}
}
}
class MoonPayExchangeProvider {
MoonPayExchangeProvider(
{required SettingsStore settingsStore, required WalletBase wallet, this.isTest = true})
: this._settingsStore = settingsStore,
this._wallet = wallet,
baseUrl = isTest ? _baseTestUrl : _baseProductUrl;
final SettingsStore _settingsStore;
final WalletBase _wallet;
static const _baseTestUrl = 'buy-staging.moonpay.com';
static const _apiKey = secrets.moonPayApiKey;
static const _baseProductUrl = 'buy.moonpay.com';
static const _secretKey = secrets.moonPaySecretKey;
static String themeToMoonPayTheme(ThemeBase theme) {
switch (theme.type) {
case ThemeType.bright:
return 'light';
case ThemeType.light:
return 'light';
case ThemeType.dark:
return 'dark';
}
}
final bool isTest;
final String baseUrl;
Future<Uri> requestUrl(
{required CryptoCurrency currency, required String refundWalletAddress}) async {
final customParams = {
'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)}',
};
final originalUri = Uri.https(
baseUrl,
'swaps',
<String, dynamic>{
'apiKey': _apiKey,
'baseCurrencyCode': currency.toString().toLowerCase(),
'refundWalletAddress': refundWalletAddress
}..addAll(customParams));
final messageBytes = utf8.encode('?${originalUri.query}');
final key = utf8.encode(_secretKey);
final hmac = Hmac(sha256, key);
final digest = hmac.convert(messageBytes);
final signature = base64.encode(digest.bytes);
if (isTest) {
return originalUri;
}
final query = Map<String, dynamic>.from(originalUri.queryParameters);
query['signature'] = signature;
final signedUri = originalUri.replace(queryParameters: query);
return signedUri;
}
Future<void> launchProvider(BuildContext context) async {
final uri = await requestUrl(currency: _wallet.currency, refundWalletAddress: _wallet.walletAddresses.address);
if (DeviceInfo.instance.isMobile) {
Navigator.of(context).pushNamed(Routes.webViewPage, arguments: [S.of(context).exchange, uri]);
} else {
await launchUrl(uri);
}
}
}

View file

@ -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/moonpay/moonpay_buy_provider.dart';
import 'package:cake_wallet/buy/onramper/onramper_buy_provider.dart';
import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
import 'package:cake_wallet/buy/payfura/payfura_buy_provider.dart';
@ -14,7 +15,6 @@ import 'package:cake_wallet/entities/exchange_api_mode.dart';
import 'package:cake_wallet/entities/parse_address_from_domain.dart';
import 'package:cake_wallet/entities/receive_page_option.dart';
import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/exchange/moonpay/moonpay_exchange_provider.dart';
import 'package:cake_wallet/nano/nano.dart';
import 'package:cake_wallet/ionia/ionia_anypay.dart';
import 'package:cake_wallet/ionia/ionia_gift_card.dart';
@ -520,8 +520,7 @@ Future<void> setup({
getIt.registerFactory<Modify2FAPage>(
() => Modify2FAPage(setup2FAViewModel: getIt.get<Setup2FAViewModel>()));
getIt.registerFactory<DesktopSettingsPage>(
() => DesktopSettingsPage());
getIt.registerFactory<DesktopSettingsPage>(() => DesktopSettingsPage());
getIt.registerFactoryParam<ReceiveOptionViewModel, ReceivePageOption?, void>(
(pageOption, _) => ReceiveOptionViewModel(getIt.get<AppStore>().wallet!, pageOption));
@ -838,7 +837,8 @@ Future<void> setup({
case WalletType.ethereum:
return ethereum!.createEthereumWalletService(_walletInfoSource);
case WalletType.bitcoinCash:
return bitcoinCash!.createBitcoinCashWalletService(_walletInfoSource, _unspentCoinsInfoSource!);
return bitcoinCash!
.createBitcoinCashWalletService(_walletInfoSource, _unspentCoinsInfoSource!);
case WalletType.nano:
return nano!.createNanoWalletService(_walletInfoSource);
default:
@ -992,11 +992,10 @@ Future<void> setup({
getIt.registerFactory(() => YatService());
getIt.registerFactory(() =>
AddressResolver(
yatService: getIt.get<YatService>(),
wallet: getIt.get<AppStore>().wallet!,
settingsStore: getIt.get<SettingsStore>()));
getIt.registerFactory(() => AddressResolver(
yatService: getIt.get<YatService>(),
wallet: getIt.get<AppStore>().wallet!,
settingsStore: getIt.get<SettingsStore>()));
getIt.registerFactoryParam<FullscreenQRPage, QrViewData, void>(
(QrViewData viewData, _) => FullscreenQRPage(qrViewData: viewData));
@ -1172,6 +1171,5 @@ Future<void> setup({
getIt.registerFactory(
() => WalletConnectConnectionsView(web3walletService: getIt.get<Web3WalletService>()));
_isSetupFinished = true;
}

View file

@ -4,7 +4,6 @@ import 'package:cake_wallet/buy/robinhood/robinhood_buy_provider.dart';
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/entities/buy_provider_types.dart';
import 'package:cake_wallet/entities/exchange_provider_types.dart';
import 'package:cake_wallet/exchange/moonpay/moonpay_exchange_provider.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';

View file

@ -1,135 +0,0 @@
import 'dart:convert';
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/generated/i18n.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:crypto/crypto.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:flutter/widgets.dart';
import 'package:cake_wallet/.secrets.g.dart' as secrets;
import 'package:cw_core/crypto_currency.dart';
import 'package:url_launcher/url_launcher.dart';
class MoonPayExchangeProvider {
MoonPayExchangeProvider(
{required SettingsStore settingsStore, required WalletBase wallet, this.isTest = true})
: this._settingsStore = settingsStore,
this._wallet = wallet,
baseUrl = isTest ? _baseTestUrl : _baseProductUrl;
final SettingsStore _settingsStore;
final WalletBase _wallet;
static const _baseTestUrl = 'buy-staging.moonpay.com';
static const _baseProductUrl = 'buy.moonpay.com';
static const _apiUrl = 'https://api.moonpay.com';
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;
static String themeToMoonPayTheme(ThemeBase theme) {
switch (theme.type) {
case ThemeType.bright:
return 'light';
case ThemeType.light:
return 'light';
case ThemeType.dark:
return 'dark';
}
}
final bool isTest;
final String baseUrl;
Future<Uri> requestUrl(
{required CryptoCurrency currency, required String refundWalletAddress}) async {
final customParams = {
'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)}',
};
final originalUri = Uri.https(
baseUrl,
'',
<String, dynamic>{
// 'apiKey': _apiKey,
'defaultBaseCurrencyCode': currency.toString().toLowerCase(),
// 'refundWalletAddress': refundWalletAddress
}..addAll(customParams));
final messageBytes = utf8.encode('?${originalUri.query}');
final key = utf8.encode(_secretKey);
final hmac = Hmac(sha256, key);
final digest = hmac.convert(messageBytes);
final signature = base64.encode(digest.bytes);
if (isTest) {
return originalUri;
}
final query = Map<String, dynamic>.from(originalUri.queryParameters);
query['signature'] = signature;
final signedUri = originalUri.replace(queryParameters: query);
return signedUri;
}
// void _initialPairBasedOnWallet() {
// switch (wallet.type) {
// case WalletType.monero:
// depositCurrency = CryptoCurrency.xmr;
// receiveCurrency = CryptoCurrency.btc;
// break;
// case WalletType.bitcoin:
// depositCurrency = CryptoCurrency.btc;
// receiveCurrency = CryptoCurrency.xmr;
// break;
// case WalletType.litecoin:
// depositCurrency = CryptoCurrency.ltc;
// receiveCurrency = CryptoCurrency.xmr;
// break;
// case WalletType.bitcoinCash:
// depositCurrency = CryptoCurrency.bch;
// receiveCurrency = CryptoCurrency.xmr;
// break;
// case WalletType.haven:
// depositCurrency = CryptoCurrency.xhv;
// receiveCurrency = CryptoCurrency.btc;
// break;
// case WalletType.ethereum:
// depositCurrency = CryptoCurrency.eth;
// receiveCurrency = CryptoCurrency.xmr;
// break;
// case WalletType.nano:
// depositCurrency = CryptoCurrency.nano;
// receiveCurrency = CryptoCurrency.xmr;
// break;
// default:
// break;
// }
// }
Future<void> launchProvider(BuildContext context) async {
// primaryColor = getColorStr(Theme.of(context).primaryColor);
// secondaryColor = getColorStr(Theme.of(context).colorScheme.background);
// primaryTextColor = getColorStr(Theme.of(context).extension<CakeTextTheme>()!.titleColor);
// secondaryTextColor =
// getColorStr(Theme.of(context).extension<CakeTextTheme>()!.secondaryTextColor);
// containerColor = getColorStr(Theme.of(context).colorScheme.background);
// cardColor = getColorStr(Theme.of(context).cardColor);
final uri = await requestUrl(currency: CryptoCurrency.xmr, refundWalletAddress: "");
if (DeviceInfo.instance.isMobile) {
Navigator.of(context).pushNamed(Routes.webViewPage, arguments: [S.of(context).exchange, uri]);
} else {
await launchUrl(uri);
}
}
}

View file

@ -403,9 +403,8 @@ Route<dynamic> createRoute(RouteSettings settings) {
fullscreenDialog: true, builder: (_) => getIt.get<BuyWebViewPage>(param1: args));
case Routes.exchange:
final args = settings.arguments as bool;
return CupertinoPageRoute<void>(
fullscreenDialog: true, builder: (_) => getIt.get<ExchangePage>(param1: args));
fullscreenDialog: true, builder: (_) => getIt.get<ExchangePage>());
case Routes.exchangeTemplate:
return CupertinoPageRoute<void>(builder: (_) => getIt.get<ExchangeTemplatePage>());

View file

@ -1,6 +1,5 @@
import 'package:cake_wallet/buy/robinhood/robinhood_buy_provider.dart';
import 'package:cake_wallet/buy/moonpay/moonpay_buy_provider.dart';
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/exchange/moonpay/moonpay_exchange_provider.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
@ -10,10 +9,10 @@ import 'package:cake_wallet/themes/extensions/transaction_trade_theme.dart';
import 'package:flutter/material.dart';
class ExchangeOptionsPage extends BasePage {
final iconDarkRobinhood = 'assets/images/robinhood_dark.png';
final iconLightRobinhood = 'assets/images/robinhood_light.png';
final iconDarkOnramper = 'assets/images/onramper_dark.png';
final iconLightOnramper = 'assets/images/onramper_light.png';
final iconLightMoonPay = 'assets/images/moonpay_dark.png';
final iconDarkMoonPay = 'assets/images/moonpay.png';
final iconLightNormalExchange = 'assets/images/transfer.png';
final iconDarkNormalExchange = 'assets/images/transfer.png';
@override
String get title => S.current.exchange;
@ -24,10 +23,10 @@ class ExchangeOptionsPage extends BasePage {
@override
Widget body(BuildContext context) {
final isLightMode = Theme.of(context).extension<OptionTileTheme>()?.useDarkImage ?? false;
final iconRobinhood =
Image.asset(isLightMode ? iconLightRobinhood : iconDarkRobinhood, height: 40, width: 40);
final iconOnramper =
Image.asset(isLightMode ? iconLightOnramper : iconDarkOnramper, height: 40, width: 40);
final iconMoonPay =
Image.asset(isLightMode ? iconLightMoonPay : iconDarkMoonPay, height: 40, width: 40);
final iconNormalExchange =
Image.asset(isLightMode ? iconLightNormalExchange : iconDarkNormalExchange, height: 40, width: 40);
return Container(
child: Center(
@ -38,19 +37,21 @@ class ExchangeOptionsPage extends BasePage {
Padding(
padding: EdgeInsets.only(top: 24),
child: OptionTile(
image: iconRobinhood,
image: iconMoonPay,
title: "MoonPay Swaps",
description: S.of(context).moonpay_exchange_description,
onPressed: () async => await Navigator.of(context).pushReplacementNamed(Routes.exchange, arguments: true),
onPressed: () async =>
await getIt.get<MoonPayExchangeProvider>().launchProvider(context),
),
),
Padding(
padding: EdgeInsets.only(top: 24),
child: OptionTile(
image: iconOnramper,
image: iconNormalExchange,
title: "Normal Exchange",
description: S.of(context).normal_exchange_description,
onPressed: () async => await Navigator.of(context).pushReplacementNamed(Routes.exchange),
onPressed: () async =>
await Navigator.of(context).pushReplacementNamed(Routes.exchange),
),
),
Spacer(),