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 ff8ac3405..753dd5f73 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -954,9 +954,8 @@ 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)); } Future<void> _setInitialHeight() async { diff --git a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart index 03d16dd3f..98c3c16a6 100644 --- a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart +++ b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart @@ -297,10 +297,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_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<CoinsInfoRow> Function(Int32 index); typedef freeze_coin = Void Function(Int32 index); typedef thaw_coin = Void Function(Int32 index); + +typedef sign_message = Pointer<Utf8> Function(Pointer<Utf8> message, Pointer<Utf8> 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<CoinsInfoRow> Function(int); typedef FreezeCoin = void Function(int); typedef ThawCoin = void Function(int); + +typedef SignMessage = Pointer<Utf8> Function(Pointer<Utf8>, Pointer<Utf8>); 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<NativeFunction<trusted_daemon>>('trusted_daemon') .asFunction<TrustedDaemon>(); +final signMessageNative = moneroApi + .lookup<NativeFunction<sign_message>>('sign_message') + .asFunction<SignMessage>(); + 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<bool> trustedDaemon() async => trustedDaemonNative() != 0; \ No newline at end of file +Future<bool> 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<String> 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<String> 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<String> 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<void> 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<void>( + 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 8777a4a4a..4dc218423 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -228,6 +228,7 @@ import 'package:cw_core/crypto_currency.dart'; import 'package:cake_wallet/entities/qr_view_data.dart'; import 'package:bitcoin_flutter/bitcoin_flutter.dart' as btc; +import 'buy/dfx/dfx_buy_provider.dart'; import 'core/totp_request_details.dart'; import 'src/screens/settings/desktop_settings/desktop_settings_page.dart'; @@ -799,6 +800,9 @@ Future<void> setup({ getIt.registerFactory<RobinhoodBuyProvider>( () => RobinhoodBuyProvider(wallet: getIt.get<AppStore>().wallet!)); + getIt.registerFactory<DFXBuyProvider>( + () => DFXBuyProvider(wallet: getIt.get<AppStore>().wallet!)); + getIt.registerFactory<OnRamperBuyProvider>(() => OnRamperBuyProvider( settingsStore: getIt.get<AppStore>().settingsStore, wallet: getIt.get<AppStore>().wallet!, @@ -944,7 +948,7 @@ Future<void> setup({ getIt.registerFactory(() => BuyAmountViewModel()); - getIt.registerFactory(() => BuyOptionsPage()); + getIt.registerFactory(() => BuyOptionsPage(getIt.get<DashboardViewModel>())); getIt.registerFactory(() { final wallet = getIt.get<AppStore>().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<BuyProviderType> 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<OnRamperBuyProvider>().launchProvider(context); - break; - case BuyProviderType.Robinhood: - await getIt.get<RobinhoodBuyProvider>().launchProvider(context); - break; - } - break; - case WalletType.nano: - case WalletType.banano: - case WalletType.monero: - await getIt.get<OnRamperBuyProvider>().launchProvider(context); - break; - default: - await showPopUp<void>( - 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<void> _launchProviderByType(BuildContext context, BuyProviderType providerType) async { + switch (providerType) { + case BuyProviderType.AskEachTime: + Navigator.pushNamed(context, Routes.buy); + break; + case BuyProviderType.Onramper: + await getIt.get<OnRamperBuyProvider>().launchProvider(context); + break; + case BuyProviderType.Robinhood: + await getIt.get<RobinhoodBuyProvider>().launchProvider(context); + break; + case BuyProviderType.DFX: + await getIt.get<DFXBuyProvider>().launchProvider(context); + break; + default: + throw UnsupportedError('Unsupported buy provider type'); + } + } + + + static Future<void> _showErrorDialog(BuildContext context, String errorMessage) async { + await showPopUp<void>( + 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<OptionTileTheme>()?.useDarkImage ?? false; - final iconRobinhood = - Image.asset(isLightMode ? iconLightRobinhood : iconDarkRobinhood, height: 40, width: 40); - final iconOnramper = - Image.asset(isLightMode ? iconLightOnramper : iconDarkOnramper, height: 40, width: 40); + final isLightMode = + Theme.of(context).extension<OptionTileTheme>()?.useDarkImage ?? false; + final iconRobinhood = Image.asset( + isLightMode ? iconLightRobinhood : iconDarkRobinhood, + height: 40, + width: 40); + final iconOnramper = Image.asset( + isLightMode ? iconLightOnramper : iconDarkOnramper, + height: 40, + width: 40); + final 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<OnRamperBuyProvider>().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<OnRamperBuyProvider>() + .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<RobinhoodBuyProvider>().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<RobinhoodBuyProvider>() + .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<DFXBuyProvider>() + .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<TransactionTradeTheme>()!.detailsTitlesColor, + color: Theme.of(context) + .extension<TransactionTradeTheme>()! + .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<WalletType, BuyProviderType> 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<String, bool> trocadorProviderStates = ObservableMap<String, bool>(); + @observable + ObservableMap<WalletType, BuyProviderType> defaultBuyProviders = ObservableMap<WalletType, BuyProviderType>(); + @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 bd02e10ed..f0f107fc5 100644 --- a/lib/view_model/dashboard/dashboard_view_model.dart +++ b/lib/view_model/dashboard/dashboard_view_model.dart @@ -287,10 +287,14 @@ abstract class DashboardViewModelBase with Store { Map<String, List<FilterItem>> filterItems; - BuyProviderType get defaultBuyProvider => settingsStore.defaultBuyProvider; + BuyProviderType get defaultBuyProvider => + settingsStore.defaultBuyProviders[wallet.type] ?? + BuyProviderType.AskEachTime; bool get isBuyEnabled => settingsStore.isBitcoinBuyEnabled; + List<BuyProviderType> 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<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo> _wallet; + final WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, + 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<BuyProviderType> 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/res/values/strings_ar.arb b/res/values/strings_ar.arb index 50d119a41..56b01da32 100644 --- a/res/values/strings_ar.arb +++ b/res/values/strings_ar.arb @@ -755,5 +755,6 @@ "bitcoin_silent_payments": "مدفوعات بيتكوين صامتة", "bitcoin_legacy": "إرث البيتكوين", "primary_silent_address": "العنوان الصامت الأساسي", + "dfx_option_description": "ﺎﺑﻭﺭﻭﺃ ﻲﻓ ﺕﺎﻛﺮﺸﻟﺍﻭ ﺔﺋﺰﺠﺘﻟﺍ ءﻼﻤﻌﻟ .ﻲﻓﺎﺿﺇ KYC ﻥﻭﺪﺑ ﻭﺭﻮﻳ 990 ﻰﻟﺇ ﻞﺼﻳ ﺎﻣ .ﻱﺮﺴﻳﻮﺴﻟﺍ", "polygonscan_history": "ﻥﺎﻜﺴﻧﻮﺠﻴﻟﻮﺑ ﺦﻳﺭﺎﺗ" } diff --git a/res/values/strings_bg.arb b/res/values/strings_bg.arb index 229db10e1..a8c8d8fde 100644 --- a/res/values/strings_bg.arb +++ b/res/values/strings_bg.arb @@ -751,5 +751,6 @@ "bitcoin_silent_payments": "Биткойн мълчаливи плащания", "bitcoin_legacy": "Legacy Bitcoin", "primary_silent_address": "Първичен безшумен адрес", + "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 6c0f3d1fe..ef72450b7 100644 --- a/res/values/strings_cs.arb +++ b/res/values/strings_cs.arb @@ -751,5 +751,6 @@ "bitcoin_silent_payments": "Bitcoinové tiché platby", "bitcoin_legacy": "Bitcoin Legacy", "primary_silent_address": "Primární tichá adresa", + "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 13b9ab1cb..25a321007 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -759,5 +759,6 @@ "bitcoin_silent_payments": "Bitcoin stille Zahlungen", "bitcoin_legacy": "Bitcoin -Erbe", "primary_silent_address": "Primäre stille Adresse", + "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 7e51df51d..6316b7b10 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -760,5 +760,6 @@ "bitcoin_silent_payments": "Bitcoin Silent Payments", "bitcoin_legacy": "Bitcoin Legacy", "primary_silent_address": "Primary Silent Address", + "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 84d8301c3..557298e85 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -752,6 +752,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)", "mainnet": "Red", "trocador_anonpay_invoice": "Trocador Anonpay Factura", diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index 0ae40142d..abf25560b 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -759,5 +759,6 @@ "bitcoin_silent_payments": "Bitcoin Paiements silencieux", "bitcoin_legacy": "Bitcoin Legacy", "primary_silent_address": "Adresse silencieuse primaire", + "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 a5fb01c8b..c24c1ed2d 100644 --- a/res/values/strings_ha.arb +++ b/res/values/strings_ha.arb @@ -737,5 +737,6 @@ "bitcoin_silent_payments": "Bitcoin Silent biya", "bitcoin_legacy": "Gado bitcoin", "primary_silent_address": "Adireshin Silent na farko", + "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 0d4df68e9..f85ab72d1 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -759,5 +759,6 @@ "bitcoin_silent_payments": "बिटकॉइन मूक भुगतान", "bitcoin_legacy": "बिटकॉइन विरासत", "primary_silent_address": "प्राथमिक मूक पता", + "dfx_option_description": "EUR और CHF के साथ क्रिप्टो खरीदें। अतिरिक्त केवाईसी के बिना 990€ तक। यूरोप में खुदरा और कॉर्पोरेट ग्राहकों के लिए", "polygonscan_history": "पॉलीगॉनस्कैन इतिहास" } diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index 4acff7d7a..0acc53074 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -750,6 +750,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)", "mainnet": "Mainnet", "trocador_anonpay_invoice": "Trokador anonpay faktura", diff --git a/res/values/strings_id.arb b/res/values/strings_id.arb index 84bb151e9..411319809 100644 --- a/res/values/strings_id.arb +++ b/res/values/strings_id.arb @@ -740,6 +740,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)", "mainnet": "Mainnet", "trocador_anonpay_invoice": "Faktur Trocador Anonpay", diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index 53b80aa03..bc46ef91c 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -759,5 +759,6 @@ "bitcoin_silent_payments": "Pagamenti silenziosi bitcoin", "bitcoin_legacy": "Bitcoin Legacy", "primary_silent_address": "Indirizzo silenzioso primario", + "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 32b38c078..947870d0c 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -759,5 +759,6 @@ "bitcoin_silent_payments": "ビットコインサイレント支払い", "bitcoin_legacy": "ビットコインレガシー", "primary_silent_address": "主なサイレントアドレス", + "dfx_option_description": "EUR と CHF で暗号通貨を購入します。追加のKYCなしで最大990ユーロ。ヨーロッパの小売および法人顧客向け", "polygonscan_history": "ポリゴンスキャン履歴" } diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index 024ba0fa1..06a052df8 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -757,5 +757,6 @@ "bitcoin_silent_payments": "비트 코인 사일런트 지불", "bitcoin_legacy": "비트 코인 유산", "primary_silent_address": "기본 조용한 주소", + "dfx_option_description": "EUR 및 CHF로 암호화폐를 구매하세요. 추가 KYC 없이 최대 990€. 유럽의 소매 및 기업 고객용", "polygonscan_history": "다각형 스캔 기록" } diff --git a/res/values/strings_my.arb b/res/values/strings_my.arb index affb4ce4d..1aeea076f 100644 --- a/res/values/strings_my.arb +++ b/res/values/strings_my.arb @@ -757,5 +757,6 @@ "bitcoin_silent_payments": "Bitcoin အသံတိတ်ငွေပေးချေမှု", "bitcoin_legacy": "Bitcoin အမွေ", "primary_silent_address": "မူလအသံတိတ်လိပ်စာ", + "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 45e443591..4dd59aef0 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -752,6 +752,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)", "mainnet": "Maimet", "trocador_anonpay_invoice": "Trocador anonpay factuur", diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index 8836b63cc..4969c4bcd 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -759,5 +759,6 @@ "bitcoin_silent_payments": "Bitcoin ciche płatności", "bitcoin_legacy": "Dziedzictwo bitcoinowe", "primary_silent_address": "Podstawowy cichy adres", + "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 ce39236e3..bb27fd823 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -758,5 +758,6 @@ "bitcoin_silent_payments": "Bitcoin Pagamentos Silenciosos", "bitcoin_legacy": "Bitcoin Legado", "primary_silent_address": "Endereço silencioso primário", + "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 8c49f56f4..cfb57399b 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -759,5 +759,6 @@ "bitcoin_silent_payments": "Биткойн молчаливые платежи", "bitcoin_legacy": "Биткойн наследие", "primary_silent_address": "Основной тихий адрес", + "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 344e6d5bd..454690411 100644 --- a/res/values/strings_th.arb +++ b/res/values/strings_th.arb @@ -757,5 +757,6 @@ "bitcoin_silent_payments": "Bitcoin Silent Payments", "bitcoin_legacy": "มรดก Bitcoin", "primary_silent_address": "ที่อยู่เงียบหลัก", + "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 43bb716e3..30e9a0a16 100644 --- a/res/values/strings_tl.arb +++ b/res/values/strings_tl.arb @@ -753,5 +753,6 @@ "bitcoin_silent_payments": "Bitcoin tahimik na pagbabayad", "bitcoin_legacy": "Pamana ng Bitcoin", "primary_silent_address": "Pangunahing tahimik na address", + "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 950e3f9e2..30c697de8 100644 --- a/res/values/strings_tr.arb +++ b/res/values/strings_tr.arb @@ -757,5 +757,6 @@ "bitcoin_silent_payments": "Bitcoin sessiz ödemeler", "bitcoin_legacy": "Bitcoin Mirası", "primary_silent_address": "Birincil sessiz adres", + "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 4573d208a..43744125a 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -752,6 +752,7 @@ "seedtype_polyseed": "Полісей (16 слів)", "seed_language_czech": "Чеський", "seed_language_korean": "Корейський", + "dfx_option_description": "Купуйте криптовалюту за EUR і CHF. До 990 євро без додаткового KYC. Для роздрібних і корпоративних клієнтів у Європі", "seed_language_chinese_traditional": "Китайський (традиційний)", "mainnet": "Мейннет", "trocador_anonpay_invoice": "Рахунок -фактура Trocador Anonpay", diff --git a/res/values/strings_ur.arb b/res/values/strings_ur.arb index ab505c3d6..192ca7d23 100644 --- a/res/values/strings_ur.arb +++ b/res/values/strings_ur.arb @@ -751,5 +751,6 @@ "bitcoin_silent_payments": "بٹ کوائن خاموش ادائیگی", "bitcoin_legacy": "بٹ کوائن میراث", "primary_silent_address": "بنیادی خاموش پتہ", + "dfx_option_description": "EUR ﺭﻭﺍ CHF ﯽﻓﺎﺿﺍ ۔ﮟﯾﺪﯾﺮﺧ ﻮﭩﭘﺮﮐ ﮫﺗﺎﺳ ﮯﮐ KYC ﮯﯿﻟ ﮯﮐ ﻦﯿﻓﺭﺎﺻ ﭧﯾﺭﻮﭘﺭﺎﮐ ﺭﻭﺍ ﮦﺩﺭﻮﺧ ﮟ", "polygonscan_history": "ﺦﯾﺭﺎﺗ ﯽﮐ ﻦﯿﮑﺳﺍ ﻥﻮﮔ ﯽﻟﻮﭘ" } diff --git a/res/values/strings_yo.arb b/res/values/strings_yo.arb index 561a6863a..390e66b56 100644 --- a/res/values/strings_yo.arb +++ b/res/values/strings_yo.arb @@ -753,5 +753,6 @@ "bitcoin_silent_payments": "Awọn sisanwọle bitcoin", "bitcoin_legacy": "Olokan Bitcoin", "primary_silent_address": "Adirẹsi ipalọlọ akọkọ", + "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 9f7c84f47..5f8e432bc 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -758,5 +758,6 @@ "bitcoin_silent_payments": "比特币无声付款", "bitcoin_legacy": "比特币遗产", "primary_silent_address": "主要无声地址", + "dfx_option_description": "用欧元和瑞士法郎购买加密货币。高达 990 欧元,无需额外 KYC。对于欧洲的零售和企业客户", "polygonscan_history": "多边形扫描历史" }