From 2138c35e38680f021b2f23b716a65d5e0e9673c5 Mon Sep 17 00:00:00 2001 From: Serhii Date: Fri, 8 Dec 2023 16:05:52 +0200 Subject: [PATCH] CW-532-DFX-buy-provider (#1209) * dfx buy provider ui * fix signing flow * fixed provider determination based on wallet type and app settings * update localization files * minor fix * Fix BTC, LTC und BCH signMessages * Add signMessage to monero * open dfx in webview * Update dfx_buy_provider.dart * Revert merge conflict * Update bitcoin_flutter ref --------- Co-authored-by: Konstantin Ullrich --- assets/images/dfx_dark.png | Bin 0 -> 3515 bytes assets/images/dfx_light.png | Bin 0 -> 3502 bytes cw_bitcoin/lib/electrum_wallet.dart | 5 +- cw_bitcoin/pubspec.yaml | 2 +- .../lib/src/bitcoin_cash_wallet.dart | 8 +- cw_bitcoin_cash/pubspec.yaml | 2 +- cw_monero/ios/Classes/monero_api.cpp | 9 + cw_monero/ios/Classes/monero_api.h | 3 +- cw_monero/lib/api/signatures.dart | 2 + cw_monero/lib/api/types.dart | 2 + cw_monero/lib/api/wallet.dart | 22 ++- cw_monero/lib/monero_wallet.dart | 6 + cw_monero/macos/Classes/monero_api.cpp | 7 + cw_monero/macos/Classes/monero_api.h | 3 +- lib/buy/dfx/dfx_buy_provider.dart | 179 ++++++++++++++++++ lib/di.dart | 6 +- lib/entities/buy_provider_types.dart | 40 +++- lib/entities/main_actions.dart | 81 ++++---- lib/src/screens/buy/buy_options_page.dart | 82 +++++--- .../screens/settings/other_settings_page.dart | 5 +- lib/store/settings_store.dart | 28 ++- .../dashboard/dashboard_view_model.dart | 6 +- .../settings/other_settings_view_model.dart | 35 ++-- pubspec_base.yaml | 2 +- res/values/strings_ar.arb | 1 + res/values/strings_bg.arb | 1 + res/values/strings_cs.arb | 1 + res/values/strings_de.arb | 1 + res/values/strings_en.arb | 1 + res/values/strings_es.arb | 2 + res/values/strings_fr.arb | 1 + res/values/strings_ha.arb | 1 + res/values/strings_hi.arb | 1 + res/values/strings_hr.arb | 2 + res/values/strings_id.arb | 2 + res/values/strings_it.arb | 1 + res/values/strings_ja.arb | 1 + res/values/strings_ko.arb | 1 + res/values/strings_my.arb | 1 + res/values/strings_nl.arb | 2 + res/values/strings_pl.arb | 1 + res/values/strings_pt.arb | 1 + res/values/strings_ru.arb | 1 + res/values/strings_th.arb | 1 + res/values/strings_tl.arb | 1 + res/values/strings_tr.arb | 1 + res/values/strings_uk.arb | 1 + res/values/strings_ur.arb | 1 + res/values/strings_yo.arb | 1 + res/values/strings_zh.arb | 1 + 50 files changed, 456 insertions(+), 109 deletions(-) create mode 100644 assets/images/dfx_dark.png create mode 100644 assets/images/dfx_light.png create mode 100644 lib/buy/dfx/dfx_buy_provider.dart diff --git a/assets/images/dfx_dark.png b/assets/images/dfx_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..cbba87372adad4c0c3f5d14dec996aacea01ee88 GIT binary patch literal 3515 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 literal 0 HcmV?d00001 diff --git a/assets/images/dfx_light.png b/assets/images/dfx_light.png new file mode 100644 index 0000000000000000000000000000000000000000..e4836be3ebecc08d3d0dee8fe3af672c511c7c2f GIT binary patch literal 3502 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 element.address == address).index : null; - return index == null - ? base64Encode(hd.sign(message)) - : base64Encode(hd.derive(index).sign(message)); + final HD = index == null ? hd : hd.derive(index); + return base64Encode(HD.signMessage(message)); } } diff --git a/cw_bitcoin/pubspec.yaml b/cw_bitcoin/pubspec.yaml index e90f9cda4..a50ff68ad 100644 --- a/cw_bitcoin/pubspec.yaml +++ b/cw_bitcoin/pubspec.yaml @@ -22,7 +22,7 @@ dependencies: bitcoin_flutter: git: url: https://github.com/cake-tech/bitcoin_flutter.git - ref: cake-update-v3 + ref: cake-update-v4 bitbox: git: url: https://github.com/cake-tech/bitbox-flutter.git diff --git a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart index 5f2a33ab6..fa41de767 100644 --- a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart +++ b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart @@ -302,10 +302,8 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store { final index = address != null ? walletAddresses.addresses .firstWhere((element) => element.address == AddressUtils.toLegacyAddress(address)) - .index - : null; - return index == null - ? base64Encode(hd.sign(message)) - : base64Encode(hd.derive(index).sign(message)); + .index : null; + final HD = index == null ? hd : hd.derive(index); + return base64Encode(HD.signMessage(message)); } } diff --git a/cw_bitcoin_cash/pubspec.yaml b/cw_bitcoin_cash/pubspec.yaml index 30ed49e80..eb1f057d8 100644 --- a/cw_bitcoin_cash/pubspec.yaml +++ b/cw_bitcoin_cash/pubspec.yaml @@ -24,7 +24,7 @@ dependencies: bitcoin_flutter: git: url: https://github.com/cake-tech/bitcoin_flutter.git - ref: cake-update-v3 + ref: cake-update-v4 bitbox: git: url: https://github.com/cake-tech/bitbox-flutter.git diff --git a/cw_monero/ios/Classes/monero_api.cpp b/cw_monero/ios/Classes/monero_api.cpp index 6162375b2..87be785ac 100644 --- a/cw_monero/ios/Classes/monero_api.cpp +++ b/cw_monero/ios/Classes/monero_api.cpp @@ -926,6 +926,8 @@ extern "C" return m_wallet->trustedDaemon(); } + // Coin Control // + CoinsInfoRow* coin(int index) { if (index >= 0 && index < m_coins_info.size()) { @@ -1020,6 +1022,13 @@ extern "C" m_coins->thaw(index); } + // Sign Messages // + + char *sign_message(char *message, char *address = "") + { + return strdup(get_current_wallet()->signMessage(std::string(message), std::string(address)).c_str()); + } + #ifdef __cplusplus } #endif diff --git a/cw_monero/ios/Classes/monero_api.h b/cw_monero/ios/Classes/monero_api.h index 74258ba4c..fa92a038d 100644 --- a/cw_monero/ios/Classes/monero_api.h +++ b/cw_monero/ios/Classes/monero_api.h @@ -32,7 +32,8 @@ void store(char *path); void set_trusted_daemon(bool arg); bool trusted_daemon(); +char *sign_message(char *message, char *address); #ifdef __cplusplus } -#endif \ No newline at end of file +#endif diff --git a/cw_monero/lib/api/signatures.dart b/cw_monero/lib/api/signatures.dart index 17099db91..bc4fc9d38 100644 --- a/cw_monero/lib/api/signatures.dart +++ b/cw_monero/lib/api/signatures.dart @@ -149,3 +149,5 @@ typedef coin = Pointer Function(Int32 index); typedef freeze_coin = Void Function(Int32 index); typedef thaw_coin = Void Function(Int32 index); + +typedef sign_message = Pointer Function(Pointer message, Pointer address); diff --git a/cw_monero/lib/api/types.dart b/cw_monero/lib/api/types.dart index 1d3904870..40a1e0321 100644 --- a/cw_monero/lib/api/types.dart +++ b/cw_monero/lib/api/types.dart @@ -149,3 +149,5 @@ typedef GetCoin = Pointer Function(int); typedef FreezeCoin = void Function(int); typedef ThawCoin = void Function(int); + +typedef SignMessage = Pointer Function(Pointer, Pointer); diff --git a/cw_monero/lib/api/wallet.dart b/cw_monero/lib/api/wallet.dart index 1680918e5..ffa5fe13b 100644 --- a/cw_monero/lib/api/wallet.dart +++ b/cw_monero/lib/api/wallet.dart @@ -8,7 +8,6 @@ import 'package:cw_monero/api/types.dart'; import 'package:cw_monero/api/monero_api.dart'; import 'package:cw_monero/api/exceptions/setup_wallet_exception.dart'; import 'package:flutter/foundation.dart'; -import 'package:flutter/services.dart'; int _boolToInt(bool value) => value ? 1 : 0; @@ -128,6 +127,10 @@ final trustedDaemonNative = moneroApi .lookup>('trusted_daemon') .asFunction(); +final signMessageNative = moneroApi + .lookup>('sign_message') + .asFunction(); + int getSyncingHeight() => getSyncingHeightNative(); bool isNeededToRefresh() => isNeededToRefreshNative() != 0; @@ -296,7 +299,7 @@ class SyncListener { final bchHeight = await getNodeHeightOrUpdate(syncHeight); - if (_lastKnownBlockHeight == syncHeight || syncHeight == null) { + if (_lastKnownBlockHeight == syncHeight) { return; } @@ -311,7 +314,7 @@ class SyncListener { } // 1. Actual new height; 2. Blocks left to finish; 3. Progress in percents; - onNewBlock?.call(syncHeight, left, ptc); + onNewBlock.call(syncHeight, left, ptc); }); } @@ -382,4 +385,15 @@ String getSubaddressLabel(int accountIndex, int addressIndex) { Future setTrustedDaemon(bool trusted) async => setTrustedDaemonNative(_boolToInt(trusted)); -Future trustedDaemon() async => trustedDaemonNative() != 0; \ No newline at end of file +Future trustedDaemon() async => trustedDaemonNative() != 0; + +String signMessage(String message, {String address = ""}) { + final messagePointer = message.toNativeUtf8(); + final addressPointer = address.toNativeUtf8(); + + final signature = convertUTF8ToString(pointer: signMessageNative(messagePointer, addressPointer)); + calloc.free(messagePointer); + calloc.free(addressPointer); + + return signature; +} diff --git a/cw_monero/lib/monero_wallet.dart b/cw_monero/lib/monero_wallet.dart index 71c7e3967..4b71fb5ff 100644 --- a/cw_monero/lib/monero_wallet.dart +++ b/cw_monero/lib/monero_wallet.dart @@ -651,4 +651,10 @@ abstract class MoneroWalletBase @override void setExceptionHandler(void Function(FlutterErrorDetails) onError) => _onError = onError; + + @override + String signMessage(String message, {String? address}) { + final useAddress = address ?? ""; + return monero_wallet.signMessage(message, address: useAddress); + } } diff --git a/cw_monero/macos/Classes/monero_api.cpp b/cw_monero/macos/Classes/monero_api.cpp index 6fabc29fd..fe75dea98 100644 --- a/cw_monero/macos/Classes/monero_api.cpp +++ b/cw_monero/macos/Classes/monero_api.cpp @@ -1020,6 +1020,13 @@ extern "C" m_coins->thaw(index); } + // Sign Messages // + + char *sign_message(char *message, char *address = "") + { + return strdup(get_current_wallet()->signMessage(std::string(message), std::string(address)).c_str()); + } + #ifdef __cplusplus } #endif diff --git a/cw_monero/macos/Classes/monero_api.h b/cw_monero/macos/Classes/monero_api.h index 74258ba4c..fa92a038d 100644 --- a/cw_monero/macos/Classes/monero_api.h +++ b/cw_monero/macos/Classes/monero_api.h @@ -32,7 +32,8 @@ void store(char *path); void set_trusted_daemon(bool arg); bool trusted_daemon(); +char *sign_message(char *message, char *address); #ifdef __cplusplus } -#endif \ No newline at end of file +#endif diff --git a/lib/buy/dfx/dfx_buy_provider.dart b/lib/buy/dfx/dfx_buy_provider.dart new file mode 100644 index 000000000..d5cf50d10 --- /dev/null +++ b/lib/buy/dfx/dfx_buy_provider.dart @@ -0,0 +1,179 @@ +import 'dart:convert'; + +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:cw_core/wallet_base.dart'; +import 'package:cw_core/wallet_type.dart'; +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; + + static const _baseUrl = 'api.dfx.swiss'; + static const _authPath = '/v1/auth/signMessage'; + static const _signUpPath = '/v1/auth/signUp'; + static const _signInPath = '/v1/auth/signIn'; + static const walletName = 'CakeWallet'; + + String get assetOut { + switch (_wallet.type) { + case WalletType.bitcoin: + return 'BTC'; + case WalletType.bitcoinCash: + return 'BCH'; + case WalletType.litecoin: + return 'LTC'; + case WalletType.monero: + return 'XMR'; + case WalletType.ethereum: + return 'ETH'; + default: + throw Exception("WalletType is not available for DFX ${_wallet.type}"); + } + } + + String get blockchain { + switch (_wallet.type) { + case WalletType.bitcoin: + case WalletType.bitcoinCash: + case WalletType.litecoin: + return 'Bitcoin'; + case WalletType.monero: + return 'Monero'; + case WalletType.ethereum: + return 'Ethereum'; + default: + throw Exception("WalletType is not available for DFX ${_wallet.type}"); + } + } + + Future getSignMessage() async { + final walletAddress = _wallet.walletAddresses.address; + final uri = Uri.https(_baseUrl, _authPath, {'address': walletAddress}); + + var response = await http.get(uri, headers: {'accept': 'application/json'}); + + if (response.statusCode == 200) { + final responseBody = jsonDecode(response.body); + return responseBody['message'] as String; + } else { + throw Exception( + 'Failed to get sign message. Status: ${response.statusCode} ${response.body}'); + } + } + + Future signUp() async { + final signMessage = getSignature(await getSignMessage()); + final walletAddress = _wallet.walletAddresses.address; + + final requestBody = jsonEncode({ + 'wallet': walletName, + 'address': walletAddress, + 'signature': signMessage, + }); + + final uri = Uri.https(_baseUrl, _signUpPath); + var response = await http.post(uri, + headers: {'Content-Type': 'application/json'}, body: requestBody); + + if (response.statusCode == 201) { + final responseBody = jsonDecode(response.body); + return responseBody['accessToken'] as String; + } else { + throw Exception( + 'Failed to sign up. Status: ${response.statusCode} ${response.body}'); + } + } + + Future signIn() async { + final signMessage = getSignature(await getSignMessage()); + final walletAddress = _wallet.walletAddresses.address; + + final requestBody = jsonEncode({ + 'address': walletAddress, + 'signature': signMessage, + }); + + final uri = Uri.https(_baseUrl, _signInPath); + var response = await http.post(uri, + headers: {'Content-Type': 'application/json'}, body: requestBody); + + if (response.statusCode == 201) { + final responseBody = jsonDecode(response.body); + return responseBody['accessToken'] as String; + } else { + throw Exception( + 'Failed to sign in. Status: ${response.statusCode} ${response.body}'); + } + } + + String getSignature(String message) { + switch (_wallet.type) { + case WalletType.ethereum: + return _wallet.signMessage(message); + case WalletType.monero: + case WalletType.litecoin: + case WalletType.bitcoin: + case WalletType.bitcoinCash: + return _wallet.signMessage(message, + address: _wallet.walletAddresses.address); + default: + throw Exception("WalletType is not available for DFX ${_wallet.type}"); + } + } + + Future launchProvider(BuildContext context) async { + try { + final assetOut = this.assetOut; + final blockchain = this.blockchain; + + String accessToken; + + try { + accessToken = await signUp(); + } on Exception catch (e) { + if (e.toString().contains('409')) { + accessToken = await signIn(); + } else { + rethrow; + } + } + + final uri = Uri.https('services.dfx.swiss', '/buy', { + 'session': accessToken, + 'lang': 'en', + 'asset-out': assetOut, + 'blockchain': blockchain, + 'asset-in': 'EUR', + }); + + if (await canLaunchUrl(uri)) { + if (DeviceInfo.instance.isMobile) { + Navigator.of(context).pushNamed(Routes.webViewPage, + arguments: [S.of(context).buy, uri]); + } else { + await launchUrl(uri, mode: LaunchMode.externalApplication); + } + } else { + throw Exception('Could not launch URL'); + } + } catch (e) { + await showPopUp( + context: context, + builder: (BuildContext context) { + return AlertWithOneAction( + alertTitle: "DFX Connect", + alertContent: S.of(context).buy_provider_unavailable + ': $e', + buttonText: S.of(context).ok, + buttonAction: () => Navigator.of(context).pop()); + }); + } + } +} diff --git a/lib/di.dart b/lib/di.dart index e0faa6db1..493ff84b9 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -227,6 +227,7 @@ import 'package:cake_wallet/core/wallet_loading_service.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cake_wallet/entities/qr_view_data.dart'; +import 'buy/dfx/dfx_buy_provider.dart'; import 'core/totp_request_details.dart'; import 'src/screens/settings/desktop_settings/desktop_settings_page.dart'; @@ -795,6 +796,9 @@ Future setup({ getIt.registerFactory( () => RobinhoodBuyProvider(wallet: getIt.get().wallet!)); + getIt.registerFactory( + () => DFXBuyProvider(wallet: getIt.get().wallet!)); + getIt.registerFactory(() => OnRamperBuyProvider( settingsStore: getIt.get().settingsStore, wallet: getIt.get().wallet!, @@ -940,7 +944,7 @@ Future setup({ getIt.registerFactory(() => BuyAmountViewModel()); - getIt.registerFactory(() => BuyOptionsPage()); + getIt.registerFactory(() => BuyOptionsPage(getIt.get())); getIt.registerFactory(() { final wallet = getIt.get().wallet; diff --git a/lib/entities/buy_provider_types.dart b/lib/entities/buy_provider_types.dart index 90c070e86..1faf2b281 100644 --- a/lib/entities/buy_provider_types.dart +++ b/lib/entities/buy_provider_types.dart @@ -1,9 +1,11 @@ import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cw_core/wallet_type.dart'; enum BuyProviderType { AskEachTime, Robinhood, - Onramper; + Onramper, + DFX; @override String toString() { @@ -14,6 +16,42 @@ enum BuyProviderType { return "Robinhood"; case BuyProviderType.Onramper: return "Onramper"; + case BuyProviderType.DFX: + return "DFX"; + } + } + + static List getAvailableProviders(WalletType walletType) { + switch (walletType) { + case WalletType.nano: + case WalletType.banano: + return [ + BuyProviderType.AskEachTime, + BuyProviderType.Onramper + ]; + case WalletType.monero: + return [ + BuyProviderType.AskEachTime, + BuyProviderType.Onramper, + BuyProviderType.DFX + ]; + case WalletType.bitcoin: + case WalletType.ethereum: + return [ + BuyProviderType.AskEachTime, + BuyProviderType.Onramper, + BuyProviderType.DFX, + BuyProviderType.Robinhood + ]; + case WalletType.litecoin: + case WalletType.bitcoinCash: + return [ + BuyProviderType.AskEachTime, + BuyProviderType.Onramper, + BuyProviderType.Robinhood + ]; + default: + return []; } } } diff --git a/lib/entities/main_actions.dart b/lib/entities/main_actions.dart index 1ef388d31..4c629e768 100644 --- a/lib/entities/main_actions.dart +++ b/lib/entities/main_actions.dart @@ -1,3 +1,4 @@ +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'; @@ -44,48 +45,54 @@ class MainActions { isEnabled: (viewModel) => viewModel.isEnabledBuyAction, canShow: (viewModel) => viewModel.hasBuyAction, onTap: (BuildContext context, DashboardViewModel viewModel) async { + if (!viewModel.isEnabledBuyAction) { + await _showErrorDialog(context, S.of(context).unsupported_asset); + return; + } + final defaultBuyProvider = viewModel.defaultBuyProvider; - final walletType = viewModel.type; - - if (!viewModel.isEnabledBuyAction) return; - - switch (walletType) { - case WalletType.bitcoin: - case WalletType.litecoin: - case WalletType.ethereum: - case WalletType.polygon: - case WalletType.bitcoinCash: - switch (defaultBuyProvider) { - 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; - } - break; - case WalletType.nano: - case WalletType.banano: - case WalletType.monero: - await getIt.get().launchProvider(context); - break; - default: - await showPopUp( - context: context, - builder: (BuildContext context) { - return AlertWithOneAction( - alertTitle: S.of(context).buy, - alertContent: S.of(context).unsupported_asset, - buttonText: S.of(context).ok, - buttonAction: () => Navigator.of(context).pop()); - }); + try { + await _launchProviderByType(context, defaultBuyProvider); + } catch (e) { + await _showErrorDialog(context, 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', diff --git a/lib/src/screens/buy/buy_options_page.dart b/lib/src/screens/buy/buy_options_page.dart index 930878544..62dfc4ec4 100644 --- a/lib/src/screens/buy/buy_options_page.dart +++ b/lib/src/screens/buy/buy_options_page.dart @@ -1,18 +1,26 @@ +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/entities/buy_provider_types.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/src/screens/base_page.dart'; import 'package:cake_wallet/src/widgets/option_tile.dart'; import 'package:cake_wallet/themes/extensions/option_tile_theme.dart'; import 'package:cake_wallet/themes/extensions/transaction_trade_theme.dart'; +import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart'; import 'package:flutter/material.dart'; class BuyOptionsPage extends BasePage { + BuyOptionsPage(this.dashboardViewModel); + + 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'; @override String get title => S.current.buy; @@ -22,11 +30,19 @@ class BuyOptionsPage extends BasePage { @override 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 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; return Container( child: Center( @@ -34,26 +50,42 @@ class BuyOptionsPage extends BasePage { constraints: BoxConstraints(maxWidth: 330), child: Column( children: [ - 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), + if (availableProviders.contains(BuyProviderType.Onramper)) + 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), + ), ), - ), - 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.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), + ), ), - ), Spacer(), Padding( padding: EdgeInsets.fromLTRB(24, 24, 24, 32), @@ -63,7 +95,9 @@ class BuyOptionsPage extends BasePage { style: TextStyle( fontSize: 14, fontWeight: FontWeight.normal, - color: Theme.of(context).extension()!.detailsTitlesColor, + color: Theme.of(context) + .extension()! + .detailsTitlesColor, ), ), ), diff --git a/lib/src/screens/settings/other_settings_page.dart b/lib/src/screens/settings/other_settings_page.dart index ede816893..cc92641f3 100644 --- a/lib/src/screens/settings/other_settings_page.dart +++ b/lib/src/screens/settings/other_settings_page.dart @@ -1,5 +1,3 @@ -import 'package:cake_wallet/buy/buy_provider.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/routes.dart'; @@ -42,9 +40,10 @@ class OtherSettingsPage extends BasePage { handler: (BuildContext context) => Navigator.of(context).pushNamed(Routes.changeRep), ), + if(_otherSettingsViewModel.isEnabledBuyAction) SettingsPickerCell( title: S.current.default_buy_provider, - items: BuyProviderType.values, + items: _otherSettingsViewModel.availableBuyProviders, displayItem: _otherSettingsViewModel.getBuyProviderType, selectedItem: _otherSettingsViewModel.buyProviderType, onItemSelected: _otherSettingsViewModel.onBuyProviderTypeSelected, diff --git a/lib/store/settings_store.dart b/lib/store/settings_store.dart index 3e38fed1f..3984bd81a 100644 --- a/lib/store/settings_store.dart +++ b/lib/store/settings_store.dart @@ -36,7 +36,6 @@ import 'package:cake_wallet/monero/monero.dart'; import 'package:cake_wallet/entities/action_list_display_mode.dart'; import 'package:cake_wallet/entities/fiat_api_mode.dart'; import 'package:cw_core/set_app_secure_native.dart'; - part 'settings_store.g.dart'; class SettingsStore = SettingsStoreBase with _$SettingsStore; @@ -123,7 +122,6 @@ abstract class SettingsStoreBase with Store { isAppSecure = initialAppSecure, disableBuy = initialDisableBuy, disableSell = initialDisableSell, - defaultBuyProvider = initialDefaultBuyProvider, shouldShowMarketPlaceInDashboard = initialShouldShowMarketPlaceInDashboard, exchangeStatus = initialExchangeStatus, currentTheme = initialTheme, @@ -178,6 +176,12 @@ 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; + }); + reaction( (_) => fiatCurrency, (FiatCurrency fiatCurrency) => sharedPreferences.setString( @@ -244,9 +248,14 @@ abstract class SettingsStoreBase with Store { sharedPreferences.setBool(PreferencesKey.disableSellKey, disableSell)); reaction( - (_) => defaultBuyProvider, - (BuyProviderType defaultBuyProvider) => - sharedPreferences.setInt(PreferencesKey.defaultBuyProvider, defaultBuyProvider.index)); + (_) => defaultBuyProviders.asObservable(), + (ObservableMap providers) { + providers.forEach((walletType, provider) { + final key = 'defaultBuyProvider_${walletType.toString()}'; + sharedPreferences.setInt(key, provider.index); + }); + } + ); reaction( (_) => autoGenerateSubaddressStatus, @@ -333,6 +342,7 @@ abstract class SettingsStoreBase with Store { reaction((_) => totpSecretKey, (String totpKey) => sharedPreferences.setString(PreferencesKey.totpSecretKey, totpKey)); + reaction( (_) => numberOfFailedTokenTrials, (int failedTokenTrail) => @@ -494,9 +504,6 @@ abstract class SettingsStoreBase with Store { @observable bool disableSell; - @observable - BuyProviderType defaultBuyProvider; - @observable bool allowBiometricalAuthentication; @@ -566,6 +573,9 @@ abstract class SettingsStoreBase with Store { @observable ObservableMap trocadorProviderStates = ObservableMap(); + @observable + ObservableMap defaultBuyProviders = ObservableMap(); + @observable SortBalanceBy sortBalanceBy; @@ -1003,8 +1013,6 @@ abstract class SettingsStoreBase with Store { isAppSecure = sharedPreferences.getBool(PreferencesKey.isAppSecureKey) ?? isAppSecure; disableBuy = sharedPreferences.getBool(PreferencesKey.disableBuyKey) ?? disableBuy; disableSell = sharedPreferences.getBool(PreferencesKey.disableSellKey) ?? disableSell; - defaultBuyProvider = - BuyProviderType.values[sharedPreferences.getInt(PreferencesKey.defaultBuyProvider) ?? 0]; allowBiometricalAuthentication = sharedPreferences.getBool(PreferencesKey.allowBiometricalAuthenticationKey) ?? allowBiometricalAuthentication; diff --git a/lib/view_model/dashboard/dashboard_view_model.dart b/lib/view_model/dashboard/dashboard_view_model.dart index fbb2fc76f..84ba54202 100644 --- a/lib/view_model/dashboard/dashboard_view_model.dart +++ b/lib/view_model/dashboard/dashboard_view_model.dart @@ -282,10 +282,14 @@ abstract class DashboardViewModelBase with Store { Map> filterItems; - BuyProviderType get defaultBuyProvider => settingsStore.defaultBuyProvider; + BuyProviderType get defaultBuyProvider => + settingsStore.defaultBuyProviders[wallet.type] ?? + BuyProviderType.AskEachTime; bool get isBuyEnabled => settingsStore.isBitcoinBuyEnabled; + List get availableProviders => BuyProviderType.getAvailableProviders(wallet.type); + bool get shouldShowYatPopup => settingsStore.shouldShowYatPopup; @action diff --git a/lib/view_model/settings/other_settings_view_model.dart b/lib/view_model/settings/other_settings_view_model.dart index b4ca46f70..4e712dae1 100644 --- a/lib/view_model/settings/other_settings_view_model.dart +++ b/lib/view_model/settings/other_settings_view_model.dart @@ -13,14 +13,15 @@ import 'package:package_info/package_info.dart'; part 'other_settings_view_model.g.dart'; -class OtherSettingsViewModel = OtherSettingsViewModelBase with _$OtherSettingsViewModel; +class OtherSettingsViewModel = OtherSettingsViewModelBase + with _$OtherSettingsViewModel; abstract class OtherSettingsViewModelBase with Store { OtherSettingsViewModelBase(this._settingsStore, this._wallet) : walletType = _wallet.type, currentVersion = '' { - PackageInfo.fromPlatform() - .then((PackageInfo packageInfo) => currentVersion = packageInfo.version); + PackageInfo.fromPlatform().then( + (PackageInfo packageInfo) => currentVersion = packageInfo.version); final priority = _settingsStore.priority[_wallet.type]; final priorities = priorityForWalletType(_wallet.type); @@ -31,7 +32,8 @@ abstract class OtherSettingsViewModelBase with Store { } final WalletType walletType; - final WalletBase, TransactionInfo> _wallet; + final WalletBase, + TransactionInfo> _wallet; @observable String currentVersion; @@ -50,15 +52,19 @@ abstract class OtherSettingsViewModelBase with Store { } @computed - bool get changeRepresentativeEnabled { - if (_wallet.type == WalletType.nano || _wallet.type == WalletType.banano) { - return true; - } + bool get changeRepresentativeEnabled => + _wallet.type == WalletType.nano || _wallet.type == WalletType.banano; - return false; - } - - BuyProviderType get buyProviderType { return _settingsStore.defaultBuyProvider; } + @computed + bool get isEnabledBuyAction => + !_settingsStore.disableBuy && _wallet.type != WalletType.haven; + + List get availableBuyProviders => + BuyProviderType.getAvailableProviders(walletType); + + BuyProviderType get buyProviderType => + _settingsStore.defaultBuyProviders[walletType] ?? + BuyProviderType.AskEachTime; String getDisplayPriority(dynamic priority) { final _priority = priority as TransactionPriority; @@ -73,7 +79,7 @@ abstract class OtherSettingsViewModelBase with Store { return priority.toString(); } - String getBuyProviderType (dynamic buyProviderType) { + String getBuyProviderType(dynamic buyProviderType) { final _buyProviderType = buyProviderType as BuyProviderType; return _buyProviderType.toString(); @@ -83,6 +89,5 @@ abstract class OtherSettingsViewModelBase with Store { _settingsStore.priority[_wallet.type] = priority; void onBuyProviderTypeSelected(BuyProviderType buyProviderType) => - _settingsStore.defaultBuyProvider = buyProviderType; - + _settingsStore.defaultBuyProviders[walletType] = buyProviderType; } diff --git a/pubspec_base.yaml b/pubspec_base.yaml index a4738bf82..b87347630 100644 --- a/pubspec_base.yaml +++ b/pubspec_base.yaml @@ -96,7 +96,7 @@ dependencies: bitcoin_flutter: git: url: https://github.com/cake-tech/bitcoin_flutter.git - ref: cake-update-v3 + ref: cake-update-v4 fluttertoast: 8.1.4 # tor: # git: diff --git a/res/values/strings_ar.arb b/res/values/strings_ar.arb index 45ad96764..269400ffc 100644 --- a/res/values/strings_ar.arb +++ b/res/values/strings_ar.arb @@ -746,5 +746,6 @@ "seed_language_czech": "التشيكية", "seed_language_korean": "الكورية", "seed_language_chinese_traditional": "تقاليد صينية)", + "dfx_option_description": "ﺎﺑﻭﺭﻭﺃ ﻲﻓ ﺕﺎﻛﺮﺸﻟﺍﻭ ﺔﺋﺰﺠﺘﻟﺍ ءﻼﻤﻌﻟ .ﻲﻓﺎﺿﺇ KYC ﻥﻭﺪﺑ ﻭﺭﻮﻳ 990 ﻰﻟﺇ ﻞﺼﻳ ﺎﻣ .ﻱﺮﺴﻳﻮﺴﻟﺍ", "polygonscan_history": "ﻥﺎﻜﺴﻧﻮﺠﻴﻟﻮﺑ ﺦﻳﺭﺎﺗ" } diff --git a/res/values/strings_bg.arb b/res/values/strings_bg.arb index d52843798..27c54cde5 100644 --- a/res/values/strings_bg.arb +++ b/res/values/strings_bg.arb @@ -742,5 +742,6 @@ "seed_language_czech": "Чех", "seed_language_korean": "Корейски", "seed_language_chinese_traditional": "Традиционен китайски)", + "dfx_option_description": "Купете крипто с EUR и CHF. До 990 € без допълнителен KYC. За клиенти на дребно и корпоративни клиенти в Европа", "polygonscan_history": "История на PolygonScan" } diff --git a/res/values/strings_cs.arb b/res/values/strings_cs.arb index 3e1d68543..10ed3b0e9 100644 --- a/res/values/strings_cs.arb +++ b/res/values/strings_cs.arb @@ -742,5 +742,6 @@ "seed_language_czech": "čeština", "seed_language_korean": "korejština", "seed_language_chinese_traditional": "Číňan (tradiční)", + "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" } diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index 62a73361c..eef930971 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -750,5 +750,6 @@ "seed_language_czech": "Tschechisch", "seed_language_korean": "Koreanisch", "seed_language_chinese_traditional": "Chinesisch (Traditionell)", + "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" } diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index 19c435ce8..053ed0488 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -751,5 +751,6 @@ "seed_language_czech": "Czech", "seed_language_korean": "Korean", "seed_language_chinese_traditional": "Chinese (Traditional)", + "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" } diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index 2b94e51b9..fcdaf17b1 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -749,6 +749,8 @@ "seedtype_polyseed": "Polieta (16 palabras)", "seed_language_czech": "checo", "seed_language_korean": "coreano", + "seed_language_chinese_traditional": "Chino tradicional)", + "dfx_option_description": "Compre criptomonedas con EUR y CHF. Hasta 990€ sin KYC adicional. Para clientes minoristas y corporativos en Europa", "seed_language_chinese_traditional": "Chino (tradicional)", "polygonscan_history": "Historial de PolygonScan" } diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index 0c71ba06e..3fe73f0b4 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -750,5 +750,6 @@ "seed_language_czech": "tchèque", "seed_language_korean": "coréen", "seed_language_chinese_traditional": "Chinois (Traditionnel)", + "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" } diff --git a/res/values/strings_ha.arb b/res/values/strings_ha.arb index 0eb9bc7cd..d35f3a89e 100644 --- a/res/values/strings_ha.arb +++ b/res/values/strings_ha.arb @@ -728,5 +728,6 @@ "seed_language_czech": "Czech", "seed_language_korean": "Yaren Koriya", "seed_language_chinese_traditional": "Sinanci (na gargajiya)", + "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" } diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index c4c11fa58..ce4ebc453 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -750,5 +750,6 @@ "seed_language_czech": "चेक", "seed_language_korean": "कोरियाई", "seed_language_chinese_traditional": "चीनी पारंपरिक)", + "dfx_option_description": "EUR और CHF के साथ क्रिप्टो खरीदें। अतिरिक्त केवाईसी के बिना 990€ तक। यूरोप में खुदरा और कॉर्पोरेट ग्राहकों के लिए", "polygonscan_history": "पॉलीगॉनस्कैन इतिहास" } diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index 82df37e69..4ab734f9f 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -747,6 +747,8 @@ "seedtype_polyseed": "Poliseed (16 riječi)", "seed_language_czech": "češki", "seed_language_korean": "korejski", + "seed_language_chinese_traditional": "Kinesko tradicionalno)", + "dfx_option_description": "Kupujte kripto s EUR i CHF. Do 990 € bez dodatnog KYC-a. Za maloprodajne i poslovne korisnike u Europi", "seed_language_chinese_traditional": "Kinesko (tradicionalno)", "polygonscan_history": "Povijest PolygonScan" } diff --git a/res/values/strings_id.arb b/res/values/strings_id.arb index de04ecb12..041bbd93c 100644 --- a/res/values/strings_id.arb +++ b/res/values/strings_id.arb @@ -737,6 +737,8 @@ "seedtype_polyseed": "Polyseed (16 kata)", "seed_language_czech": "Ceko", "seed_language_korean": "Korea", + "seed_language_chinese_traditional": "Cina tradisional)", + "dfx_option_description": "Beli kripto dengan EUR & CHF. Hingga 990€ tanpa KYC tambahan. Untuk pelanggan ritel dan korporat di Eropa", "seed_language_chinese_traditional": "Cina (tradisional)", "polygonscan_history": "Sejarah PolygonScan" } diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index acb7a71c3..250edda6b 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -750,5 +750,6 @@ "seed_language_czech": "ceco", "seed_language_korean": "coreano", "seed_language_chinese_traditional": "Cinese tradizionale)", + "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" } diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index 7907d3835..37b5e60fd 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -750,5 +750,6 @@ "seed_language_czech": "チェコ", "seed_language_korean": "韓国語", "seed_language_chinese_traditional": "中国の伝統的な)", + "dfx_option_description": "EUR と CHF で暗号通貨を購入します。追加のKYCなしで最大990ユーロ。ヨーロッパの小売および法人顧客向け", "polygonscan_history": "ポリゴンスキャン履歴" } diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index e9a54974e..611915f56 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -748,5 +748,6 @@ "seed_language_czech": "체코 사람", "seed_language_korean": "한국인", "seed_language_chinese_traditional": "중국 전통)", + "dfx_option_description": "EUR 및 CHF로 암호화폐를 구매하세요. 추가 KYC 없이 최대 990€. 유럽의 소매 및 기업 고객용", "polygonscan_history": "다각형 스캔 기록" } diff --git a/res/values/strings_my.arb b/res/values/strings_my.arb index 02e3d38c2..cf12b9b16 100644 --- a/res/values/strings_my.arb +++ b/res/values/strings_my.arb @@ -748,5 +748,6 @@ "seed_language_czech": "ချက်", "seed_language_korean": "ကိုးရီးယား", "seed_language_chinese_traditional": "တရုတ်ရိုးရာ)", + "dfx_option_description": "EUR & CHF ဖြင့် crypto ကိုဝယ်ပါ။ အပို KYC မပါဘဲ 990€ အထိ။ ဥရောပရှိ လက်လီရောင်းချသူများနှင့် ကော်ပိုရိတ်ဖောက်သည်များအတွက်", "polygonscan_history": "PolygonScan မှတ်တမ်း" } diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index 4fd0fbab7..b36a5a234 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -749,6 +749,8 @@ "seedtype_polyseed": "Polyseed (16 woorden)", "seed_language_czech": "Tsjechisch", "seed_language_korean": "Koreaans", + "seed_language_chinese_traditional": "Chinese traditionele)", + "dfx_option_description": "Koop crypto met EUR & CHF. Tot 990€ zonder extra KYC. Voor particuliere en zakelijke klanten in Europa", "seed_language_chinese_traditional": "Chinese (traditionele)", "polygonscan_history": "PolygonScan-geschiedenis" } diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index bf3cc5329..afb9bf9ec 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -750,5 +750,6 @@ "seed_language_czech": "Czech", "seed_language_korean": "koreański", "seed_language_chinese_traditional": "Chiński tradycyjny)", + "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" } diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index ba4beb6c5..c2fe03ad3 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -749,5 +749,6 @@ "seed_language_czech": "Tcheco", "seed_language_korean": "coreano", "seed_language_chinese_traditional": "Chinês tradicional)", + "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" } diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index 83a1aaf2b..107cbf596 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -750,5 +750,6 @@ "seed_language_czech": "Чешский", "seed_language_korean": "Корейский", "seed_language_chinese_traditional": "Китайский традиционный)", + "dfx_option_description": "Покупайте криптовалюту за EUR и CHF. До 990€ без дополнительного KYC. Для розничных и корпоративных клиентов в Европе", "polygonscan_history": "История PolygonScan" } diff --git a/res/values/strings_th.arb b/res/values/strings_th.arb index e046eaba8..134868c08 100644 --- a/res/values/strings_th.arb +++ b/res/values/strings_th.arb @@ -748,5 +748,6 @@ "seed_language_czech": "ภาษาเช็ก", "seed_language_korean": "เกาหลี", "seed_language_chinese_traditional": "จีน (ดั้งเดิม)", + "dfx_option_description": "ซื้อ crypto ด้วย EUR และ CHF สูงถึง 990€ โดยไม่มี KYC เพิ่มเติม สำหรับลูกค้ารายย่อยและลูกค้าองค์กรในยุโรป", "polygonscan_history": "ประวัติ PolygonScan" } diff --git a/res/values/strings_tl.arb b/res/values/strings_tl.arb index 06ee4bc89..d12e89378 100644 --- a/res/values/strings_tl.arb +++ b/res/values/strings_tl.arb @@ -744,5 +744,6 @@ "seed_language_czech": "Czech", "seed_language_korean": "Korean", "seed_language_chinese_traditional": "Intsik (tradisyonal)", + "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" } diff --git a/res/values/strings_tr.arb b/res/values/strings_tr.arb index f300951ba..30a354caa 100644 --- a/res/values/strings_tr.arb +++ b/res/values/strings_tr.arb @@ -748,5 +748,6 @@ "seed_language_czech": "Çek", "seed_language_korean": "Koreli", "seed_language_chinese_traditional": "Çin geleneği)", + "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" } diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index 6f8d87e86..f977308b5 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -749,6 +749,7 @@ "seedtype_polyseed": "Полісей (16 слів)", "seed_language_czech": "Чеський", "seed_language_korean": "Корейський", + "dfx_option_description": "Купуйте криптовалюту за EUR і CHF. До 990 євро без додаткового KYC. Для роздрібних і корпоративних клієнтів у Європі", "seed_language_chinese_traditional": "Китайський (традиційний)", "polygonscan_history": "Історія PolygonScan" } diff --git a/res/values/strings_ur.arb b/res/values/strings_ur.arb index f75d287ef..be7525c1b 100644 --- a/res/values/strings_ur.arb +++ b/res/values/strings_ur.arb @@ -742,5 +742,6 @@ "seed_language_czech": "چیک", "seed_language_korean": "کورین", "seed_language_chinese_traditional": "چینی (روایتی)", + "dfx_option_description": "EUR ﺭﻭﺍ CHF ﯽﻓﺎﺿﺍ ۔ﮟﯾﺪﯾﺮﺧ ﻮﭩﭘﺮﮐ ﮫﺗﺎﺳ ﮯﮐ KYC ﮯﯿﻟ ﮯﮐ ﻦﯿﻓﺭﺎﺻ ﭧﯾﺭﻮﭘﺭﺎﮐ ﺭﻭﺍ ﮦﺩﺭﻮﺧ ﮟ", "polygonscan_history": "ﺦﯾﺭﺎﺗ ﯽﮐ ﻦﯿﮑﺳﺍ ﻥﻮﮔ ﯽﻟﻮﭘ" } diff --git a/res/values/strings_yo.arb b/res/values/strings_yo.arb index b5937a49c..63d21666e 100644 --- a/res/values/strings_yo.arb +++ b/res/values/strings_yo.arb @@ -744,5 +744,6 @@ "seed_language_czech": "Czech", "seed_language_korean": "Ara ẹni", "seed_language_chinese_traditional": "Kannada (ibile)", + "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" } diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index b487f83f5..25710eff5 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -749,5 +749,6 @@ "seed_language_czech": "捷克", "seed_language_korean": "韩国人", "seed_language_chinese_traditional": "中国传统的)", + "dfx_option_description": "用欧元和瑞士法郎购买加密货币。高达 990 欧元,无需额外 KYC。对于欧洲的零售和企业客户", "polygonscan_history": "多边形扫描历史" }