diff --git a/assets/images/dfx_dark.png b/assets/images/dfx_dark.png new file mode 100644 index 000000000..cbba87372 Binary files /dev/null and b/assets/images/dfx_dark.png differ diff --git a/assets/images/dfx_light.png b/assets/images/dfx_light.png new file mode 100644 index 000000000..e4836be3e Binary files /dev/null and b/assets/images/dfx_light.png differ diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index 85053133f..894a969a0 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -725,8 +725,7 @@ abstract class ElectrumWalletBase final index = address != null ? walletAddresses.addresses.firstWhere((element) => 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": "多边形扫描历史" }