diff --git a/assets/images/moonpay_dark.png b/assets/images/moonpay_dark.png new file mode 100644 index 000000000..872e322e2 Binary files /dev/null and b/assets/images/moonpay_dark.png differ diff --git a/assets/images/moonpay_light.png b/assets/images/moonpay_light.png new file mode 100644 index 000000000..c76ae6e74 Binary files /dev/null and b/assets/images/moonpay_light.png differ diff --git a/cw_haven/pubspec.lock b/cw_haven/pubspec.lock index 525e8e5fa..b0a350cc7 100644 --- a/cw_haven/pubspec.lock +++ b/cw_haven/pubspec.lock @@ -623,15 +623,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.1" - tor: - dependency: transitive - description: - path: "." - ref: main - resolved-ref: "09ba92cb11d4e3cacf97256e57863b805f79f2e5" - url: "https://github.com/cake-tech/tor.git" - source: git - version: "0.0.1" typed_data: dependency: transitive description: diff --git a/lib/buy/buy_exception.dart b/lib/buy/buy_exception.dart index edc6a7db0..c201b3b2d 100644 --- a/lib/buy/buy_exception.dart +++ b/lib/buy/buy_exception.dart @@ -2,11 +2,11 @@ import 'package:flutter/foundation.dart'; import 'package:cake_wallet/buy/buy_provider_description.dart'; class BuyException implements Exception { - BuyException({required this.description, required this.text}); + BuyException({required this.title, required this.content}); - final BuyProviderDescription description; - final String text; + final String title; + final String content; @override - String toString() => '${description.title}: $text'; + String toString() => '$title: $content'; } \ No newline at end of file diff --git a/lib/buy/buy_provider.dart b/lib/buy/buy_provider.dart index 10a13ed94..93f4e8105 100644 --- a/lib/buy/buy_provider.dart +++ b/lib/buy/buy_provider.dart @@ -1,27 +1,35 @@ import 'package:cake_wallet/buy/buy_amount.dart'; -import 'package:cake_wallet/buy/buy_provider_description.dart'; import 'package:cake_wallet/buy/order.dart'; import 'package:cw_core/wallet_base.dart'; -import 'package:cw_core/wallet_type.dart'; +import 'package:flutter/material.dart'; abstract class BuyProvider { - BuyProvider({required this.wallet, required this.isTestEnvironment}); + BuyProvider({ + required this.wallet, + required this.isTestEnvironment, + }); final WalletBase wallet; final bool isTestEnvironment; String get title; - BuyProviderDescription get description; - String get trackUrl; - WalletType get walletType => wallet.type; - String get walletAddress => wallet.walletAddresses.address; - String get walletId => wallet.id; + String get buyOptionDescription; + + String get sellOptionDescription; + + String get lightIcon; + + String get darkIcon; @override String toString() => title; - Future requestUrl(String amount, String sourceCurrency); - Future findOrderById(String id); - Future calculateAmount(String amount, String sourceCurrency); -} \ No newline at end of file + Future launchProvider(BuildContext context, bool? isBuyAction); + + Future requestUrl(String amount, String sourceCurrency) => throw UnimplementedError(); + + Future findOrderById(String id) => throw UnimplementedError(); + + Future calculateAmount(String amount, String sourceCurrency) => throw UnimplementedError(); +} diff --git a/lib/buy/dfx/dfx_buy_provider.dart b/lib/buy/dfx/dfx_buy_provider.dart index d5cf50d10..18ac081ea 100644 --- a/lib/buy/dfx/dfx_buy_provider.dart +++ b/lib/buy/dfx/dfx_buy_provider.dart @@ -1,5 +1,6 @@ import 'dart:convert'; +import 'package:cake_wallet/buy/buy_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'; @@ -11,10 +12,9 @@ import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import 'package:url_launcher/url_launcher.dart'; -class DFXBuyProvider { - DFXBuyProvider({required WalletBase wallet}) : this._wallet = wallet; - - final WalletBase _wallet; +class DFXBuyProvider extends BuyProvider { + DFXBuyProvider({required WalletBase wallet, bool isTestEnvironment = false}) + : super(wallet: wallet, isTestEnvironment: isTestEnvironment); static const _baseUrl = 'api.dfx.swiss'; static const _authPath = '/v1/auth/signMessage'; @@ -22,8 +22,23 @@ class DFXBuyProvider { static const _signInPath = '/v1/auth/signIn'; static const walletName = 'CakeWallet'; + @override + String get title => 'DFX Connect'; + + @override + String get buyOptionDescription => S.current.dfx_option_description; + + @override + String get sellOptionDescription => S.current.dfx_option_description; + + @override + String get lightIcon => 'assets/images/dfx_light.png'; + + @override + String get darkIcon => 'assets/images/dfx_dark.png'; + String get assetOut { - switch (_wallet.type) { + switch (wallet.type) { case WalletType.bitcoin: return 'BTC'; case WalletType.bitcoinCash: @@ -35,12 +50,12 @@ class DFXBuyProvider { case WalletType.ethereum: return 'ETH'; default: - throw Exception("WalletType is not available for DFX ${_wallet.type}"); + throw Exception("WalletType is not available for DFX ${wallet.type}"); } } String get blockchain { - switch (_wallet.type) { + switch (wallet.type) { case WalletType.bitcoin: case WalletType.bitcoinCash: case WalletType.litecoin: @@ -50,12 +65,12 @@ class DFXBuyProvider { case WalletType.ethereum: return 'Ethereum'; default: - throw Exception("WalletType is not available for DFX ${_wallet.type}"); + throw Exception("WalletType is not available for DFX ${wallet.type}"); } } Future getSignMessage() async { - final walletAddress = _wallet.walletAddresses.address; + final walletAddress = wallet.walletAddresses.address; final uri = Uri.https(_baseUrl, _authPath, {'address': walletAddress}); var response = await http.get(uri, headers: {'accept': 'application/json'}); @@ -71,7 +86,7 @@ class DFXBuyProvider { Future signUp() async { final signMessage = getSignature(await getSignMessage()); - final walletAddress = _wallet.walletAddresses.address; + final walletAddress = wallet.walletAddresses.address; final requestBody = jsonEncode({ 'wallet': walletName, @@ -86,6 +101,10 @@ class DFXBuyProvider { if (response.statusCode == 201) { final responseBody = jsonDecode(response.body); return responseBody['accessToken'] as String; + } else if (response.statusCode == 403) { + final responseBody = jsonDecode(response.body); + final message = responseBody['message'] ?? 'Service unavailable in your country'; + throw Exception(message); } else { throw Exception( 'Failed to sign up. Status: ${response.statusCode} ${response.body}'); @@ -94,7 +113,7 @@ class DFXBuyProvider { Future signIn() async { final signMessage = getSignature(await getSignMessage()); - final walletAddress = _wallet.walletAddresses.address; + final walletAddress = wallet.walletAddresses.address; final requestBody = jsonEncode({ 'address': walletAddress, @@ -108,6 +127,10 @@ class DFXBuyProvider { if (response.statusCode == 201) { final responseBody = jsonDecode(response.body); return responseBody['accessToken'] as String; + } else if (response.statusCode == 403) { + final responseBody = jsonDecode(response.body); + final message = responseBody['message'] ?? 'Service unavailable in your country'; + throw Exception(message); } else { throw Exception( 'Failed to sign in. Status: ${response.statusCode} ${response.body}'); @@ -115,24 +138,25 @@ class DFXBuyProvider { } String getSignature(String message) { - switch (_wallet.type) { + switch (wallet.type) { case WalletType.ethereum: - return _wallet.signMessage(message); + return wallet.signMessage(message); case WalletType.monero: case WalletType.litecoin: case WalletType.bitcoin: case WalletType.bitcoinCash: - return _wallet.signMessage(message, - address: _wallet.walletAddresses.address); + return wallet.signMessage(message, + address: wallet.walletAddresses.address); default: - throw Exception("WalletType is not available for DFX ${_wallet.type}"); + throw Exception("WalletType is not available for DFX ${wallet.type}"); } } - Future launchProvider(BuildContext context) async { + Future launchProvider(BuildContext context, bool? isBuyAction) async { try { final assetOut = this.assetOut; final blockchain = this.blockchain; + final actionType = isBuyAction == true ? '/buy' : '/sell'; String accessToken; @@ -146,7 +170,7 @@ class DFXBuyProvider { } } - final uri = Uri.https('services.dfx.swiss', '/buy', { + final uri = Uri.https('services.dfx.swiss', actionType, { 'session': accessToken, 'lang': 'en', 'asset-out': assetOut, @@ -156,8 +180,8 @@ class DFXBuyProvider { if (await canLaunchUrl(uri)) { if (DeviceInfo.instance.isMobile) { - Navigator.of(context).pushNamed(Routes.webViewPage, - arguments: [S.of(context).buy, uri]); + Navigator.of(context) + .pushNamed(Routes.webViewPage, arguments: ["DFX Connect", uri]); } else { await launchUrl(uri, mode: LaunchMode.externalApplication); } diff --git a/lib/buy/moonpay/moonpay_buy_provider.dart b/lib/buy/moonpay/moonpay_buy_provider.dart index 0736edeb7..0831e1313 100644 --- a/lib/buy/moonpay/moonpay_buy_provider.dart +++ b/lib/buy/moonpay/moonpay_buy_provider.dart @@ -1,9 +1,14 @@ import 'dart:convert'; import 'package:cake_wallet/palette.dart'; +import 'package:cake_wallet/routes.dart'; +import 'package:cake_wallet/src/widgets/alert_with_one_action.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:cake_wallet/generated/i18n.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,33 +19,41 @@ 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({required SettingsStore settingsStore, + required WalletBase wallet, this.isTest = false}) + : baseUrl = isTest ? _baseTestUrl : _baseProductUrl, + this._settingsStore = settingsStore, + this._wallet = wallet; + + final SettingsStore _settingsStore; + final WalletBase _wallet; static const _baseTestUrl = 'sell-sandbox.moonpay.com'; static const _baseProductUrl = 'sell.moonpay.com'; + static String themeToMoonPayTheme(ThemeBase theme) { switch (theme.type) { case ThemeType.bright: - return 'light'; case ThemeType.light: return 'light'; case ThemeType.dark: 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 requestUrl( - {required CryptoCurrency currency, - required String refundWalletAddress, - required SettingsStore settingsStore}) async { - + Future requestUrl({ + required CryptoCurrency currency, + required String refundWalletAddress, + required SettingsStore settingsStore, + }) async { final customParams = { 'theme': themeToMoonPayTheme(settingsStore.currentTheme), 'language': settingsStore.languageCode, @@ -50,11 +63,15 @@ class MoonPaySellProvider { }; final originalUri = Uri.https( - baseUrl, '', { + baseUrl, + '', + { 'apiKey': _apiKey, 'defaultBaseCurrencyCode': currency.toString().toLowerCase(), - 'refundWalletAddress': refundWalletAddress - }..addAll(customParams)); + 'refundWalletAddress': refundWalletAddress, + }..addAll(customParams), + ); + final messageBytes = utf8.encode('?${originalUri.query}'); final key = utf8.encode(_secretKey); final hmac = Hmac(sha256, key); @@ -70,6 +87,38 @@ class MoonPaySellProvider { final signedUri = originalUri.replace(queryParameters: query); return signedUri; } + + Future launchProvider(BuildContext context) async { + try { + final uri = await requestUrl( + 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(), + ); + }, + ); + } + } } class MoonPayBuyProvider extends BuyProvider { @@ -88,20 +137,24 @@ class MoonPayBuyProvider extends BuyProvider { static const _secretKey = secrets.moonPaySecretKey; @override - String get title => 'MoonPay'; + String get title => 'Moon Pay'; @override - BuyProviderDescription get description => BuyProviderDescription.moonPay; + String get buyOptionDescription => ''; + @override + String get lightIcon => 'assets/images/moonpay_light.png'; + + @override + String get darkIcon => 'assets/images/moonpay_dark.png'; + String get currencyCode => - walletTypeToCryptoCurrency(walletType).title.toLowerCase(); + walletTypeToCryptoCurrency(wallet.type).title.toLowerCase(); - @override String get trackUrl => baseUrl + '/transaction_receipt?transactionId='; String baseUrl; - @override Future requestUrl(String amount, String sourceCurrency) async { final enabledPaymentMethods = 'credit_debit_card%2Capple_pay%2Cgoogle_pay%2Csamsung_pay' @@ -109,7 +162,7 @@ class MoonPayBuyProvider extends BuyProvider { final suffix = '?apiKey=' + _apiKey + '¤cyCode=' + currencyCode + '&enabledPaymentMethods=' + enabledPaymentMethods + - '&walletAddress=' + walletAddress + + '&walletAddress=' + wallet.walletAddresses.address + '&baseCurrencyCode=' + sourceCurrency.toLowerCase() + '&baseCurrencyAmount=' + amount + '&lockAmount=true' + '&showAllCurrencies=false' + '&showWalletAddressForm=false'; @@ -127,7 +180,6 @@ class MoonPayBuyProvider extends BuyProvider { return isTestEnvironment ? originalUrl : urlWithSignature; } - @override Future calculateAmount(String amount, String sourceCurrency) async { final url = _apiUrl + _currenciesSuffix + '/$currencyCode' + _quoteSuffix + '/?apiKey=' + _apiKey + @@ -138,8 +190,8 @@ class MoonPayBuyProvider extends BuyProvider { if (response.statusCode != 200) { throw BuyException( - description: description, - text: 'Quote is not found!'); + title: buyOptionDescription, + content: 'Quote is not found!'); } final responseJSON = json.decode(response.body) as Map; @@ -153,7 +205,6 @@ class MoonPayBuyProvider extends BuyProvider { minAmount: minSourceAmount); } - @override Future findOrderById(String id) async { final url = _apiUrl + _transactionsSuffix + '/$id' + '?apiKey=' + _apiKey; @@ -162,8 +213,8 @@ class MoonPayBuyProvider extends BuyProvider { if (response.statusCode != 200) { throw BuyException( - description: description, - text: 'Transaction $id is not found!'); + title: buyOptionDescription, + content: 'Transaction $id is not found!'); } final responseJSON = json.decode(response.body) as Map; @@ -175,13 +226,13 @@ class MoonPayBuyProvider extends BuyProvider { return Order( id: id, - provider: description, + provider: BuyProviderDescription.moonPay, transferId: id, state: state, createdAt: createdAt, amount: amount.toString(), - receiveAddress: walletAddress, - walletId: walletId + receiveAddress: wallet.walletAddresses.address, + walletId: wallet.id ); } @@ -201,4 +252,14 @@ class MoonPayBuyProvider extends BuyProvider { return isBuyEnable; } + + @override + // TODO: implement sellOptionDescription + String get sellOptionDescription => throw UnimplementedError(); + + @override + Future launchProvider(BuildContext context, bool? isBuyAction) { + // TODO: implement launchProvider + throw UnimplementedError(); + } } \ No newline at end of file diff --git a/lib/buy/onramper/onramper_buy_provider.dart b/lib/buy/onramper/onramper_buy_provider.dart index 872fcebf5..e38d95df3 100644 --- a/lib/buy/onramper/onramper_buy_provider.dart +++ b/lib/buy/onramper/onramper_buy_provider.dart @@ -1,4 +1,5 @@ import 'package:cake_wallet/.secrets.g.dart' as secrets; +import 'package:cake_wallet/buy/buy_provider.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/store/settings_store.dart'; @@ -9,20 +10,34 @@ import 'package:cw_core/wallet_base.dart'; import 'package:flutter/material.dart'; import 'package:url_launcher/url_launcher.dart'; -class OnRamperBuyProvider { - OnRamperBuyProvider({required SettingsStore settingsStore, required WalletBase wallet}) - : this._settingsStore = settingsStore, - this._wallet = wallet; - - final SettingsStore _settingsStore; - final WalletBase _wallet; +class OnRamperBuyProvider extends BuyProvider { + OnRamperBuyProvider(this._settingsStore, + {required WalletBase wallet, bool isTestEnvironment = false}) + : super(wallet: wallet, isTestEnvironment: isTestEnvironment); static const _baseUrl = 'buy.onramper.com'; + final SettingsStore _settingsStore; + + @override + String get title => 'Onramper'; + + @override + String get buyOptionDescription => S.current.onramper_option_description; + + @override + String get sellOptionDescription => S.current.onramper_option_description; + + @override + String get lightIcon => 'assets/images/onramper_light.png'; + + @override + String get darkIcon => 'assets/images/onramper_dark.png'; + String get _apiKey => secrets.onramperApiKey; String get _normalizeCryptoCurrency { - switch (_wallet.currency) { + switch (wallet.currency) { case CryptoCurrency.ltc: return "LTC_LITECOIN"; case CryptoCurrency.xmr: @@ -32,7 +47,7 @@ class OnRamperBuyProvider { case CryptoCurrency.nano: return "XNO_NANO"; default: - return _wallet.currency.title; + return wallet.currency.title; } } @@ -40,7 +55,7 @@ class OnRamperBuyProvider { return color.value.toRadixString(16).replaceAll(RegExp(r'^ff'), ""); } - Uri requestUrl(BuildContext context) { + Uri requestOnramperUrl(BuildContext context) { String primaryColor, secondaryColor, primaryTextColor, @@ -50,9 +65,10 @@ class OnRamperBuyProvider { primaryColor = getColorStr(Theme.of(context).primaryColor); secondaryColor = getColorStr(Theme.of(context).colorScheme.background); - primaryTextColor = getColorStr(Theme.of(context).extension()!.titleColor); - secondaryTextColor = - getColorStr(Theme.of(context).extension()!.secondaryTextColor); + primaryTextColor = + getColorStr(Theme.of(context).extension()!.titleColor); + secondaryTextColor = getColorStr( + Theme.of(context).extension()!.secondaryTextColor); containerColor = getColorStr(Theme.of(context).colorScheme.background); cardColor = getColorStr(Theme.of(context).cardColor); @@ -60,12 +76,13 @@ class OnRamperBuyProvider { cardColor = getColorStr(Colors.white); } - final networkName = _wallet.currency.fullName?.toUpperCase().replaceAll(" ", ""); + final networkName = + wallet.currency.fullName?.toUpperCase().replaceAll(" ", ""); return Uri.https(_baseUrl, '', { 'apiKey': _apiKey, 'defaultCrypto': _normalizeCryptoCurrency, - 'networkWallets': '${networkName}:${_wallet.walletAddresses.address}', + 'networkWallets': '${networkName}:${wallet.walletAddresses.address}', 'supportSell': "false", 'supportSwap': "false", 'primaryColor': primaryColor, @@ -77,10 +94,11 @@ class OnRamperBuyProvider { }); } - Future launchProvider(BuildContext context) async { - final uri = requestUrl(context); + Future launchProvider(BuildContext context, bool? isBuyAction) async { + final uri = requestOnramperUrl(context); if (DeviceInfo.instance.isMobile) { - Navigator.of(context).pushNamed(Routes.webViewPage, arguments: [S.of(context).buy, uri]); + Navigator.of(context) + .pushNamed(Routes.webViewPage, arguments: [S.of(context).buy, uri]); } else { await launchUrl(uri); } diff --git a/lib/buy/robinhood/robinhood_buy_provider.dart b/lib/buy/robinhood/robinhood_buy_provider.dart index ade0bf99f..ff6c6a8cf 100644 --- a/lib/buy/robinhood/robinhood_buy_provider.dart +++ b/lib/buy/robinhood/robinhood_buy_provider.dart @@ -1,6 +1,10 @@ import 'dart:convert'; import 'package:cake_wallet/.secrets.g.dart' as secrets; +import 'package:cake_wallet/buy/buy_amount.dart'; +import 'package:cake_wallet/buy/buy_provider.dart'; +import 'package:cake_wallet/buy/buy_provider_description.dart'; +import 'package:cake_wallet/buy/order.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; @@ -10,40 +14,48 @@ import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import 'package:url_launcher/url_launcher.dart'; -class RobinhoodBuyProvider { - RobinhoodBuyProvider({required WalletBase wallet}) : this._wallet = wallet; - - final WalletBase _wallet; +class RobinhoodBuyProvider extends BuyProvider{ + RobinhoodBuyProvider({required WalletBase wallet, bool isTestEnvironment = false}) + : super(wallet: wallet, isTestEnvironment: isTestEnvironment); static const _baseUrl = 'applink.robinhood.com'; static const _cIdBaseUrl = 'exchange-helper.cakewallet.com'; + + @override + String get title => 'Robinhood Connect'; + + @override + String get buyOptionDescription => S.current.robinhood_option_description; + + @override + String get sellOptionDescription => S.current.robinhood_option_description; + + @override + String get lightIcon => 'assets/images/robinhood_light.png'; + + @override + String get darkIcon => 'assets/images/robinhood_dark.png'; + String get _applicationId => secrets.robinhoodApplicationId; String get _apiSecret => secrets.robinhoodCIdApiSecret; - bool get isAvailable => [ - WalletType.bitcoin, - WalletType.bitcoinCash, - WalletType.litecoin, - WalletType.ethereum - ].contains(_wallet.type); - String getSignature(String message) { - switch (_wallet.type) { + switch (wallet.type) { case WalletType.ethereum: - return _wallet.signMessage(message); + return wallet.signMessage(message); case WalletType.litecoin: case WalletType.bitcoin: case WalletType.bitcoinCash: - return _wallet.signMessage(message, address: _wallet.walletAddresses.address); + return wallet.signMessage(message, address: wallet.walletAddresses.address); default: - throw Exception("WalletType is not available for Robinhood ${_wallet.type}"); + throw Exception("WalletType is not available for Robinhood ${wallet.type}"); } } Future getConnectId() async { - final walletAddress = _wallet.walletAddresses.address; + final walletAddress = wallet.walletAddresses.address; final valid_until = (DateTime.now().millisecondsSinceEpoch / 1000).round() + 10; final message = "$_apiSecret:${valid_until}"; @@ -64,22 +76,22 @@ class RobinhoodBuyProvider { } } - Future requestUrl() async { + Future requestProviderUrl() async { final connectId = await getConnectId(); - final networkName = _wallet.currency.fullName?.toUpperCase().replaceAll(" ", "_"); + final networkName = wallet.currency.fullName?.toUpperCase().replaceAll(" ", "_"); return Uri.https(_baseUrl, '/u/connect', { 'applicationId': _applicationId, 'connectId': connectId, - 'walletAddress': _wallet.walletAddresses.address, - 'userIdentifier': _wallet.walletAddresses.address, + 'walletAddress': wallet.walletAddresses.address, + 'userIdentifier': wallet.walletAddresses.address, 'supportedNetworks': networkName }); } - Future launchProvider(BuildContext context) async { + Future launchProvider(BuildContext context, bool? isBuyAction) async { try { - final uri = await requestUrl(); + final uri = await requestProviderUrl(); await launchUrl(uri, mode: LaunchMode.externalApplication); } catch (_) { await showPopUp( @@ -93,4 +105,5 @@ class RobinhoodBuyProvider { }); } } + } diff --git a/lib/buy/wyre/wyre_buy_provider.dart b/lib/buy/wyre/wyre_buy_provider.dart index 652c92f58..80e48fd4f 100644 --- a/lib/buy/wyre/wyre_buy_provider.dart +++ b/lib/buy/wyre/wyre_buy_provider.dart @@ -1,5 +1,6 @@ import 'dart:convert'; import 'package:cake_wallet/buy/buy_exception.dart'; +import 'package:flutter/src/widgets/framework.dart'; import 'package:http/http.dart'; import 'package:cake_wallet/buy/buy_amount.dart'; import 'package:cake_wallet/buy/buy_provider.dart'; @@ -35,16 +36,20 @@ class WyreBuyProvider extends BuyProvider { String get title => 'Wyre'; @override - BuyProviderDescription get description => BuyProviderDescription.wyre; + String get buyOptionDescription => ''; @override + String get lightIcon => 'assets/images/robinhood_light.png'; + + @override + String get darkIcon => 'assets/images/robinhood_dark.png'; + String get trackUrl => isTestEnvironment ? _trackTestUrl : _trackProductUrl; String baseApiUrl; - @override Future requestUrl(String amount, String sourceCurrency) async { final timestamp = DateTime.now().millisecondsSinceEpoch.toString(); final url = baseApiUrl + _ordersSuffix + _reserveSuffix + @@ -53,8 +58,8 @@ class WyreBuyProvider extends BuyProvider { final body = { 'amount': amount, 'sourceCurrency': sourceCurrency, - 'destCurrency': walletTypeToCryptoCurrency(walletType).title, - 'dest': walletTypeToString(walletType).toLowerCase() + ':' + walletAddress, + 'destCurrency': walletTypeToCryptoCurrency(wallet.type).title, + 'dest': walletTypeToString(wallet.type).toLowerCase() + ':' + wallet.walletAddresses.address, 'referrerAccountId': _accountId, 'lockFields': ['amount', 'sourceCurrency', 'destCurrency', 'dest'] }; @@ -68,8 +73,8 @@ class WyreBuyProvider extends BuyProvider { if (response.statusCode != 200) { throw BuyException( - description: description, - text: 'Url $url is not found!'); + title: buyOptionDescription, + content: 'Url $url is not found!'); } final responseJSON = json.decode(response.body) as Map; @@ -77,14 +82,13 @@ class WyreBuyProvider extends BuyProvider { return urlFromResponse; } - @override Future calculateAmount(String amount, String sourceCurrency) async { final quoteUrl = _baseProductApiUrl + _ordersSuffix + _quoteSuffix; final body = { 'amount': amount, 'sourceCurrency': sourceCurrency, - 'destCurrency': walletTypeToCryptoCurrency(walletType).title, - 'dest': walletTypeToString(walletType).toLowerCase() + ':' + walletAddress, + 'destCurrency': walletTypeToCryptoCurrency(wallet.type).title, + 'dest': walletTypeToString(wallet.type).toLowerCase() + ':' + wallet.walletAddresses.address, 'accountId': _accountId, 'country': _countryCode }; @@ -99,8 +103,8 @@ class WyreBuyProvider extends BuyProvider { if (response.statusCode != 200) { throw BuyException( - description: description, - text: 'Quote is not found!'); + title: buyOptionDescription, + content: 'Quote is not found!'); } final responseJSON = json.decode(response.body) as Map; @@ -111,7 +115,6 @@ class WyreBuyProvider extends BuyProvider { return BuyAmount(sourceAmount: sourceAmount, destAmount: destAmount, achSourceAmount: achAmount); } - @override Future findOrderById(String id) async { final orderUrl = baseApiUrl + _ordersSuffix + '/$id'; final orderUri = Uri.parse(orderUrl); @@ -119,8 +122,8 @@ class WyreBuyProvider extends BuyProvider { if (orderResponse.statusCode != 200) { throw BuyException( - description: description, - text: 'Order $id is not found!'); + title: buyOptionDescription, + content: 'Order $id is not found!'); } final orderResponseJSON = @@ -141,8 +144,8 @@ class WyreBuyProvider extends BuyProvider { if (transferResponse.statusCode != 200) { throw BuyException( - description: description, - text: 'Transfer $transferId is not found!'); + title: buyOptionDescription, + content: 'Transfer $transferId is not found!'); } final transferResponseJSON = @@ -151,15 +154,25 @@ class WyreBuyProvider extends BuyProvider { return Order( id: id, - provider: description, + provider: BuyProviderDescription.wyre, transferId: transferId, from: from, to: to, state: state, createdAt: createdAt, amount: amount.toString(), - receiveAddress: walletAddress, - walletId: walletId + receiveAddress: wallet.walletAddresses.address, + walletId: wallet.id ); } + + @override + Future launchProvider(BuildContext context, bool? isBuyAction) { + // TODO: implement launchProvider + throw UnimplementedError(); + } + + @override + // TODO: implement sellOptionDescription + String get sellOptionDescription => throw UnimplementedError(); } \ No newline at end of file diff --git a/lib/di.dart b/lib/di.dart index 03b7bfec8..1a0dc5788 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/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'; @@ -796,8 +797,12 @@ Future setup({ getIt .registerFactory(() => DFXBuyProvider(wallet: getIt.get().wallet!)); + getIt.registerFactory(() => MoonPaySellProvider( + settingsStore: getIt.get().settingsStore, + wallet: getIt.get().wallet!)); + getIt.registerFactory(() => OnRamperBuyProvider( - settingsStore: getIt.get().settingsStore, + getIt.get().settingsStore, wallet: getIt.get().wallet!, )); @@ -941,7 +946,8 @@ Future setup({ getIt.registerFactory(() => BuyAmountViewModel()); - getIt.registerFactory(() => BuyOptionsPage(getIt.get())); + getIt.registerFactoryParam( + (isBuyOption, _) => BuySellOptionsPage(getIt.get(), isBuyOption)); getIt.registerFactory(() { final wallet = getIt.get().wallet; diff --git a/lib/entities/buy_provider_types.dart b/lib/entities/buy_provider_types.dart index 1faf2b281..1913d1b25 100644 --- a/lib/entities/buy_provider_types.dart +++ b/lib/entities/buy_provider_types.dart @@ -1,57 +1,112 @@ -import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/buy/buy_provider.dart'; +import 'package:cake_wallet/buy/dfx/dfx_buy_provider.dart'; +import 'package:cake_wallet/buy/onramper/onramper_buy_provider.dart'; +import 'package:cake_wallet/buy/robinhood/robinhood_buy_provider.dart'; +import 'package:cake_wallet/di.dart'; import 'package:cw_core/wallet_type.dart'; enum BuyProviderType { - AskEachTime, - Robinhood, - Onramper, - DFX; + askEachTime, + robinhood, + dfx, + onramper, +} - @override - String toString() { +extension BuyProviderTypeName on BuyProviderType { + String get name { switch (this) { - case BuyProviderType.AskEachTime: - return S.current.ask_each_time; - case BuyProviderType.Robinhood: - return "Robinhood"; - case BuyProviderType.Onramper: - return "Onramper"; - case BuyProviderType.DFX: - return "DFX"; + case BuyProviderType.askEachTime: + return 'Ask each time'; + case BuyProviderType.robinhood: + return 'Robinhood Connect'; + case BuyProviderType.dfx: + return 'DFX Connect'; + case BuyProviderType.onramper: + return 'Onramper'; + default: + return this.toString().split('.').last; } } - static List getAvailableProviders(WalletType walletType) { + String get id { + switch (this) { + case BuyProviderType.askEachTime: + return 'ask_each_time_provider'; + case BuyProviderType.robinhood: + return 'robinhood_connect_provider'; + case BuyProviderType.dfx: + return 'dfx_connect_provider'; + case BuyProviderType.onramper: + return 'onramper_provider'; + default: + return this.toString().split('.').last.replaceAll('.', '_') + .toLowerCase() + '_provider'; + } + } +} + +class BuyProviderHelper { + static List getAvailableBuyProviderTypes( + WalletType walletType) { switch (walletType) { case WalletType.nano: case WalletType.banano: - return [ - BuyProviderType.AskEachTime, - BuyProviderType.Onramper - ]; + return [BuyProviderType.askEachTime, BuyProviderType.onramper]; case WalletType.monero: return [ - BuyProviderType.AskEachTime, - BuyProviderType.Onramper, - BuyProviderType.DFX + BuyProviderType.askEachTime, + BuyProviderType.onramper, + BuyProviderType.dfx ]; case WalletType.bitcoin: case WalletType.ethereum: return [ - BuyProviderType.AskEachTime, - BuyProviderType.Onramper, - BuyProviderType.DFX, - BuyProviderType.Robinhood + BuyProviderType.askEachTime, + BuyProviderType.onramper, + BuyProviderType.dfx, + BuyProviderType.robinhood ]; case WalletType.litecoin: case WalletType.bitcoinCash: - return [ - BuyProviderType.AskEachTime, - BuyProviderType.Onramper, - BuyProviderType.Robinhood - ]; + return [ + BuyProviderType.askEachTime, + BuyProviderType.onramper, + BuyProviderType.robinhood + ]; default: return []; } } + + static List getAvailableSellProviderTypes( + WalletType walletType) { + switch (walletType) { + case WalletType.nano: + case WalletType.banano: + return [BuyProviderType.askEachTime]; + case WalletType.monero: + return [BuyProviderType.askEachTime, BuyProviderType.dfx]; + case WalletType.bitcoin: + case WalletType.ethereum: + return [BuyProviderType.askEachTime, BuyProviderType.dfx]; + case WalletType.litecoin: + case WalletType.bitcoinCash: + return [BuyProviderType.askEachTime]; + default: + return []; + } + } + + static BuyProvider? getProviderByType(BuyProviderType type) { + switch (type) { + case BuyProviderType.robinhood: + return getIt.get(); + case BuyProviderType.dfx: + return getIt.get(); + case BuyProviderType.onramper: + return getIt.get(); + case BuyProviderType.askEachTime: + return null; + } + } } diff --git a/lib/entities/main_actions.dart b/lib/entities/main_actions.dart index 4c629e768..0e4e05ae0 100644 --- a/lib/entities/main_actions.dart +++ b/lib/entities/main_actions.dart @@ -1,18 +1,9 @@ -import 'package:cake_wallet/buy/dfx/dfx_buy_provider.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/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/generated/i18n.dart'; import 'package:cake_wallet/routes.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/dashboard/dashboard_view_model.dart'; -import 'package:cw_core/wallet_type.dart'; import 'package:flutter/material.dart'; -import 'package:url_launcher/url_launcher.dart'; class MainActions { final String Function(BuildContext context) name; @@ -46,53 +37,22 @@ class MainActions { canShow: (viewModel) => viewModel.hasBuyAction, onTap: (BuildContext context, DashboardViewModel viewModel) async { if (!viewModel.isEnabledBuyAction) { - await _showErrorDialog(context, S.of(context).unsupported_asset); + await _showErrorDialog( + context, S.of(context).buy, S.of(context).unsupported_asset); return; } final defaultBuyProvider = viewModel.defaultBuyProvider; try { - await _launchProviderByType(context, defaultBuyProvider); + defaultBuyProvider != null + ? await defaultBuyProvider.launchProvider(context, true) + : await Navigator.of(context).pushNamed(Routes.buySellPage, arguments: true); } catch (e) { - await _showErrorDialog(context, e.toString()); + await _showErrorDialog(context, defaultBuyProvider.toString(), e.toString()); } }, ); - static Future _launchProviderByType(BuildContext context, BuyProviderType providerType) async { - switch (providerType) { - case BuyProviderType.AskEachTime: - Navigator.pushNamed(context, Routes.buy); - break; - case BuyProviderType.Onramper: - await getIt.get().launchProvider(context); - break; - case BuyProviderType.Robinhood: - await getIt.get().launchProvider(context); - break; - case BuyProviderType.DFX: - await getIt.get().launchProvider(context); - break; - default: - throw UnsupportedError('Unsupported buy provider type'); - } - } - - - static Future _showErrorDialog(BuildContext context, String errorMessage) async { - await showPopUp( - context: context, - builder: (BuildContext context) { - return AlertWithOneAction( - alertTitle: S.of(context).buy, - alertContent: errorMessage, - buttonText: S.of(context).ok, - buttonAction: () => Navigator.of(context).pop(), - ); - }, - ); - } - static MainActions receiveAction = MainActions._( name: (context) => S.of(context).receive, image: 'assets/images/received.png', @@ -127,42 +87,35 @@ class MainActions { isEnabled: (viewModel) => viewModel.isEnabledSellAction, canShow: (viewModel) => viewModel.hasSellAction, onTap: (BuildContext context, DashboardViewModel viewModel) async { - final walletType = viewModel.type; + if (!viewModel.isEnabledSellAction) { + await _showErrorDialog( + context, S.of(context).sell, S.of(context).unsupported_asset); + return; + } - switch (walletType) { - case WalletType.bitcoin: - case WalletType.litecoin: - case WalletType.ethereum: - case WalletType.polygon: - case WalletType.bitcoinCash: - if (viewModel.isEnabledSellAction) { - final moonPaySellProvider = MoonPaySellProvider(); - final uri = await moonPaySellProvider.requestUrl( - currency: viewModel.wallet.currency, - refundWalletAddress: viewModel.wallet.walletAddresses.address, - settingsStore: viewModel.settingsStore, - ); - if (DeviceInfo.instance.isMobile) { - Navigator.of(context).pushNamed(Routes.webViewPage, - arguments: [S.of(context).sell, uri]); - } else { - await launchUrl(uri); - } - } - - break; - default: - await showPopUp( - context: context, - builder: (BuildContext context) { - return AlertWithOneAction( - alertTitle: S.of(context).sell, - alertContent: S.of(context).unsupported_asset, - buttonText: S.of(context).ok, - buttonAction: () => Navigator.of(context).pop()); - }, - ); + final defaultSellProvider = viewModel.defaultSellProvider; + try { + defaultSellProvider != null + ? await defaultSellProvider.launchProvider(context, false) + : await Navigator.of(context).pushNamed(Routes.buySellPage, arguments: false); + } catch (e) { + await _showErrorDialog(context, defaultSellProvider.toString(), e.toString()); } }, ); -} + + static Future _showErrorDialog( + BuildContext context, String title, String errorMessage) async { + await showPopUp( + context: context, + builder: (BuildContext context) { + return AlertWithOneAction( + alertTitle: title, + alertContent: errorMessage, + buttonText: S.of(context).ok, + buttonAction: () => Navigator.of(context).pop(), + ); + }, + ); + } +} \ No newline at end of file diff --git a/lib/router.dart b/lib/router.dart index 11eeb582b..f9ab17d37 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -390,8 +390,10 @@ Route createRoute(RouteSettings settings) { return MaterialPageRoute( builder: (_) => getIt.get(param1: settings.arguments as Order)); - case Routes.buy: - return MaterialPageRoute(builder: (_) => getIt.get()); + case Routes.buySellPage: + final args = settings.arguments as bool; + return MaterialPageRoute( + builder: (_) => getIt.get(param1: args)); case Routes.buyWebView: final args = settings.arguments as List; diff --git a/lib/routes.dart b/lib/routes.dart index 60801297a..59d4acbc9 100644 --- a/lib/routes.dart +++ b/lib/routes.dart @@ -55,7 +55,7 @@ class Routes { static const supportLiveChat = '/support/live_chat'; static const supportOtherLinks = '/support/other'; static const orderDetails = '/order_details'; - static const buy = '/buy'; + static const buySellPage = '/buy_sell_page'; static const buyWebView = '/buy_web_view'; static const unspentCoinsList = '/unspent_coins_list'; static const unspentCoinsDetails = '/unspent_coins_details'; diff --git a/lib/src/screens/buy/buy_options_page.dart b/lib/src/screens/buy/buy_options_page.dart index 62dfc4ec4..6ce8fe9f2 100644 --- a/lib/src/screens/buy/buy_options_page.dart +++ b/lib/src/screens/buy/buy_options_page.dart @@ -1,7 +1,4 @@ -import 'package:cake_wallet/buy/dfx/dfx_buy_provider.dart'; -import 'package:cake_wallet/buy/onramper/onramper_buy_provider.dart'; -import 'package:cake_wallet/buy/robinhood/robinhood_buy_provider.dart'; -import 'package:cake_wallet/di.dart'; +import 'package:cake_wallet/buy/buy_provider.dart'; import 'package:cake_wallet/entities/buy_provider_types.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/src/screens/base_page.dart'; @@ -11,19 +8,14 @@ import 'package:cake_wallet/themes/extensions/transaction_trade_theme.dart'; import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart'; import 'package:flutter/material.dart'; -class BuyOptionsPage extends BasePage { - BuyOptionsPage(this.dashboardViewModel); +class BuySellOptionsPage extends BasePage { + BuySellOptionsPage(this.dashboardViewModel, this.isBuyAction); final DashboardViewModel dashboardViewModel; - 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 iconDarkDFX = 'assets/images/dfx_dark.png'; - final iconLightDFX = 'assets/images/dfx_light.png'; + final bool isBuyAction; @override - String get title => S.current.buy; + String get title => isBuyAction ? S.current.buy : S.current.sell; @override AppBarStyle get appBarStyle => AppBarStyle.regular; @@ -32,17 +24,9 @@ class BuyOptionsPage extends BasePage { Widget body(BuildContext context) { final isLightMode = Theme.of(context).extension()?.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 iconDFX = Image.asset(isLightMode ? iconLightDFX : iconDarkDFX, - height: 40, width: 40); - final availableProviders = dashboardViewModel.availableProviders; + final availableProviders = isBuyAction + ? dashboardViewModel.availableBuyProviders + : dashboardViewModel.availableSellProviders; return Container( child: Center( @@ -50,57 +34,43 @@ class BuyOptionsPage extends BasePage { constraints: BoxConstraints(maxWidth: 330), child: Column( children: [ - if (availableProviders.contains(BuyProviderType.Onramper)) - Padding( + ...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: iconOnramper, - title: "Onramper", - description: S.of(context).onramper_option_description, - onPressed: () async => await getIt - .get() - .launchProvider(context), + image: icon, + title: provider.toString(), + description: isBuyAction + ? provider.buyOptionDescription + : provider.sellOptionDescription, + onPressed: () => + provider.launchProvider(context, isBuyAction), ), - ), - if (availableProviders.contains(BuyProviderType.Robinhood)) - Padding( - padding: EdgeInsets.only(top: 24), - child: OptionTile( - image: iconRobinhood, - title: "Robinhood Connect", - description: S.of(context).robinhood_option_description, - onPressed: () async => await getIt - .get() - .launchProvider(context), - ), - ), - if (availableProviders.contains(BuyProviderType.DFX)) - Padding( - padding: EdgeInsets.only(top: 24), - child: OptionTile( - image: iconDFX, - title: "DFX Connect", - description: S.of(context).dfx_option_description, - onPressed: () async => await getIt - .get() - .launchProvider(context), - ), - ), + ); + }).toList(), Spacer(), - Padding( - padding: EdgeInsets.fromLTRB(24, 24, 24, 32), - child: Text( - S.of(context).select_buy_provider_notice, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.normal, - color: Theme.of(context) - .extension()! - .detailsTitlesColor, + 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/widgets/buy_list_item.dart b/lib/src/screens/buy/widgets/buy_list_item.dart index fc8fea81d..d8adddfdc 100644 --- a/lib/src/screens/buy/widgets/buy_list_item.dart +++ b/lib/src/screens/buy/widgets/buy_list_item.dart @@ -29,11 +29,10 @@ class BuyListItem extends StatelessWidget { @override Widget build(BuildContext context) { - final isSelected = selectedProvider?.description == provider.description; + final isSelected = selectedProvider?.buyOptionDescription == provider.buyOptionDescription; final iconColor = isSelected ? Colors.white : Colors.black; - final providerIcon = getBuyProviderIcon(provider.description, - iconColor: iconColor)!; + final providerIcon = Image.asset('assets/images/wyre-icon.png', width: 36, height: 36); final backgroundColor = isSelected ? Palette.greyBlueCraiola @@ -76,7 +75,7 @@ class BuyListItem extends StatelessWidget { padding: EdgeInsets.only(right: 10), child: providerIcon), Text( - provider.description.title, + provider.title, style: TextStyle( color: secondaryTextColor, fontSize: 20, diff --git a/lib/src/screens/settings/other_settings_page.dart b/lib/src/screens/settings/other_settings_page.dart index cc92641f3..c58523185 100644 --- a/lib/src/screens/settings/other_settings_page.dart +++ b/lib/src/screens/settings/other_settings_page.dart @@ -43,10 +43,18 @@ class OtherSettingsPage extends BasePage { if(_otherSettingsViewModel.isEnabledBuyAction) SettingsPickerCell( title: S.current.default_buy_provider, - items: _otherSettingsViewModel.availableBuyProviders, + items: _otherSettingsViewModel.availableBuyProvidersTypes, displayItem: _otherSettingsViewModel.getBuyProviderType, selectedItem: _otherSettingsViewModel.buyProviderType, - onItemSelected: _otherSettingsViewModel.onBuyProviderTypeSelected, + onItemSelected: _otherSettingsViewModel.onBuyProviderTypeSelected + ), + if(_otherSettingsViewModel.isEnabledSellAction) + SettingsPickerCell( + title: S.current.default_sell_provider, + items: _otherSettingsViewModel.availableSellProvidersTypes, + displayItem: _otherSettingsViewModel.getSellProviderType, + selectedItem: _otherSettingsViewModel.sellProviderType, + onItemSelected: _otherSettingsViewModel.onSellProviderTypeSelected, ), SettingsCellWithArrow( title: S.current.settings_terms_and_conditions, diff --git a/lib/store/settings_store.dart b/lib/store/settings_store.dart index 95254e557..20f310c83 100644 --- a/lib/store/settings_store.dart +++ b/lib/store/settings_store.dart @@ -2,6 +2,7 @@ import 'dart:io'; import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart'; +import 'package:cake_wallet/buy/buy_provider.dart'; import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart'; import 'package:cake_wallet/entities/buy_provider_types.dart'; import 'package:cake_wallet/entities/cake_2fa_preset_options.dart'; @@ -147,7 +148,9 @@ abstract class SettingsStoreBase with Store { initialShouldRequireTOTP2FAForAllSecurityAndBackupSettings, currentSyncMode = initialSyncMode, currentSyncAll = initialSyncAll, - priority = ObservableMap() { + priority = ObservableMap(), + defaultBuyProviders = ObservableMap(), + defaultSellProviders = ObservableMap() { //this.nodes = ObservableMap.of(nodes); if (initialMoneroTransactionPriority != null) { @@ -181,9 +184,25 @@ abstract class SettingsStoreBase with Store { initializeTrocadorProviderStates(); WalletType.values.forEach((walletType) { - final key = 'defaultBuyProvider_${walletType.toString()}'; - final providerIndex = sharedPreferences.getInt(key); - defaultBuyProviders[walletType] = providerIndex != null ? BuyProviderType.values[providerIndex] : BuyProviderType.AskEachTime; + final key = 'buyProvider_${walletType.toString()}'; + final providerId = sharedPreferences.getString(key); + if (providerId != null) { + defaultBuyProviders[walletType] = BuyProviderType.values + .firstWhere((provider) => provider.id == providerId, orElse: () => BuyProviderType.askEachTime); + } else { + defaultBuyProviders[walletType] = BuyProviderType.askEachTime; + } + }); + + WalletType.values.forEach((walletType) { + final key = 'sellProvider_${walletType.toString()}'; + final providerId = sharedPreferences.getString(key); + if (providerId != null) { + defaultSellProviders[walletType] = BuyProviderType.values + .firstWhere((provider) => provider.id == providerId, orElse: () => BuyProviderType.askEachTime); + } else { + defaultSellProviders[walletType] = BuyProviderType.askEachTime; + } }); reaction( @@ -196,6 +215,20 @@ abstract class SettingsStoreBase with Store { (bool shouldShowYatPopup) => sharedPreferences.setBool(PreferencesKey.shouldShowYatPopup, shouldShowYatPopup)); + defaultBuyProviders.observe((change) { + final String key = 'buyProvider_${change.key.toString()}'; + if (change.newValue != null) { + sharedPreferences.setString(key, change.newValue!.id); + } + }); + + defaultSellProviders.observe((change) { + final String key = 'sellProvider_${change.key.toString()}'; + if (change.newValue != null) { + sharedPreferences.setString(key, change.newValue!.id); + } + }); + priority.observe((change) { final String? key; switch (change.key) { @@ -251,16 +284,6 @@ abstract class SettingsStoreBase with Store { (bool disableSell) => sharedPreferences.setBool(PreferencesKey.disableSellKey, disableSell)); - reaction( - (_) => defaultBuyProviders.asObservable(), - (ObservableMap providers) { - providers.forEach((walletType, provider) { - final key = 'defaultBuyProvider_${walletType.toString()}'; - sharedPreferences.setInt(key, provider.index); - }); - } - ); - reaction( (_) => walletListOrder, (WalletListOrderType walletListOrder) => @@ -594,7 +617,10 @@ abstract class SettingsStoreBase with Store { ObservableMap trocadorProviderStates = ObservableMap(); @observable - ObservableMap defaultBuyProviders = ObservableMap(); + ObservableMap defaultBuyProviders; + + @observable + ObservableMap defaultSellProviders; @observable SortBalanceBy sortBalanceBy; diff --git a/lib/view_model/buy/buy_view_model.dart b/lib/view_model/buy/buy_view_model.dart index 080caa3ad..c0e22dae2 100644 --- a/lib/view_model/buy/buy_view_model.dart +++ b/lib/view_model/buy/buy_view_model.dart @@ -61,8 +61,7 @@ abstract class BuyViewModelBase with Store { String _url = ''; try { - _url = await selectedProvider - !.requestUrl(doubleAmount.toString(), fiatCurrency.title); + _url = await selectedProvider!.requestUrl(doubleAmount.toString(), fiatCurrency.title); } catch (e) { print(e.toString()); } diff --git a/lib/view_model/dashboard/dashboard_view_model.dart b/lib/view_model/dashboard/dashboard_view_model.dart index 19b5ad544..f4c66b432 100644 --- a/lib/view_model/dashboard/dashboard_view_model.dart +++ b/lib/view_model/dashboard/dashboard_view_model.dart @@ -1,5 +1,6 @@ import 'dart:convert'; +import 'package:cake_wallet/buy/buy_provider.dart'; import 'package:cake_wallet/core/key_service.dart'; import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart'; import 'package:cake_wallet/entities/balance_display_mode.dart'; @@ -41,6 +42,7 @@ import 'package:cw_core/wallet_type.dart'; import 'package:eth_sig_util/util/utils.dart'; import 'package:flutter/services.dart'; import 'package:mobx/mobx.dart'; +import 'package:cake_wallet/entities/buy_provider_types.dart'; part 'dashboard_view_model.g.dart'; @@ -295,13 +297,31 @@ abstract class DashboardViewModelBase with Store { Map> filterItems; - BuyProviderType get defaultBuyProvider => - settingsStore.defaultBuyProviders[wallet.type] ?? BuyProviderType.AskEachTime; + BuyProvider? get defaultBuyProvider => BuyProviderHelper.getProviderByType( + settingsStore.defaultBuyProviders[wallet.type] ?? BuyProviderType.askEachTime); + + BuyProvider? get defaultSellProvider => BuyProviderHelper.getProviderByType( + settingsStore.defaultSellProviders[wallet.type] ?? BuyProviderType.askEachTime); bool get isBuyEnabled => settingsStore.isBitcoinBuyEnabled; - List get availableProviders => - BuyProviderType.getAvailableProviders(wallet.type); + List get availableBuyProviders { + final providerTypes = BuyProviderHelper.getAvailableBuyProviderTypes(wallet.type); + return providerTypes + .map((type) => BuyProviderHelper.getProviderByType(type)) + .where((provider) => provider != null) + .cast() + .toList(); + } + + List get availableSellProviders { + final providerTypes = BuyProviderHelper.getAvailableSellProviderTypes(wallet.type); + return providerTypes + .map((type) => BuyProviderHelper.getProviderByType(type)) + .where((provider) => provider != null) + .cast() + .toList(); + } bool get shouldShowYatPopup => settingsStore.shouldShowYatPopup; @@ -315,16 +335,15 @@ abstract class DashboardViewModelBase with Store { bool hasExchangeAction; @computed - bool get isEnabledBuyAction => !settingsStore.disableBuy && wallet.type != WalletType.haven; + bool get isEnabledBuyAction => + !settingsStore.disableBuy && availableBuyProviders.isNotEmpty; @observable bool hasBuyAction; @computed bool get isEnabledSellAction => - !settingsStore.disableSell && - wallet.type != WalletType.haven && - wallet.type != WalletType.monero; + !settingsStore.disableSell && availableSellProviders.isNotEmpty; @observable bool hasSellAction; diff --git a/lib/view_model/order_details_view_model.dart b/lib/view_model/order_details_view_model.dart index 0047ed7b2..4ac848ecf 100644 --- a/lib/view_model/order_details_view_model.dart +++ b/lib/view_model/order_details_view_model.dart @@ -50,8 +50,10 @@ abstract class OrderDetailsViewModelBase with Store { @action Future _updateOrder() async { try { - if (_provider != null) { - final updatedOrder = await _provider!.findOrderById(order.id); + if (_provider != null && (_provider is MoonPayBuyProvider || _provider is WyreBuyProvider)) { + final updatedOrder = _provider is MoonPayBuyProvider + ? await (_provider as MoonPayBuyProvider).findOrderById(order.id) + : await (_provider as WyreBuyProvider).findOrderById(order.id); updatedOrder.from = order.from; updatedOrder.to = order.to; updatedOrder.receiveAddress = order.receiveAddress; @@ -87,19 +89,26 @@ abstract class OrderDetailsViewModelBase with Store { value: order.provider.title) ); - if (_provider?.trackUrl.isNotEmpty ?? false) { - final buildURL = _provider!.trackUrl + '${order.transferId}'; - items.add( - TrackTradeListItem( - title: 'Track', - value: buildURL, - onTap: () { - try { - launch(buildURL); - } catch (e) {} - } - ) - ); + if (_provider != null && (_provider is MoonPayBuyProvider || _provider is WyreBuyProvider)) { + + final trackUrl = _provider is MoonPayBuyProvider + ? (_provider as MoonPayBuyProvider).trackUrl + : (_provider as WyreBuyProvider).trackUrl; + + if (trackUrl.isNotEmpty ?? false) { + final buildURL = trackUrl + '${order.transferId}'; + items.add( + TrackTradeListItem( + title: 'Track', + value: buildURL, + onTap: () { + try { + launch(buildURL); + } catch (e) {} + } + ) + ); + } } items.add( diff --git a/lib/view_model/settings/other_settings_view_model.dart b/lib/view_model/settings/other_settings_view_model.dart index 4e712dae1..60ed550ef 100644 --- a/lib/view_model/settings/other_settings_view_model.dart +++ b/lib/view_model/settings/other_settings_view_model.dart @@ -1,6 +1,7 @@ import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/entities/buy_provider_types.dart'; import 'package:cake_wallet/entities/priority_for_wallet_type.dart'; +import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/store/settings_store.dart'; import 'package:cw_core/balance.dart'; import 'package:cw_core/transaction_history.dart'; @@ -59,12 +60,24 @@ abstract class OtherSettingsViewModelBase with Store { bool get isEnabledBuyAction => !_settingsStore.disableBuy && _wallet.type != WalletType.haven; - List get availableBuyProviders => - BuyProviderType.getAvailableProviders(walletType); + @computed + bool get isEnabledSellAction => + !_settingsStore.disableSell && _wallet.type != WalletType.haven; + + List get availableBuyProvidersTypes { + return BuyProviderHelper.getAvailableBuyProviderTypes(walletType); + } + + List get availableSellProvidersTypes => + BuyProviderHelper.getAvailableSellProviderTypes(walletType); BuyProviderType get buyProviderType => _settingsStore.defaultBuyProviders[walletType] ?? - BuyProviderType.AskEachTime; + BuyProviderType.askEachTime; + + BuyProviderType get sellProviderType => + _settingsStore.defaultSellProviders[walletType] ?? + BuyProviderType.askEachTime; String getDisplayPriority(dynamic priority) { final _priority = priority as TransactionPriority; @@ -81,13 +94,27 @@ abstract class OtherSettingsViewModelBase with Store { String getBuyProviderType(dynamic buyProviderType) { final _buyProviderType = buyProviderType as BuyProviderType; + return _buyProviderType == BuyProviderType.askEachTime + ? S.current.ask_each_time + : _buyProviderType.name; + } - return _buyProviderType.toString(); + String getSellProviderType(dynamic sellProviderType) { + final _sellProviderType = sellProviderType as BuyProviderType; + return _sellProviderType == BuyProviderType.askEachTime + ? S.current.ask_each_time + : _sellProviderType.name; } void onDisplayPrioritySelected(TransactionPriority priority) => _settingsStore.priority[_wallet.type] = priority; - void onBuyProviderTypeSelected(BuyProviderType buyProviderType) => + @action + BuyProviderType onBuyProviderTypeSelected(BuyProviderType buyProviderType) => _settingsStore.defaultBuyProviders[walletType] = buyProviderType; + + @action + BuyProviderType onSellProviderTypeSelected( + BuyProviderType sellProviderType) => + _settingsStore.defaultSellProviders[walletType] = sellProviderType; } diff --git a/res/values/strings_ar.arb b/res/values/strings_ar.arb index 251afa409..ba2f7731b 100644 --- a/res/values/strings_ar.arb +++ b/res/values/strings_ar.arb @@ -754,6 +754,8 @@ "dfx_option_description": "ﺎﺑﻭﺭﻭﺃ ﻲﻓ ﺕﺎﻛﺮﺸﻟﺍﻭ ﺔﺋﺰﺠﺘﻟﺍ ءﻼﻤﻌﻟ .ﻲﻓﺎﺿﺇ KYC ﻥﻭﺪﺑ ﻭﺭﻮﻳ 990 ﻰﻟﺇ ﻞﺼﻳ ﺎﻣ .ﻱﺮﺴﻳﻮﺴﻟﺍ", "polygonscan_history": "ﻥﺎﻜﺴﻧﻮﺠﻴﻟﻮﺑ ﺦﻳﺭﺎﺗ", "wallet_seed_legacy": "بذرة محفظة قديمة", + "default_sell_provider": " ﻲﺿﺍﺮﺘﻓﻻﺍ ﻊﻴﺒﻟﺍ ﺩﻭﺰﻣ", + "select_sell_provider_notice": ".ﻖﻴﺒﻄﺘﻟﺍ ﺕﺍﺩﺍﺪﻋﺇ ﻲﻓ ﻚﺑ ﺹﺎﺨﻟﺍ ﻲﺿﺍﺮﺘﻓﻻﺍ ﻊﻴﺒﻟﺍ ﺩﻭﺰﻣ ﻦﻴﻴﻌﺗ ﻖﻳﺮﻃ ﻦﻋ ﺔﺷﺎﺸﻟﺍ ﻩﺬﻫ ﻲﻄﺨﺗ", "custom_drag": "مخصص (عقد وسحب)", "switchToEVMCompatibleWallet": " (Ethereum، Polygon) ﻯﺮﺧﺃ ﺓﺮﻣ ﺔﻟﻭﺎﺤﻤﻟﺍﻭ EVM ﻊﻣ ﺔﻘﻓﺍﻮﺘﻣ ﺔﻈﻔﺤﻣ ﻰﻟﺇ ﻞﻳﺪﺒﺘﻟﺍ ﻰﺟﺮﻳ" } diff --git a/res/values/strings_bg.arb b/res/values/strings_bg.arb index ae83da481..d53df2d0e 100644 --- a/res/values/strings_bg.arb +++ b/res/values/strings_bg.arb @@ -750,6 +750,8 @@ "dfx_option_description": "Купете крипто с EUR и CHF. До 990 € без допълнителен KYC. За клиенти на дребно и корпоративни клиенти в Европа", "polygonscan_history": "История на PolygonScan", "wallet_seed_legacy": "Наследено портфейл семе", + "default_sell_provider": "Доставчик за продажба по подразбиране", + "select_sell_provider_notice": "Изберете доставчик на продажба по-горе. Можете да пропуснете този екран, като зададете своя доставчик на продажба по подразбиране в настройките на приложението.", "custom_drag": "Персонализиране (задръжте и плъзнете)", "switchToEVMCompatibleWallet": "Моля, превключете към портфейл, съвместим с EVM, и опитайте отново (Ethereum, Polygon)" } diff --git a/res/values/strings_cs.arb b/res/values/strings_cs.arb index da22f9f6b..4d29f32a0 100644 --- a/res/values/strings_cs.arb +++ b/res/values/strings_cs.arb @@ -750,6 +750,8 @@ "dfx_option_description": "Nakupujte kryptoměny za EUR a CHF. Až 990 € bez dalších KYC. Pro maloobchodní a firemní zákazníky v Evropě", "polygonscan_history": "Historie PolygonScan", "wallet_seed_legacy": "Starší semeno peněženky", + "default_sell_provider": "Výchozí poskytovatel prodeje", + "select_sell_provider_notice": "Výše vyberte poskytovatele prodeje. Tuto obrazovku můžete přeskočit nastavením výchozího poskytovatele prodeje v nastavení aplikace.", "custom_drag": "Custom (Hold and Drag)", "switchToEVMCompatibleWallet": "Přepněte na peněženku kompatibilní s EVM a zkuste to znovu (Ethereum, Polygon)" } diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index 69b7e47d2..e8f03a23b 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -758,6 +758,8 @@ "dfx_option_description": "Krypto mit EUR und CHF kaufen. Bis zu 990€ ohne zusätzliches KYC. Für Privat- und Firmenkunden in Europa", "polygonscan_history": "PolygonScan-Verlauf", "wallet_seed_legacy": "Legacy Wallet Seed", + "default_sell_provider": "Standard-Verkaufsanbieter", + "select_sell_provider_notice": "Wählen Sie oben einen Verkaufsanbieter aus. Sie können diesen Bildschirm überspringen, indem Sie in den App-Einstellungen Ihren Standard-Verkaufsanbieter festlegen.", "custom_drag": "Custom (Hold and Drag)", "switchToEVMCompatibleWallet": "Bitte wechseln Sie zu einem EVM-kompatiblen Wallet und versuchen Sie es erneut (Ethereum, Polygon)" } diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index e1a7fdebc..3f7cbdad2 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -759,6 +759,8 @@ "dfx_option_description": "Buy crypto with EUR & CHF. Up to 990€ without additional KYC. For retail and corporate customers in Europe", "polygonscan_history": "PolygonScan history", "wallet_seed_legacy": "Legacy wallet seed", + "default_sell_provider": "Default Sell Provider", + "select_sell_provider_notice": "Select a sell provider above. You can skip this screen by setting your default sell provider in app settings.", "custom_drag": "Custom (Hold and Drag)", "switchToEVMCompatibleWallet": "Please switch to an EVM compatible wallet and try again (Ethereum, Polygon)" } diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index 21de24163..09bbe985e 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -758,6 +758,8 @@ "dfx_option_description": "Compre criptomonedas con EUR y CHF. Hasta 990€ sin KYC adicional. Para clientes minoristas y corporativos en Europa", "polygonscan_history": "Historial de PolygonScan", "wallet_seed_legacy": "Semilla de billetera heredada", + "default_sell_provider": "Proveedor de venta predeterminado", + "select_sell_provider_notice": "Seleccione un proveedor de venta arriba. Puede omitir esta pantalla configurando su proveedor de venta predeterminado en la configuración de la aplicación.", "custom_drag": "Custom (mantenía y arrastre)", "switchToEVMCompatibleWallet": "Cambie a una billetera compatible con EVM e inténtelo nuevamente (Ethereum, Polygon)" } diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index 2d3895351..7af139a8f 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -758,6 +758,8 @@ "dfx_option_description": "Achetez des crypto-monnaies avec EUR et CHF. Jusqu'à 990€ sans KYC supplémentaire. Pour les clients particuliers et entreprises en Europe", "polygonscan_history": "Historique de PolygonScan", "wallet_seed_legacy": "Graine de portefeuille hérité", + "default_sell_provider": "Fournisseur de vente par défaut", + "select_sell_provider_notice": "Sélectionnez un fournisseur de vente ci-dessus. Vous pouvez ignorer cet écran en définissant votre fournisseur de vente par défaut dans les paramètres de l'application.", "custom_drag": "Custom (maintenir et traîner)", "switchToEVMCompatibleWallet": "Veuillez passer à un portefeuille compatible EVM et réessayer (Ethereum, Polygon)" } diff --git a/res/values/strings_ha.arb b/res/values/strings_ha.arb index 69e118f49..ec820f01f 100644 --- a/res/values/strings_ha.arb +++ b/res/values/strings_ha.arb @@ -736,6 +736,8 @@ "dfx_option_description": "Sayi crypto tare da EUR & CHF. Har zuwa € 990 ba tare da ƙarin KYC ba. Don 'yan kasuwa da abokan ciniki na kamfanoni a Turai", "polygonscan_history": "PolygonScan tarihin kowane zamani", "wallet_seed_legacy": "Tallarin walat walat", + "default_sell_provider": "Tsohuwar Mai Bayar Siyarwa", + "select_sell_provider_notice": "Zaɓi mai bada siyarwa a sama. Kuna iya tsallake wannan allon ta saita mai bada siyar da ku a cikin saitunan app.", "custom_drag": "Al'ada (riƙe da ja)", "switchToEVMCompatibleWallet": "Da fatan za a canza zuwa walat ɗin EVM mai jituwa kuma a sake gwadawa (Ethereum, Polygon)" } diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index 5774203db..da83e8cc7 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -758,6 +758,8 @@ "dfx_option_description": "EUR और CHF के साथ क्रिप्टो खरीदें। अतिरिक्त केवाईसी के बिना 990€ तक। यूरोप में खुदरा और कॉर्पोरेट ग्राहकों के लिए", "polygonscan_history": "पॉलीगॉनस्कैन इतिहास", "wallet_seed_legacy": "विरासत बटुए बीज", + "default_sell_provider": "डिफ़ॉल्ट विक्रय प्रदाता", + "select_sell_provider_notice": "ऊपर एक विक्रय प्रदाता का चयन करें। आप ऐप सेटिंग में अपना डिफ़ॉल्ट विक्रय प्रदाता सेट करके इस स्क्रीन को छोड़ सकते हैं।", "custom_drag": "कस्टम (पकड़ और खींचें)", "switchToEVMCompatibleWallet": "कृपया ईवीएम संगत वॉलेट पर स्विच करें और पुनः प्रयास करें (एथेरियम, पॉलीगॉन)" } diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index 5fde4d6de..71b767bdc 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -756,6 +756,8 @@ "dfx_option_description": "Kupujte kripto s EUR i CHF. Do 990 € bez dodatnog KYC-a. Za maloprodajne i poslovne korisnike u Europi", "polygonscan_history": "Povijest PolygonScan", "wallet_seed_legacy": "Sjeme naslijeđenog novčanika", + "default_sell_provider": "Zadani dobavljač prodaje", + "select_sell_provider_notice": "Gore odaberite pružatelja usluga prodaje. Ovaj zaslon možete preskočiti postavljanjem zadanog pružatelja usluga prodaje u postavkama aplikacije.", "custom_drag": "Prilagođeni (držite i povucite)", "switchToEVMCompatibleWallet": "Prijeđite na novčanik kompatibilan s EVM-om i pokušajte ponovno (Ethereum, Polygon)" } diff --git a/res/values/strings_id.arb b/res/values/strings_id.arb index 208569b31..2398eccd1 100644 --- a/res/values/strings_id.arb +++ b/res/values/strings_id.arb @@ -746,6 +746,8 @@ "dfx_option_description": "Beli kripto dengan EUR & CHF. Hingga 990€ tanpa KYC tambahan. Untuk pelanggan ritel dan korporat di Eropa", "polygonscan_history": "Sejarah PolygonScan", "wallet_seed_legacy": "Biji dompet warisan", + "default_sell_provider": "Penyedia Penjualan Default", + "select_sell_provider_notice": "Pilih penyedia jual di atas. Anda dapat melewati layar ini dengan mengatur penyedia penjualan default Anda di pengaturan aplikasi.", "custom_drag": "Khusus (tahan dan seret)", "switchToEVMCompatibleWallet": "Silakan beralih ke dompet yang kompatibel dengan EVM dan coba lagi (Ethereum, Polygon)" } diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index 51beaf576..e8b07c25f 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -758,6 +758,8 @@ "dfx_option_description": "Acquista criptovalute con EUR e CHF. Fino a 990€ senza KYC aggiuntivi. Per clienti al dettaglio e aziendali in Europa", "polygonscan_history": "Cronologia PolygonScan", "wallet_seed_legacy": "Seme di portafoglio legacy", + "default_sell_provider": "Fornitore di vendita predefinito", + "select_sell_provider_notice": "Seleziona un fornitore di vendita sopra. Puoi saltare questa schermata impostando il tuo fornitore di vendita predefinito nelle impostazioni dell'app.", "custom_drag": "Custom (Hold and Drag)", "switchToEVMCompatibleWallet": "Passa a un portafoglio compatibile con EVM e riprova (Ethereum, Polygon)" } diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index ca197adae..94d43cbfb 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -758,6 +758,8 @@ "dfx_option_description": "EUR と CHF で暗号通貨を購入します。追加のKYCなしで最大990ユーロ。ヨーロッパの小売および法人顧客向け", "polygonscan_history": "ポリゴンスキャン履歴", "wallet_seed_legacy": "レガシーウォレットシード", + "default_sell_provider": "デフォルトの販売プロバイダー", + "select_sell_provider_notice": "上記の販売プロバイダーを選択してください。アプリ設定でデフォルトの販売プロバイダーを設定することで、この画面をスキップできます。", "custom_drag": "カスタム(ホールドとドラッグ)", "switchToEVMCompatibleWallet": "EVM 互換のウォレットに切り替えて再試行してください (イーサリアム、ポリゴン)" } diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index c657dad7e..ca727c046 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -756,6 +756,8 @@ "dfx_option_description": "EUR 및 CHF로 암호화폐를 구매하세요. 추가 KYC 없이 최대 990€. 유럽의 소매 및 기업 고객용", "polygonscan_history": "다각형 스캔 기록", "wallet_seed_legacy": "레거시 지갑 시드", + "default_sell_provider": "기본 판매 공급자", + "select_sell_provider_notice": "위에서 판매 공급자를 선택하세요. 앱 설정에서 기본 판매 공급자를 설정하면 이 화면을 건너뛸 수 있습니다.", "custom_drag": "사용자 정의 (홀드 앤 드래그)", "switchToEVMCompatibleWallet": "EVM 호환 지갑으로 전환 후 다시 시도해 주세요. (이더리움, 폴리곤)" } diff --git a/res/values/strings_my.arb b/res/values/strings_my.arb index 690f59ccc..bd09bf740 100644 --- a/res/values/strings_my.arb +++ b/res/values/strings_my.arb @@ -756,6 +756,8 @@ "dfx_option_description": "EUR & CHF ဖြင့် crypto ကိုဝယ်ပါ။ အပို KYC မပါဘဲ 990€ အထိ။ ဥရောပရှိ လက်လီရောင်းချသူများနှင့် ကော်ပိုရိတ်ဖောက်သည်များအတွက်", "polygonscan_history": "PolygonScan မှတ်တမ်း", "wallet_seed_legacy": "အမွေအနှစ်ပိုက်ဆံအိတ်မျိုးစေ့", + "default_sell_provider": "ပုံသေရောင်းချပေးသူ", + "select_sell_provider_notice": "အထက်ဖော်ပြပါ အရောင်းဝန်ဆောင်မှုပေးသူကို ရွေးပါ။ အက်ပ်ဆက်တင်များတွင် သင်၏မူလရောင်းချပေးသူကို သတ်မှတ်ခြင်းဖြင့် ဤစခရင်ကို ကျော်နိုင်သည်။", "custom_drag": "စိတ်ကြိုက် (Drag)", "switchToEVMCompatibleWallet": "ကျေးဇူးပြု၍ EVM တွဲဖက်သုံးနိုင်သော ပိုက်ဆံအိတ်သို့ ပြောင်းပြီး ထပ်စမ်းကြည့်ပါ (Ethereum၊ Polygon)" } diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index 426341bc4..8b8e1875e 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -758,6 +758,8 @@ "dfx_option_description": "Koop crypto met EUR & CHF. Tot 990€ zonder extra KYC. Voor particuliere en zakelijke klanten in Europa", "polygonscan_history": "PolygonScan-geschiedenis", "wallet_seed_legacy": "Legacy portemonnee zaad", + "default_sell_provider": "Standaard verkoopaanbieder", + "select_sell_provider_notice": "Selecteer hierboven een verkoopaanbieder. U kunt dit scherm overslaan door uw standaardverkoopprovider in te stellen in de app-instellingen.", "custom_drag": "Custom (vasthouden en slepen)", "switchToEVMCompatibleWallet": "Schakel over naar een EVM-compatibele portemonnee en probeer het opnieuw (Ethereum, Polygon)" } diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index 5b6581bad..03d669904 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -758,6 +758,8 @@ "dfx_option_description": "Kupuj kryptowaluty za EUR i CHF. Do 990 € bez dodatkowego KYC. Dla klientów detalicznych i korporacyjnych w Europie", "polygonscan_history": "Historia PolygonScan", "wallet_seed_legacy": "Dziedziczne ziarno portfela", + "default_sell_provider": "Domyślny dostawca sprzedaży", + "select_sell_provider_notice": "Wybierz dostawcę sprzedaży powyżej. Możesz pominąć ten ekran, ustawiając domyślnego dostawcę sprzedaży w ustawieniach aplikacji.", "custom_drag": "Niestandardowe (trzymaj i przeciągnij)", "switchToEVMCompatibleWallet": "Przejdź na portfel zgodny z EVM i spróbuj ponownie (Ethereum, Polygon)" } diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index 5df592837..8cc55ee08 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -757,6 +757,8 @@ "dfx_option_description": "Compre criptografia com EUR e CHF. Até 990€ sem KYC adicional. Para clientes de varejo e corporativos na Europa", "polygonscan_history": "História do PolygonScan", "wallet_seed_legacy": "Semente de carteira herdada", + "default_sell_provider": "Provedor de venda padrão", + "select_sell_provider_notice": "Selecione um fornecedor de venda acima. Você pode pular esta tela definindo seu provedor de venda padrão nas configurações do aplicativo.", "custom_drag": "Personalizado (segure e arraste)", "switchToEVMCompatibleWallet": "Mude para uma carteira compatível com EVM e tente novamente (Ethereum, Polygon)" } diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index e6d149596..bfc6b7b10 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -758,6 +758,8 @@ "dfx_option_description": "Покупайте криптовалюту за EUR и CHF. До 990€ без дополнительного KYC. Для розничных и корпоративных клиентов в Европе", "polygonscan_history": "История PolygonScan", "wallet_seed_legacy": "Наследие семя кошелька", + "default_sell_provider": "Поставщик продаж по умолчанию", + "select_sell_provider_notice": "Выберите поставщика услуг продажи выше. Вы можете пропустить этот экран, установив поставщика услуг продаж по умолчанию в настройках приложения.", "custom_drag": "Пользователь (удерживайте и перетаскивайте)", "switchToEVMCompatibleWallet": "Пожалуйста, переключитесь на кошелек, совместимый с EVM, и повторите попытку (Ethereum, Polygon)." } diff --git a/res/values/strings_th.arb b/res/values/strings_th.arb index b445562da..d3142a534 100644 --- a/res/values/strings_th.arb +++ b/res/values/strings_th.arb @@ -756,6 +756,8 @@ "dfx_option_description": "ซื้อ crypto ด้วย EUR และ CHF สูงถึง 990€ โดยไม่มี KYC เพิ่มเติม สำหรับลูกค้ารายย่อยและลูกค้าองค์กรในยุโรป", "polygonscan_history": "ประวัติ PolygonScan", "wallet_seed_legacy": "เมล็ดกระเป๋าเงินมรดก", + "default_sell_provider": "ผู้ให้บริการการขายเริ่มต้น", + "select_sell_provider_notice": "เลือกผู้ให้บริการการขายด้านบน คุณสามารถข้ามหน้าจอนี้ได้โดยการตั้งค่าผู้ให้บริการการขายเริ่มต้นในการตั้งค่าแอป", "custom_drag": "กำหนดเอง (ค้างและลาก)", "switchToEVMCompatibleWallet": "โปรดเปลี่ยนไปใช้กระเป๋าเงินที่รองรับ EVM แล้วลองอีกครั้ง (Ethereum, Polygon)" } diff --git a/res/values/strings_tl.arb b/res/values/strings_tl.arb index 5b9af5243..dcae1d6ed 100644 --- a/res/values/strings_tl.arb +++ b/res/values/strings_tl.arb @@ -752,6 +752,8 @@ "dfx_option_description": "Bumili ng crypto gamit ang EUR at CHF. Hanggang 990€ nang walang karagdagang KYC. Para sa retail at corporate na mga customer sa Europe", "polygonscan_history": "Kasaysayan ng PolygonScan", "wallet_seed_legacy": "Legacy wallet seed", + "default_sell_provider": "Default na Sell Provider", + "select_sell_provider_notice": "Pumili ng provider ng nagbebenta sa itaas. Maaari mong laktawan ang screen na ito sa pamamagitan ng pagtatakda ng iyong default na sell provider sa mga setting ng app.", "custom_drag": "Pasadyang (hawakan at i -drag)", "switchToEVMCompatibleWallet": "Mangyaring lumipat sa isang EVM compatible na wallet at subukang muli (Ethereum, Polygon)" } diff --git a/res/values/strings_tr.arb b/res/values/strings_tr.arb index 948415da9..a2722856a 100644 --- a/res/values/strings_tr.arb +++ b/res/values/strings_tr.arb @@ -756,6 +756,8 @@ "dfx_option_description": "EUR ve CHF ile kripto satın alın. Ek KYC olmadan 990 €'ya kadar. Avrupa'daki perakende ve kurumsal müşteriler için", "polygonscan_history": "PolygonScan geçmişi", "wallet_seed_legacy": "Eski cüzdan tohumu", + "default_sell_provider": "Varsayılan Satış Sağlayıcısı", + "select_sell_provider_notice": "Yukarıdan bir satış sağlayıcısı seçin. Uygulama ayarlarında varsayılan satış sağlayıcınızı ayarlayarak bu ekranı atlayabilirsiniz.", "custom_drag": "Özel (Bekle ve Sürükle)", "switchToEVMCompatibleWallet": "Lütfen EVM uyumlu bir cüzdana geçin ve tekrar deneyin (Ethereum, Polygon)" } diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index f01afeaad..1a37f6550 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -758,6 +758,8 @@ "seed_language_chinese_traditional": "Китайський (традиційний)", "polygonscan_history": "Історія PolygonScan", "wallet_seed_legacy": "Спадець насіння гаманця", + "default_sell_provider": "Постачальник продажу за замовчуванням", + "select_sell_provider_notice": "Виберіть вище постачальника послуг продажу. Ви можете пропустити цей екран, встановивши постачальника послуг продажу за умовчанням у налаштуваннях програми.", "custom_drag": "На замовлення (утримуйте та перетягується)", "switchToEVMCompatibleWallet": "Перейдіть на гаманець, сумісний з EVM, і повторіть спробу (Ethereum, Polygon)" } diff --git a/res/values/strings_ur.arb b/res/values/strings_ur.arb index c8d46bc44..af030e0e7 100644 --- a/res/values/strings_ur.arb +++ b/res/values/strings_ur.arb @@ -750,6 +750,8 @@ "dfx_option_description": "EUR ﺭﻭﺍ CHF ﯽﻓﺎﺿﺍ ۔ﮟﯾﺪﯾﺮﺧ ﻮﭩﭘﺮﮐ ﮫﺗﺎﺳ ﮯﮐ KYC ﮯﯿﻟ ﮯﮐ ﻦﯿﻓﺭﺎﺻ ﭧﯾﺭﻮﭘﺭﺎﮐ ﺭﻭﺍ ﮦﺩﺭﻮﺧ ﮟ", "polygonscan_history": "ﺦﯾﺭﺎﺗ ﯽﮐ ﻦﯿﮑﺳﺍ ﻥﻮﮔ ﯽﻟﻮﭘ", "wallet_seed_legacy": "میراثی پرس کا بیج", + "default_sell_provider": " ﮦﺪﻨﻨﮐ ﻢﮨﺍﺮﻓ ﻞﯿﺳ ﭧﻟﺎﻔﯾﮈ", + "select_sell_provider_notice": "۔ﮟﯿﮨ ﮯﺘﮑﺳ ﮌﻮﮭﭼ ﻮﮐ ﻦﯾﺮﮑﺳﺍ ﺱﺍ ﺮﮐ ﮮﺩ ﺐﯿﺗﺮﺗ ﻮﮐ ﮦﺪﻨﻨﮐ ﻢﮨﺍﺮﻓ ﻞﯿﺳ ﭧﻟﺎﻔﯾﮈ ﮯﻨﭘﺍ ﮟﯿﻣ ﺕﺎﺒ", "custom_drag": "کسٹم (ہولڈ اینڈ ڈریگ)", "switchToEVMCompatibleWallet": "(Ethereum, Polygon) ﮟﯾﺮﮐ ﺶﺷﻮﮐ ﮦﺭﺎﺑﻭﺩ ﺭﻭﺍ ﮟﯾﺮﮐ ﭻﺋﻮﺳ ﺮﭘ ﭧﯿﻟﺍﻭ ﮯﻟﺍﻭ ﮯﻨﮭﮐﺭ ﺖﻘﺑﺎﻄﻣ " } diff --git a/res/values/strings_yo.arb b/res/values/strings_yo.arb index 3323429a8..a0dcf33a8 100644 --- a/res/values/strings_yo.arb +++ b/res/values/strings_yo.arb @@ -752,6 +752,8 @@ "dfx_option_description": "Ra crypto pẹlu EUR & CHF. Titi di 990 € laisi afikun KYC. Fun soobu ati awọn onibara ile-iṣẹ ni Yuroopu", "polygonscan_history": "PolygonScan itan", "wallet_seed_legacy": "Irugbin akole", + "default_sell_provider": "Aiyipada Olupese Tita", + "select_sell_provider_notice": "Yan olupese ti o ta loke. O le foju iboju yii nipa tito olupese iṣẹ tita aiyipada rẹ ni awọn eto app.", "custom_drag": "Aṣa (mu ati fa)", "switchToEVMCompatibleWallet": "Jọwọ yipada si apamọwọ ibaramu EVM ki o tun gbiyanju lẹẹkansi (Ethereum, Polygon)" } diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index 148460ffb..b7f216640 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -757,6 +757,8 @@ "dfx_option_description": "用欧元和瑞士法郎购买加密货币。高达 990 欧元,无需额外 KYC。对于欧洲的零售和企业客户", "polygonscan_history": "多边形扫描历史", "wallet_seed_legacy": "旧的钱包种子", + "default_sell_provider": "默认销售提供商", + "select_sell_provider_notice": "选择上面的销售提供商。您可以通过在应用程序设置中设置默认销售提供商来跳过此屏幕。", "custom_drag": "定制(保持和拖动)", "switchToEVMCompatibleWallet": "请切换到 EVM 兼容钱包并重试(以太坊、Polygon)" }